From 1782bb6e87a4ef35e444cbd4d0c29933f6969ec0 Mon Sep 17 00:00:00 2001 From: Jesse Chappell Date: Sun, 4 Jul 2021 15:51:07 -0400 Subject: [PATCH 01/36] git subrepo clone --branch=sononew --force https://github.com/essej/aoo.git deps/aoo subrepo: subdir: "deps/aoo" merged: "e9055610a5" upstream: origin: "https://github.com/essej/aoo.git" branch: "sononew" commit: "e9055610a5" git-subrepo: version: "0.4.1" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "a04d8c2" --- deps/aoo/.git-ci/gitlab-iem.yml | 268 ++- deps/aoo/.git-ci/requirements.apt | 1 - deps/aoo/.git-ci/requirements.brew | 1 - deps/aoo/.git-ci/requirements.msys2 | 1 - deps/aoo/.gitattributes | 2 +- deps/aoo/.gitignore | 2 + deps/aoo/.gitmodules | 3 + deps/aoo/.gitrepo | 6 +- deps/aoo/CMakeLists.txt | 182 ++ deps/aoo/aoo/CMakeLists.txt | 111 + deps/aoo/aoo/src/aoo.cpp | 440 ++++ deps/aoo/aoo/src/buffer.cpp | 388 +++ deps/aoo/aoo/src/buffer.hpp | 199 ++ deps/aoo/aoo/src/codec.hpp | 145 ++ deps/aoo/aoo/src/codec/opus.cpp | 499 ++++ deps/aoo/aoo/src/codec/pcm.cpp | 438 ++++ deps/aoo/aoo/src/imp.hpp | 171 ++ deps/aoo/{lib/src => aoo/src/net}/SLIP.hpp | 24 +- deps/aoo/aoo/src/net/client.cpp | 1927 +++++++++++++++ deps/aoo/aoo/src/net/client.hpp | 549 +++++ deps/aoo/aoo/src/net/commands.hpp | 27 + deps/aoo/aoo/src/net/server.cpp | 1118 +++++++++ deps/aoo/aoo/src/net/server.hpp | 323 +++ deps/aoo/aoo/src/resampler.cpp | 150 ++ deps/aoo/aoo/src/resampler.hpp | 35 + deps/aoo/aoo/src/sink.cpp | 2074 +++++++++++++++++ deps/aoo/aoo/src/sink.hpp | 402 ++++ deps/aoo/aoo/src/source.cpp | 1528 ++++++++++++ deps/aoo/aoo/src/source.hpp | 274 +++ deps/aoo/{lib => aoo}/src/time_dll.hpp | 2 +- deps/aoo/aoo/src/timer.cpp | 118 + deps/aoo/aoo/src/timer.hpp | 59 + deps/aoo/common/lockfree.hpp | 639 +++++ deps/aoo/common/net_utils.cpp | 720 ++++++ deps/aoo/common/net_utils.hpp | 144 ++ deps/aoo/common/ntp.cpp | 189 ++ deps/aoo/{lib/src => common}/sync.cpp | 156 +- deps/aoo/common/sync.hpp | 246 ++ deps/aoo/{lib/src => common}/time.cpp | 35 +- deps/aoo/common/time.hpp | 102 + .../aoo/aoo_utils.hpp => common/utils.hpp} | 59 +- deps/aoo/deps/md5/CMakeLists.txt | 3 + deps/aoo/deps/opus | 1 + deps/aoo/deps/oscpack/CMakeLists.txt | 77 +- deps/aoo/include/aoo/aoo.h | 946 ++++++++ deps/aoo/include/aoo/aoo.hpp | 387 +++ deps/aoo/include/aoo/aoo_net.h | 366 +++ deps/aoo/include/aoo/aoo_net.hpp | 222 ++ deps/aoo/include/aoo/aoo_types.h | 194 ++ .../{lib/aoo => include/aoo/codec}/aoo_opus.h | 15 +- .../{lib/aoo => include/aoo/codec}/aoo_pcm.h | 10 +- deps/aoo/lib/aoo/aoo.h | 734 ------ deps/aoo/lib/aoo/aoo.hpp | 356 --- deps/aoo/lib/aoo/aoo_net.h | 264 --- deps/aoo/lib/aoo/aoo_net.hpp | 154 -- deps/aoo/lib/aoo/aoo_types.h | 77 - deps/aoo/lib/src/client.cpp | 1421 ----------- deps/aoo/lib/src/client.hpp | 344 --- deps/aoo/lib/src/codec_opus.cpp | 456 ---- deps/aoo/lib/src/codec_pcm.cpp | 402 ---- deps/aoo/lib/src/common.cpp | 1101 --------- deps/aoo/lib/src/common.hpp | 294 --- deps/aoo/lib/src/lockfree.hpp | 237 -- deps/aoo/lib/src/net_utils.cpp | 117 - deps/aoo/lib/src/net_utils.hpp | 119 - deps/aoo/lib/src/server.cpp | 1307 ----------- deps/aoo/lib/src/server.hpp | 250 -- deps/aoo/lib/src/sink.cpp | 1662 ------------- deps/aoo/lib/src/sink.hpp | 363 --- deps/aoo/lib/src/source.cpp | 1537 ------------ deps/aoo/lib/src/source.hpp | 228 -- deps/aoo/lib/src/sync.hpp | 125 - deps/aoo/lib/src/time.hpp | 83 - deps/aoo/pd/CMakeLists.txt | 91 + deps/aoo/pd/Makefile | 156 -- deps/aoo/pd/aoo_net-help.pd | 348 +++ deps/aoo/pd/aoo_pack~-test.pd | 24 +- deps/aoo/pd/aoo_receive~-help.pd | 223 +- deps/aoo/pd/aoo_receive~-test.pd | 113 +- deps/aoo/pd/aoo_send~-help.pd | 592 +++-- deps/aoo/pd/aoo_server-help.pd | 125 - deps/aoo/pd/pd-lib-builder/CHANGELOG.txt | 104 - .../pd/pd-lib-builder/Makefile.pdlibbuilder | 1340 ----------- deps/aoo/pd/pd-lib-builder/README.md | 199 -- deps/aoo/pd/pd-lib-builder/tips-tricks.md | 231 -- deps/aoo/pd/src/aoo_client.c | 269 --- deps/aoo/pd/src/aoo_client.cpp | 781 +++++++ deps/aoo/pd/src/aoo_common.c | 412 ---- deps/aoo/pd/src/aoo_common.cpp | 313 +++ deps/aoo/pd/src/aoo_common.h | 102 - deps/aoo/pd/src/aoo_common.hpp | 76 + deps/aoo/pd/src/aoo_net.c | 313 --- deps/aoo/pd/src/aoo_net.h | 63 - deps/aoo/pd/src/aoo_node.c | 601 ----- deps/aoo/pd/src/aoo_node.cpp | 478 ++++ .../aoo/pd/src/{aoo_pack~.c => aoo_pack~.cpp} | 296 +-- deps/aoo/pd/src/aoo_receive~.c | 528 ----- deps/aoo/pd/src/aoo_receive~.cpp | 645 +++++ deps/aoo/pd/src/aoo_route.c | 101 - deps/aoo/pd/src/aoo_route.cpp | 108 + deps/aoo/pd/src/aoo_send~.c | 729 ------ deps/aoo/pd/src/aoo_send~.cpp | 653 ++++++ deps/aoo/pd/src/aoo_server.c | 152 -- deps/aoo/pd/src/aoo_server.cpp | 171 ++ deps/aoo/pd/src/aoo_setup.c | 217 -- deps/aoo/pd/src/aoo_setup.cpp | 174 ++ deps/aoo/pd/src/aoo_unpack~.c | 309 --- deps/aoo/pd/src/aoo_unpack~.cpp | 341 +++ deps/aoo/readme.rst | 2 + deps/aoo/sc/CMakeLists.txt | 75 + deps/aoo/sc/HelpSource/AooAddr.schelp | 75 + deps/aoo/sc/HelpSource/AooClient.schelp | 0 deps/aoo/sc/HelpSource/AooReceive.schelp | 98 + deps/aoo/sc/HelpSource/AooReceiveCtl.schelp | 186 ++ deps/aoo/sc/HelpSource/AooSend.schelp | 89 + deps/aoo/sc/HelpSource/AooSendCtl.schelp | 199 ++ deps/aoo/sc/HelpSource/AooServer.schelp | 0 deps/aoo/sc/HelpSource/Classes/AooAddr.schelp | 30 + .../HelpSource/Classes/AooFormatOpus.schelp | 55 + .../sc/HelpSource/Classes/AooFormatPCM.schelp | 33 + deps/aoo/sc/HelpSource/Classes/AooPeer.schelp | 41 + .../sc/HelpSource/Classes/AooReceive.schelp | 46 + deps/aoo/sc/HelpSource/Classes/AooSend.schelp | 43 + deps/aoo/sc/classes/Aoo.sc | 386 +++ deps/aoo/sc/classes/AooClient.sc | 265 +++ deps/aoo/sc/classes/AooReceive.sc | 144 ++ deps/aoo/sc/classes/AooSend.sc | 162 ++ deps/aoo/sc/classes/AooServer.sc | 96 + deps/aoo/sc/src/Aoo.cpp | 547 +++++ deps/aoo/sc/src/Aoo.hpp | 239 ++ deps/aoo/sc/src/AooClient.cpp | 669 ++++++ deps/aoo/sc/src/AooClient.hpp | 64 + deps/aoo/sc/src/AooNode.cpp | 443 ++++ deps/aoo/sc/src/AooReceive.cpp | 399 ++++ deps/aoo/sc/src/AooReceive.hpp | 35 + deps/aoo/sc/src/AooSend.cpp | 453 ++++ deps/aoo/sc/src/AooSend.hpp | 59 + deps/aoo/sc/src/AooServer.cpp | 210 ++ deps/aoo/sc/src/AooServer.hpp | 21 + deps/aoo/sc/src/rt_shared_ptr.hpp | 80 + 140 files changed, 25956 insertions(+), 18275 deletions(-) delete mode 100644 deps/aoo/.git-ci/requirements.apt delete mode 100644 deps/aoo/.git-ci/requirements.brew delete mode 100644 deps/aoo/.git-ci/requirements.msys2 create mode 100644 deps/aoo/.gitmodules create mode 100644 deps/aoo/CMakeLists.txt create mode 100644 deps/aoo/aoo/CMakeLists.txt create mode 100644 deps/aoo/aoo/src/aoo.cpp create mode 100644 deps/aoo/aoo/src/buffer.cpp create mode 100644 deps/aoo/aoo/src/buffer.hpp create mode 100644 deps/aoo/aoo/src/codec.hpp create mode 100644 deps/aoo/aoo/src/codec/opus.cpp create mode 100644 deps/aoo/aoo/src/codec/pcm.cpp create mode 100644 deps/aoo/aoo/src/imp.hpp rename deps/aoo/{lib/src => aoo/src/net}/SLIP.hpp (87%) create mode 100644 deps/aoo/aoo/src/net/client.cpp create mode 100644 deps/aoo/aoo/src/net/client.hpp create mode 100644 deps/aoo/aoo/src/net/commands.hpp create mode 100644 deps/aoo/aoo/src/net/server.cpp create mode 100644 deps/aoo/aoo/src/net/server.hpp create mode 100644 deps/aoo/aoo/src/resampler.cpp create mode 100644 deps/aoo/aoo/src/resampler.hpp create mode 100644 deps/aoo/aoo/src/sink.cpp create mode 100644 deps/aoo/aoo/src/sink.hpp create mode 100644 deps/aoo/aoo/src/source.cpp create mode 100644 deps/aoo/aoo/src/source.hpp rename deps/aoo/{lib => aoo}/src/time_dll.hpp (97%) create mode 100644 deps/aoo/aoo/src/timer.cpp create mode 100644 deps/aoo/aoo/src/timer.hpp create mode 100644 deps/aoo/common/lockfree.hpp create mode 100644 deps/aoo/common/net_utils.cpp create mode 100644 deps/aoo/common/net_utils.hpp create mode 100644 deps/aoo/common/ntp.cpp rename deps/aoo/{lib/src => common}/sync.cpp (50%) create mode 100644 deps/aoo/common/sync.hpp rename deps/aoo/{lib/src => common}/time.cpp (84%) create mode 100644 deps/aoo/common/time.hpp rename deps/aoo/{lib/aoo/aoo_utils.hpp => common/utils.hpp} (63%) create mode 100644 deps/aoo/deps/md5/CMakeLists.txt create mode 160000 deps/aoo/deps/opus create mode 100644 deps/aoo/include/aoo/aoo.h create mode 100644 deps/aoo/include/aoo/aoo.hpp create mode 100644 deps/aoo/include/aoo/aoo_net.h create mode 100644 deps/aoo/include/aoo/aoo_net.hpp create mode 100644 deps/aoo/include/aoo/aoo_types.h rename deps/aoo/{lib/aoo => include/aoo/codec}/aoo_opus.h (61%) rename deps/aoo/{lib/aoo => include/aoo/codec}/aoo_pcm.h (81%) delete mode 100644 deps/aoo/lib/aoo/aoo.h delete mode 100644 deps/aoo/lib/aoo/aoo.hpp delete mode 100644 deps/aoo/lib/aoo/aoo_net.h delete mode 100644 deps/aoo/lib/aoo/aoo_net.hpp delete mode 100644 deps/aoo/lib/aoo/aoo_types.h delete mode 100644 deps/aoo/lib/src/client.cpp delete mode 100644 deps/aoo/lib/src/client.hpp delete mode 100644 deps/aoo/lib/src/codec_opus.cpp delete mode 100644 deps/aoo/lib/src/codec_pcm.cpp delete mode 100644 deps/aoo/lib/src/common.cpp delete mode 100644 deps/aoo/lib/src/common.hpp delete mode 100644 deps/aoo/lib/src/lockfree.hpp delete mode 100644 deps/aoo/lib/src/net_utils.cpp delete mode 100644 deps/aoo/lib/src/net_utils.hpp delete mode 100644 deps/aoo/lib/src/server.cpp delete mode 100644 deps/aoo/lib/src/server.hpp delete mode 100644 deps/aoo/lib/src/sink.cpp delete mode 100644 deps/aoo/lib/src/sink.hpp delete mode 100644 deps/aoo/lib/src/source.cpp delete mode 100644 deps/aoo/lib/src/source.hpp delete mode 100644 deps/aoo/lib/src/sync.hpp delete mode 100644 deps/aoo/lib/src/time.hpp create mode 100644 deps/aoo/pd/CMakeLists.txt delete mode 100644 deps/aoo/pd/Makefile create mode 100644 deps/aoo/pd/aoo_net-help.pd delete mode 100644 deps/aoo/pd/aoo_server-help.pd delete mode 100644 deps/aoo/pd/pd-lib-builder/CHANGELOG.txt delete mode 100644 deps/aoo/pd/pd-lib-builder/Makefile.pdlibbuilder delete mode 100644 deps/aoo/pd/pd-lib-builder/README.md delete mode 100644 deps/aoo/pd/pd-lib-builder/tips-tricks.md delete mode 100644 deps/aoo/pd/src/aoo_client.c create mode 100644 deps/aoo/pd/src/aoo_client.cpp delete mode 100644 deps/aoo/pd/src/aoo_common.c create mode 100644 deps/aoo/pd/src/aoo_common.cpp delete mode 100644 deps/aoo/pd/src/aoo_common.h create mode 100644 deps/aoo/pd/src/aoo_common.hpp delete mode 100644 deps/aoo/pd/src/aoo_net.c delete mode 100644 deps/aoo/pd/src/aoo_net.h delete mode 100644 deps/aoo/pd/src/aoo_node.c create mode 100644 deps/aoo/pd/src/aoo_node.cpp rename deps/aoo/pd/src/{aoo_pack~.c => aoo_pack~.cpp} (50%) delete mode 100644 deps/aoo/pd/src/aoo_receive~.c create mode 100644 deps/aoo/pd/src/aoo_receive~.cpp delete mode 100644 deps/aoo/pd/src/aoo_route.c create mode 100644 deps/aoo/pd/src/aoo_route.cpp delete mode 100644 deps/aoo/pd/src/aoo_send~.c create mode 100644 deps/aoo/pd/src/aoo_send~.cpp delete mode 100644 deps/aoo/pd/src/aoo_server.c create mode 100644 deps/aoo/pd/src/aoo_server.cpp delete mode 100644 deps/aoo/pd/src/aoo_setup.c create mode 100644 deps/aoo/pd/src/aoo_setup.cpp delete mode 100644 deps/aoo/pd/src/aoo_unpack~.c create mode 100644 deps/aoo/pd/src/aoo_unpack~.cpp create mode 100644 deps/aoo/sc/CMakeLists.txt create mode 100644 deps/aoo/sc/HelpSource/AooAddr.schelp create mode 100644 deps/aoo/sc/HelpSource/AooClient.schelp create mode 100644 deps/aoo/sc/HelpSource/AooReceive.schelp create mode 100644 deps/aoo/sc/HelpSource/AooReceiveCtl.schelp create mode 100644 deps/aoo/sc/HelpSource/AooSend.schelp create mode 100644 deps/aoo/sc/HelpSource/AooSendCtl.schelp create mode 100644 deps/aoo/sc/HelpSource/AooServer.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooAddr.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooFormatOpus.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooFormatPCM.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooPeer.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooReceive.schelp create mode 100644 deps/aoo/sc/HelpSource/Classes/AooSend.schelp create mode 100644 deps/aoo/sc/classes/Aoo.sc create mode 100644 deps/aoo/sc/classes/AooClient.sc create mode 100644 deps/aoo/sc/classes/AooReceive.sc create mode 100644 deps/aoo/sc/classes/AooSend.sc create mode 100644 deps/aoo/sc/classes/AooServer.sc create mode 100644 deps/aoo/sc/src/Aoo.cpp create mode 100644 deps/aoo/sc/src/Aoo.hpp create mode 100644 deps/aoo/sc/src/AooClient.cpp create mode 100644 deps/aoo/sc/src/AooClient.hpp create mode 100644 deps/aoo/sc/src/AooNode.cpp create mode 100644 deps/aoo/sc/src/AooReceive.cpp create mode 100644 deps/aoo/sc/src/AooReceive.hpp create mode 100644 deps/aoo/sc/src/AooSend.cpp create mode 100644 deps/aoo/sc/src/AooSend.hpp create mode 100644 deps/aoo/sc/src/AooServer.cpp create mode 100644 deps/aoo/sc/src/AooServer.hpp create mode 100644 deps/aoo/sc/src/rt_shared_ptr.hpp diff --git a/deps/aoo/.git-ci/gitlab-iem.yml b/deps/aoo/.git-ci/gitlab-iem.yml index aba8fd1a..2c43bc1e 100644 --- a/deps/aoo/.git-ci/gitlab-iem.yml +++ b/deps/aoo/.git-ci/gitlab-iem.yml @@ -1,6 +1,266 @@ ---- +### global variables + variables: - SRCDIR: pd/ + PDVERSION: 0.51-1 + VST2DIR: /tmp/vst2sdk + VST3DIR: /tmp/vst3sdk + SC_PATH: /tmp/supercollider + +### script snippets + +.script:deps: &script_deps +# supercollider + - git clone https://github.com/supercollider/supercollider.git "${SC_PATH}" + - pushd "${SC_PATH}" + - git submodule update --init + - popd +# submodules (Opus) + - git submodule update --init + - cmake -Hdeps/opus -Bdeps/opus/build -DOPUS_STACK_PROTECTOR=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -DCMAKE_INSTALL_PREFIX=deps/opus + - make -C deps/opus/build install VERBOSE=1 + +.script:cmake: &script_cmake + - prefix="$(pwd)/build/${CI_JOB_NAME%_*}" + - cmake -H. -Bbuild -DSTATIC_LIBS=${STATIC_LIBS} -DCMAKE_INSTALL_PREFIX="$prefix/aoo" + -DPD_EXTENSION=${PD_EXTENSION} -DPD_DIR="${PD_PATH}" -DPD_INSTALLDIR="$prefix/pd" + -DSUPERNOVA=${SUPERNOVA} -DSC_INCLUDEDIR="${SC_PATH}" -DSC_INSTALLDIR="$prefix/sc" + +### build snippets +.script:make: &script_make + - make -C build VERBOSE=1 +.script:make_install: &script_make_install + - make -C build install/strip VERBOSE=1 + +####################################################################### +### configuration templates (to be used for snapshot and release builds) +.build:script: + stage: build + script: + - *script_make + - *script_make_install + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${CI_JOB_NAME%_*} + paths: + - "build/${CI_JOB_NAME%_*}/aoo" + - "build/${CI_JOB_NAME%_*}/pd/aoo" + - "build/${CI_JOB_NAME%_*}/sc/aoo" + +.snapshot: + except: + - tags + artifacts: + expire_in: 1 week + +.release: + only: + - tags + +.build:linux: &build_linux + extends: .build:script + image: gcc + variables: + SUPERNOVA: 1 + STATIC_LIBS: 1 + before_script: + - apt-get update && apt-get install -y --no-install-recommends make cmake git + puredata-dev puredata + - *script_deps + - *script_cmake + +.build:linux_i386: &build_linux_i386 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:i386 + variables: + PD_EXTENSION: l_i386 + +.build:linux_armhf: &build_linux_armhf + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:armhf + variables: + PD_EXTENSION: l_arm + +.build:linux_arm64: &build_linux_arm64 + extends: .build:linux + image: registry.git.iem.at/devtools/docker/debiancross:arm64 + variables: + PD_EXTENSION: l_arm64 + +.build:macos: &build_macos + extends: .build:script + tags: + - osx + variables: + SUPERNOVA: 1 + before_script: + - wget -q -O Pd.tgz http://msp.ucsd.edu/Software/pd-${PDVERSION}.mac.tar.gz + - rm -rf /Applications/Pd*.app/ + - tar xvf Pd.tgz -C /Applications/ + - rm -f Pd.tgz + - *script_deps + - *script_cmake + +.build:w32: &build_w32 + extends: .build:script + tags: + - windows + variables: + IEMCI_CONFIGURATIONS: mingw32 + SUPERNOVA: 1 + STATIC_LIBS: 1 + PD_URL: http://msp.ucsd.edu/Software/pd-${PDVERSION}-i386.msw.zip + before_script: + - pacman --noconfirm -S cmake + - wget -q -O Pd.zip ${PD_URL} +# install locally to avoid hassles with Windows vs. Unix file paths. +# NOTE: PD_PATH is also used in .script:cmake. + - export PD_PATH=/tmp/pd + - rm -rf "${PD_PATH}"; mkdir -p "${PD_PATH}" + - unzip -q Pd.zip -d "${PD_PATH}" + - mv -v "${PD_PATH}"/*/* "${PD_PATH}" + - *script_deps + - *script_cmake + +.build:w64: &build_w64 + extends: .build:w32 + variables: + IEMCI_CONFIGURATIONS: mingw64 + PD_EXTENSION: m_amd64 + PD_URL: http://msp.ucsd.edu/Software/pd-${PDVERSION}.msw.zip + +### job templates +.Linux: + <<: *build_linux +.Linux_i386: + allow_failure: false + <<: *build_linux_i386 +.Linux_ARMhf: + allow_failure: false + <<: *build_linux_armhf +.Linux_ARM64: + allow_failure: false + <<: *build_linux_arm64 +.Darwin: + <<: *build_macos +.w32: + <<: *build_w32 +.w64: + <<: *build_w64 + +####################################################################### +### create deken packages and (optionally) upload them; +### if you want to automatically upload a package, you need to +### set DEKEN_USERNAME/DEKEN_PASSWORD in the CI-project settings. +### (https://git.iem.at/help/ci/variables/README#variables) +.package: + stage: deploy + image: debian:buster + variables: + DEKEN_ROOT: "yes" + before_script: + - apt-get update && apt-get --no-install-recommends -y install deken zip + script: +# create zip files for all platform (e.g. aoo_v0.3.0_w32.zip) + - rm -f ./*.zip + - root=$(pwd) +# Pd + SuperCollider: + - for dir in ./build/*; do + for lib in pd sc; do + name=aoo_${lib}_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir/$lib" && zip -r "${root}/${name}" ./aoo) + ; done + ; done +# C/C++ library: + - for dir in ./build/*; do + name=aoo_${CI_COMMIT_REF_NAME}_$(basename "$dir").zip; + echo create $name; + (cd "$dir" && zip -r "${root}/${name}" ./aoo) + ; done +# Deken: + - rm -f ./*.dek + - rm -rf ./pd/aoo + - mkdir -p ./pd/aoo +# create a single deken package containing binaries for all platforms + - for dir in ./build/*/pd/aoo; do cp -r $dir/* ./pd/aoo; done + - deken package --version="${CI_COMMIT_TAG#v}" ./pd/aoo +# upload deken package (optional) + - test -z "${CI_COMMIT_TAG}" || test -z "${DEKEN_USERNAME}" || test -z "${DEKEN_PASSWORD}" || deken upload --no-source-error ./*.dek + artifacts: + name: ${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_package + paths: + - ./*.dek + - ./*.zip + + +####################################################################### +### the actual jobs: (linux,macos,windows)*(release,snapshot) + +### release jobs +Linux: + extends: + - .Linux + - .release +Linux_i386: + extends: + - .Linux_i386 + - .release +Linux_ARMhf: + extends: + - .Linux_ARMhf + - .release +Linux_ARM64: + extends: + - .Linux_ARM64 + - .release +Darwin: + extends: + - .Darwin + - .release +w32: + extends: + - .w32 + - .release +w64: + extends: + - .w64 + - .release +package: + extends: + - .package + - .release + +### snapshot jobs +Linux_snapshot: + extends: + - .Linux + - .snapshot +Linux_i386_snapshot: + extends: + - .Linux_i386 + - .snapshot +Linux_ARMhf_snapshot: + extends: + - .Linux_ARMhf + - .snapshot +Linux_ARM64_snapshot: + extends: + - .Linux_ARM64 + - .snapshot +Darwin_snapshot: + extends: + - .Darwin + - .snapshot +w32_snapshot: + extends: + - .w32 + - .snapshot +w64_snapshot: + extends: + - .w64 + - .snapshot +package_snapshot: + extends: + - .package + - .snapshot -include: - - https://git.iem.at/pd/iem-ci/raw/master/pd-lib-builder/gitlab-iem.yml diff --git a/deps/aoo/.git-ci/requirements.apt b/deps/aoo/.git-ci/requirements.apt deleted file mode 100644 index 4801f4bf..00000000 --- a/deps/aoo/.git-ci/requirements.apt +++ /dev/null @@ -1 +0,0 @@ -libopus-dev diff --git a/deps/aoo/.git-ci/requirements.brew b/deps/aoo/.git-ci/requirements.brew deleted file mode 100644 index 3f85f81e..00000000 --- a/deps/aoo/.git-ci/requirements.brew +++ /dev/null @@ -1 +0,0 @@ -brew "opus" diff --git a/deps/aoo/.git-ci/requirements.msys2 b/deps/aoo/.git-ci/requirements.msys2 deleted file mode 100644 index 00572003..00000000 --- a/deps/aoo/.git-ci/requirements.msys2 +++ /dev/null @@ -1 +0,0 @@ -@MINGW@opus diff --git a/deps/aoo/.gitattributes b/deps/aoo/.gitattributes index 0793be4f..bb75d008 100644 --- a/deps/aoo/.gitattributes +++ b/deps/aoo/.gitattributes @@ -14,4 +14,4 @@ # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary -*.ttf binary +*.ttf binary \ No newline at end of file diff --git a/deps/aoo/.gitignore b/deps/aoo/.gitignore index 9dc7134b..7122bbe9 100644 --- a/deps/aoo/.gitignore +++ b/deps/aoo/.gitignore @@ -11,3 +11,5 @@ lib/src/lib/oscpack/examples** lib/src/lib/oscpack/tests** lib/src/lib/oscpack/ip** +build** +install** diff --git a/deps/aoo/.gitmodules b/deps/aoo/.gitmodules new file mode 100644 index 00000000..36d16ff8 --- /dev/null +++ b/deps/aoo/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/opus"] + path = deps/opus + url = https://github.com/xiph/opus.git diff --git a/deps/aoo/.gitrepo b/deps/aoo/.gitrepo index c4e8632d..a44cb55b 100644 --- a/deps/aoo/.gitrepo +++ b/deps/aoo/.gitrepo @@ -5,8 +5,8 @@ ; [subrepo] remote = https://github.com/essej/aoo.git - branch = sono - commit = e67c763c268b636d5b9ad7b5a5fc24b004890700 - parent = 04a75fd9c344e54952c646a32fd68ef319d91dfe + branch = sononew + commit = e9055610a554d56acf0bb208ba0ac40d4cc040a9 + parent = acd1caf26ab35d8a6a65ba6418a614f0daaddf38 method = merge cmdver = 0.4.1 diff --git a/deps/aoo/CMakeLists.txt b/deps/aoo/CMakeLists.txt new file mode 100644 index 00000000..0799a961 --- /dev/null +++ b/deps/aoo/CMakeLists.txt @@ -0,0 +1,182 @@ +cmake_minimum_required (VERSION 2.8) + +include(GNUInstallDirs) + +set(PROJECT "aoo") +message(STATUS "Project: ${PROJECT}") +project(${PROJECT}) + +include (CheckCCompilerFlag) +include (CheckCXXCompilerFlag) + +if(UNIX AND NOT APPLE AND NOT MINGW) + set(LINUX TRUE) +endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_COMPILER_IS_CLANG 1) +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) +endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") + +if(LINUX AND CMAKE_COMPILER_IS_GNUCXX) + option(STATIC_LIBS "link with static libraries (libstdc++ and libgcc)" ON) +endif() +if(MINGW) + option(STATIC_LIBS "link with static libraries (libstdc++, libgcc and phread)" ON) + set(CMAKE_EXECUTABLE_SUFFIX ".exe") +endif() + +# logging +set(LOGLEVEL "WARNING" CACHE STRING "LOGLEVEL") +message(STATUS "LOGLEVEL: ${LOGLEVEL}") +add_definitions("-DAOO_LOGLEVEL=AOO_LOGLEVEL_${LOGLEVEL}") + +# Windows paths +if (WIN32 OR MINGW) + # check if "Program Files (x86)" exists (64-bit Windows) and if we compile for 32-bit + set(_pf_x86 "ProgramFiles(x86)") + if (DEFINED ENV{${_pf_x86}} AND (CMAKE_SIZEOF_VOID_P EQUAL 4)) + set(PROGRAMFILES $ENV{${_pf_x86}}) + else() + set(PROGRAMFILES $ENV{PROGRAMFILES}) + endif() + set(APPDATA $ENV{APPDATA}) + set(LOCALAPPDATA $ENV{LOCALAPPDATA}) +endif() + +# compiler flags +if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG) + add_definitions(-fvisibility=hidden) + + CHECK_CXX_COMPILER_FLAG(-msse HAS_CXX_SSE) + if (HAS_CXX_SSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse") + endif() + + CHECK_CXX_COMPILER_FLAG(-msse2 HAS_CXX_SSE2) + if (HAS_CXX_SSE2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse2") + endif() + + CHECK_CXX_COMPILER_FLAG(-msse3 HAS_CXX_SSE3) + if (HAS_CXX_SSE3) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse3") + endif() + + if (FALSE) + CHECK_CXX_COMPILER_FLAG(-msse4 HAS_CXX_SSE4) + if (HAS_CXX_SSE4) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4") + endif() + endif() + + CHECK_CXX_COMPILER_FLAG(-mfpmath=sse HAS_CXX_FPMATH_SSE) + if (HAS_CXX_FPMATH_SSE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpmath=sse") + endif() + + if(NATIVE) + add_definitions(-march=native) + endif() + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -ffast-math -funroll-loops -fomit-frame-pointer") + + if(CMAKE_COMPILER_IS_CLANG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() +endif() +if (MINGW) + set(CMAKE_CXX_COMPILER g++) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mstackrealign") +endif() +if (LINUX) + add_definitions("-fPIC") +endif() + +# dependencies +include_directories(deps) + +# oscpack +option(SYSTEM_OSCPACK "use system provided oscpack library" OFF) +if (SYSTEM_OSCPACK) + set(OSCPACK_LIB "oscpack" CACHE STRING "oscpack linker flags") +else() + set(OSCPACK_LIB "oscpack") + add_subdirectory("deps/oscpack") +endif() + +# md5 +option(SYSTEM_MD5 "use system provided md5 library" OFF) +if (SYSTEM_MD5) + set(MD5_LIB "md5" CACHE STRING "md5 linker flags") +else() + set(MD5_LIB "md5") + add_subdirectory("deps/md5") +endif() + +# Opus +option(CODEC_OPUS "use Opus codec" ON) +if (CODEC_OPUS) + add_definitions("-DUSE_CODEC_OPUS=1") + + option(SYSTEM_OPUS "use system provided Opus library" OFF) + if (SYSTEM_OPUS) + set(CODEC_OPUS_LIB "opus" CACHE STRING "Opus linker flags") + else() + include_directories("deps/opus/include") + # use different variable for finding the library, + # because we don't want to change the cache variable + find_library(OPUS_LIB_STATIC "opus" HINTS "deps/opus/lib" REQUIRED) + set(CODEC_OPUS_LIB ${OPUS_LIB_STATIC}) + if (MINGW) + list(APPEND LIBS "ssp") # for fortified functions + endif() + endif() + message(STATUS "Opus library: ${CODEC_OPUS_LIB}") +endif() + +# platform specific linker flags +if (LINUX) + list(APPEND LIBS "-pthread") + if(STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc") + endif() + set(CMAKE_BUILD_RPATH_USE_ORIGIN ON) +endif() +if (MINGW) + if (STATIC_LIBS) + list(APPEND LIBS "-static-libstdc++" "-static-libgcc" "-static -lpthread") + else() + list(APPEND LIBS "-lpthread") + endif() +endif() +if (APPLE) + list(APPEND LIBS "-lpthread") +endif() + +# headers +include_directories(.) +include_directories(include) + +# "aoo" library +set(AOO_SHARED "aoo") +set(AOO_STATIC "aoo_static") +add_subdirectory(aoo) + +# Pd external +option(BUILD_PD_EXTERNAL "build Pd external" ON) +if (BUILD_PD_EXTERNAL) + add_subdirectory(pd) +endif() + +# SC extension +option(BUILD_SC_EXTENSION "build SC extension" ON) +if (BUILD_SC_EXTENSION) + add_subdirectory(sc) +endif() diff --git a/deps/aoo/aoo/CMakeLists.txt b/deps/aoo/aoo/CMakeLists.txt new file mode 100644 index 00000000..e517c2fb --- /dev/null +++ b/deps/aoo/aoo/CMakeLists.txt @@ -0,0 +1,111 @@ +cmake_minimum_required (VERSION 2.8) + +# networking support +option(USE_AOO_NET "build with networking support" ON) + +# compile time options +option(CUSTOM_ALLOCATOR "build with custom allocator support" OFF) + +option(DYNAMIC_RESAMPLING "enable/disable dynamic resampling" ON) +message(STATUS "use dynamic resampling: ${DYNAMIC_RESAMPLING}") + +set(DLL_BANDWIDTH 0.0001 CACHE STRING "default DLL filter bandwidth") +message(STATUS "default DLL filter bandwidth: ${DLL_BANDWIDTH}") + +option(TIMER_CHECK "use timer check" ON) +message(STATUS "timer check: ${TIMER_CHECK}") + +set(TIMER_TOLERANCE 0.25 CACHE STRING "default timer check tolerance") +message(STATUS "default timer check tolerance: ${TIMER_TOLERANCE}") + +option(BINARY_DATA_MSG "use timer check" ON) +message(STATUS "send binary data message: ${BINARY_DATA_MSG}") + +# compile time debugging options +option(DEBUG_MEMORY "debug memory usage" OFF) + +option(DEBUG_DATA "debug memory usage" OFF) + +option(DEBUG_DLL "debug time DLL filter" OFF) + +option(DEBUG_TIMER "debug timer" OFF) + +option(DEBUG_RESAMPLER "debug resampler" OFF) + +option(DEBUG_AUDIO_BUFFER "debug audio buffer" OFF) + +option(DEBUG_JITTER_BUFFER "debug jitter buffer" OFF) + +set(OPTIONS + "-DAOO_CUSTOM_ALLOCATOR=$" + "-DAOO_DYNAMIC_RESAMPLING=$" + "-DAOO_DLL_BANDWIDTH=${DLL_BANDWIDTH}" + "-DAOO_TIMER_CHECK=$" + "-DAOO_TIMER_TOLERANCE=${TIMER_TOLERANCE}" + "-DAOO_BINARY_DATA_MSG=$" + "-DAOO_DEBUG_MEMORY=$" + "-DAOO_DEBUG_DATA=$" + "-DAOO_DEBUG_DLL=$" + "-DAOO_DEBUG_TIMER=$" + "-DAOO_DEBUG_RESAMPLER=$" + "-DAOO_DEBUG_AUDIO_BUFFER=$" + "-DAOO_DEBUG_JITTER_BUFFER=$" +) + +set(INCLUDE "../include/aoo") +set(COMMON "../common") + +file(GLOB AOO_HEADERS "${INCLUDE}/*.h" "${INCLUDE}/*.hpp" + "${INCLUDE}/codec/aoo_pcm.h" "src/*.hpp" "${COMMON}/*.hpp") +file(GLOB AOO_SRC "src/*.cpp" "src/codec/pcm.cpp" "${COMMON}/*.cpp") + +if (CODEC_OPUS) + list(APPEND AOO_HEADERS "${INCLUDE}/codec/aoo_opus.h") + list(APPEND AOO_SRC "src/codec/opus.cpp") +endif() + +if (USE_AOO_NET) + list(APPEND AOO_HEADERS "src/net/server.hpp" "src/net/client.hpp" + "src/net/SLIP.hpp") + list(APPEND AOO_SRC "src/net/client.cpp" "src/net/server.cpp") +endif() + +# static library +add_library(${AOO_STATIC} STATIC ${AOO_HEADERS} ${AOO_SRC}) + +target_compile_definitions(${AOO_STATIC} PUBLIC ${OPTIONS} "-DAOO_STATIC") + +# shared library +option(BUILD_AOO_SHARED "build 'aoo' shared library" ON) +option(BUILD_AOO_STATIC "build 'aoo' static library" ON) + +if (BUILD_AOO_SHARED) + add_library(${AOO_SHARED} SHARED ${AOO_HEADERS} ${AOO_SRC}) + + target_compile_definitions(${AOO_SHARED} PUBLIC "-DAOO_BUILD" ${OPTIONS}) + if (WIN32 OR MINGW) + target_compile_definitions(${AOO_SHARED} PUBLIC "-DDLL_EXPORT") + target_link_libraries(${AOO_SHARED} "ws2_32") + endif() + if (USE_AOO_NET) + target_link_libraries(${AOO_SHARED} ${MD5_LIB}) + endif() + target_link_libraries(${AOO_SHARED} ${OSCPACK_LIB} ${CODEC_OPUS_LIB} ${LIBS}) +endif() + +if (BUILD_AOO_SHARED OR BUILD_AOO_STATIC) + # install library files + if (BUILD_AOO_SHARED) + list(APPEND TARGET_LIST ${AOO_SHARED}) + endif() + if (BUILD_AOO_STATIC) + list(APPEND TARGET_LIST ${AOO_STATIC}) + endif() + install(TARGETS ${TARGET_LIST} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + # install public headers (note the trailing slash!) + install(DIRECTORY "${INCLUDE}/" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${AOO_SHARED}) +endif() diff --git a/deps/aoo/aoo/src/aoo.cpp b/deps/aoo/aoo/src/aoo.cpp new file mode 100644 index 00000000..02149016 --- /dev/null +++ b/deps/aoo/aoo/src/aoo.cpp @@ -0,0 +1,440 @@ +#include "aoo/aoo.h" +#if USE_AOO_NET +#include "aoo/aoo_net.h" +#include "common/net_utils.hpp" +#endif + +#include "imp.hpp" +#include "codec.hpp" + +#include "common/sync.hpp" +#include "common/time.hpp" +#include "common/utils.hpp" + +#include "aoo/codec/aoo_pcm.h" +#if USE_CODEC_OPUS +#include "aoo/codec/aoo_opus.h" +#endif + +#include +#include +#include +#include + +namespace aoo { + +/*//////////////////// helper /////////////////////////*/ + +char * copy_string(const char * s){ + if (s){ + auto len = strlen(s); + auto result = aoo::allocate(len + 1); + memcpy(result, s, len + 1); + return (char *)result; + } else { + return nullptr; + } +} + +void free_string(char *s){ + if (s){ + auto len = strlen(s); + aoo::deallocate(s, len + 1); + } +} + +void * copy_sockaddr(const void *sa, int32_t len){ + if (sa){ + auto result = aoo::allocate(len); + memcpy(result, sa, len); + return result; + } else { + return nullptr; + } +} + +void free_sockaddr(void *sa, int32_t len){ + if (sa){ + aoo::deallocate(sa, len); + } +} + +} // aoo + +/*//////////////////// allocator /////////////////////*/ + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY + +namespace aoo { + +#if AOO_DEBUG_MEMORY +std::atomic total_memory{0}; +#endif + +static aoo_allocator g_allocator { + [](size_t n, void *){ + #if AOO_DEBUG_MEMORY + auto total = total_memory.fetch_add(n, std::memory_order_relaxed) + n; + fprintf(stderr, "allocate %d bytes (total: %d)\n", n, total); + fflush(stderr); + #endif + return operator new(n); + }, + nullptr, + [](void *ptr, size_t n, void *){ + #if AOO_DEBUG_MEMORY + auto total = total_memory.fetch_sub(n, std::memory_order_relaxed) - n; + fprintf(stderr, "deallocate %d bytes (total: %d)\n", n, total); + fflush(stderr); + #endif + operator delete(ptr); + }, + nullptr +}; + +void * allocate(size_t size){ + return g_allocator.alloc(size, g_allocator.context); +} + +void deallocate(void *ptr, size_t size){ + g_allocator.free(ptr, size, g_allocator.context); +} + +} // aoo + +#endif + +#if AOO_CUSTOM_ALLOCATOR +void aoo_set_allocator(const aoo_allocator *alloc){ + aoo::g_allocator = *alloc; +} +#endif + +/*//////////////////// Log ////////////////////////////*/ + +#define LOG_MUTEX 1 + +#if LOG_MUTEX +static aoo::sync::mutex g_log_mutex; +#endif + +static void cerr_logfunction(const char *msg, int32_t level, void *ctx){ +#if LOG_MUTEX + aoo::sync::scoped_lock lock(g_log_mutex); +#endif + std::cerr << msg; + std::flush(std::cerr); +} + +static aoo_logfunction g_logfunction = cerr_logfunction; +static void *g_logcontext = nullptr; + +void aoo_set_logfunction(aoo_logfunction f, void *context){ + g_logfunction = f; + g_logcontext = context; +} + +static const char *errmsg[] = { + // TODO + "undefined" +}; + +const char *aoo_error_string(aoo_error e){ + if (e == AOO_OK){ + return "no error"; + } else { + return "unspecified error"; // TODO + } +} + +namespace aoo { + +Log::~Log(){ + stream_ << "\n"; + std::string msg = stream_.str(); + g_logfunction(msg.c_str(), level_, g_logcontext); +} + +} + +/*//////////////////// OSC ////////////////////////////*/ + +aoo_error aoo_parse_pattern(const char *msg, int32_t n, + aoo_type *type, aoo_id *id, int32_t *offset) +{ + int32_t count = 0; + if (n >= AOO_BIN_MSG_HEADER_SIZE && + !memcmp(msg, AOO_BIN_MSG_DOMAIN, AOO_BIN_MSG_DOMAIN_SIZE)) + { + // domain (int32), type (int16), cmd (int16), id (int32) ... + *type = aoo::from_bytes(msg + 4); + // cmd = aoo::from_bytes(msg + 6); + *id = aoo::from_bytes(msg + 8); + *offset = 12; + + return AOO_OK; + } else if (n >= AOO_MSG_DOMAIN_LEN + && !memcmp(msg, AOO_MSG_DOMAIN, AOO_MSG_DOMAIN_LEN)) + { + count += AOO_MSG_DOMAIN_LEN; + if (n >= (count + AOO_MSG_SOURCE_LEN) + && !memcmp(msg + count, AOO_MSG_SOURCE, AOO_MSG_SOURCE_LEN)) + { + *type = AOO_TYPE_SOURCE; + count += AOO_MSG_SOURCE_LEN; + } else if (n >= (count + AOO_MSG_SINK_LEN) + && !memcmp(msg + count, AOO_MSG_SINK, AOO_MSG_SINK_LEN)) + { + *type = AOO_TYPE_SINK; + count += AOO_MSG_SINK_LEN; + } else { + #if USE_AOO_NET + if (n >= (count + AOO_NET_MSG_CLIENT_LEN) + && !memcmp(msg + count, AOO_NET_MSG_CLIENT, AOO_NET_MSG_CLIENT_LEN)) + { + *type = AOO_TYPE_CLIENT; + count += AOO_NET_MSG_CLIENT_LEN; + } else if (n >= (count + AOO_NET_MSG_SERVER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_SERVER, AOO_NET_MSG_SERVER_LEN)) + { + *type = AOO_TYPE_SERVER; + count += AOO_NET_MSG_SERVER_LEN; + } else if (n >= (count + AOO_NET_MSG_PEER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_PEER, AOO_NET_MSG_PEER_LEN)) + { + *type = AOO_TYPE_PEER; + count += AOO_NET_MSG_PEER_LEN; + } else if (n >= (count + AOO_NET_MSG_RELAY_LEN) + && !memcmp(msg + count, AOO_NET_MSG_RELAY, AOO_NET_MSG_RELAY_LEN)) + { + *type = AOO_TYPE_RELAY; + count += AOO_NET_MSG_RELAY_LEN; + } else { + return AOO_ERROR_UNSPECIFIED; + } + + if (offset){ + *offset = count; + } + #endif // USE_AOO_NET + + return AOO_OK; + } + + // /aoo/source or /aoo/sink + if (id){ + int32_t skip = 0; + if (sscanf(msg + count, "/%d%n", id, &skip) > 0){ + count += skip; + } else { + // TODO only print relevant part of OSC address string + LOG_ERROR("aoo_parse_pattern: bad ID " << (msg + count)); + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } + + if (offset){ + *offset = count; + } + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; // not an AoO message + } +} + +// OSC time stamp (NTP time) +uint64_t aoo_osctime_now(void){ + return aoo::time_tag::now(); +} + +double aoo_osctime_to_seconds(uint64_t t){ + return aoo::time_tag(t).to_seconds(); +} + +uint64_t aoo_osctime_from_seconds(double s){ + return aoo::time_tag::from_seconds(s); +} + +double aoo_osctime_duration(uint64_t t1, uint64_t t2){ + return aoo::time_tag::duration(t1, t2); +} + +/*/////////////// version ////////////////////*/ + +void aoo_version(int32_t *major, int32_t *minor, + int32_t *patch, int32_t *pre){ + if (major) *major = AOO_VERSION_MAJOR; + if (minor) *minor = AOO_VERSION_MINOR; + if (patch) *patch = AOO_VERSION_PATCH; + if (pre) *pre = AOO_VERSION_PRERELEASE; +} + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +const char *aoo_version_string(){ + return STR(AOO_VERSION_MAJOR) "." STR(AOO_VERSION_MINOR) + #if AOO_VERSION_PATCH > 0 + "." STR(AOO_VERSION_PATCH) + #endif + #if AOO_VERSION_PRERELEASE > 0 + "-pre" STR(AOO_VERSION_PRERELEASE) + #endif + ; +} + +namespace aoo { + +bool check_version(uint32_t version){ + auto major = (version >> 24) & 255; + auto minor = (version >> 16) & 255; + auto bugfix = (version >> 8) & 255; + + if (major != AOO_VERSION_MAJOR){ + return false; + } + + return true; +} + +uint32_t make_version(){ + // make version: major, minor, bugfix, [protocol] + return ((uint32_t)AOO_VERSION_MAJOR << 24) | ((uint32_t)AOO_VERSION_MINOR << 16) + | ((uint32_t)AOO_VERSION_PATCH << 8); +} + +/*//////////////////// memory /////////////////*/ + +memory_block * memory_block::allocate(size_t size){ + auto fullsize = sizeof(memory_block::header) + size; + auto mem = (memory_block *)aoo::allocate(fullsize); + mem->header.next = nullptr; + mem->header.size = size; +#if DEBUG_MEMORY + fprintf(stderr, "allocate memory block (%d bytes)\n", size); + fflush(stderr); +#endif + return mem; +} + +void memory_block::free(memory_block *mem){ +#if DEBUG_MEMORY + fprintf(stderr, "deallocate memory block (%d bytes)\n", mem->size()); + fflush(stderr); +#endif + aoo::deallocate(mem, mem->full_size()); +} + +memory_list::~memory_list(){ + // free memory blocks + auto mem = memlist_.load(std::memory_order_relaxed); + while (mem){ + auto next = mem->header.next; + memory_block::free(mem); + mem = next; + } +} + +memory_block* memory_list::alloc(size_t size) { + for (;;){ + // try to pop existing block + auto head = memlist_.load(std::memory_order_relaxed); + if (head){ + auto next = head->header.next; + if (memlist_.compare_exchange_weak(head, next, std::memory_order_acq_rel)){ + if (head->header.size >= size){ + #if DEBUG_MEMORY + fprintf(stderr, "reuse memory block (%d bytes)\n", head->header.size); + fflush(stderr); + #endif + return head; + } else { + // free block + memory_block::free(head); + } + } else { + // try again + continue; + } + } + // allocate new block + return memory_block::allocate(size); + } +} +void memory_list::free(memory_block* b) { + b->header.next = memlist_.load(std::memory_order_relaxed); + // check if the head has changed and update it atomically. + // (if the CAS fails, 'next' is updated to the current head) + while (!memlist_.compare_exchange_weak(b->header.next, b, std::memory_order_acq_rel)) ; +#if DEBUG_MEMORY + fprintf(stderr, "return memory block (%d bytes)\n", b->header.size); + fflush(stderr); +#endif +} + +} // aoo + +/*//////////////////// codec //////////////////*/ + + +namespace aoo { + +static std::unordered_map> g_codec_dict; + +const aoo::codec * find_codec(const char * name){ + auto it = g_codec_dict.find(name); + if (it != g_codec_dict.end()){ + return it->second.get(); + } else { + return nullptr; + } +} + +} // aoo + +aoo_error aoo_register_codec(const char *name, const aoo_codec *codec){ + if (aoo::g_codec_dict.count(name) != 0){ + LOG_WARNING("aoo: codec " << name << " already registered!"); + return AOO_ERROR_UNSPECIFIED; + } + aoo::g_codec_dict[name] = std::make_unique(codec); + LOG_VERBOSE("aoo: registered codec '" << name << "'"); + return AOO_OK; +} + +/*/////////////// (de)initialize //////////////////*/ + +void aoo_codec_pcm_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc); +#if USE_CODEC_OPUS +void aoo_codec_opus_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc); +#endif + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY +#define ALLOCATOR &aoo::g_allocator +#else +#define ALLOCATOR nullptr +#endif + +void aoo_initialize(){ + static bool initialized = false; + if (!initialized){ + #if USE_AOO_NET + aoo::socket_init(); + #endif + + // register codecs + aoo_codec_pcm_setup(aoo_register_codec, ALLOCATOR); + + #if USE_CODEC_OPUS + aoo_codec_opus_setup(aoo_register_codec, ALLOCATOR); + #endif + + initialized = true; + } +} + +void aoo_terminate() {} + + diff --git a/deps/aoo/aoo/src/buffer.cpp b/deps/aoo/aoo/src/buffer.cpp new file mode 100644 index 00000000..e30da8d0 --- /dev/null +++ b/deps/aoo/aoo/src/buffer.cpp @@ -0,0 +1,388 @@ +#include "buffer.hpp" + +#include "common/utils.hpp" + +#include +#include + +namespace aoo { + +/*////////////////////////// sent_block /////////////////////////////*/ + +void sent_block::set(int32_t seq, double sr, + const char *data, int32_t nbytes, + int32_t nframes, int32_t framesize) +{ + sequence = seq; + samplerate = sr; + numframes_ = nframes; + framesize_ = framesize; + buffer_.assign(data, data + nbytes); +} + +int32_t sent_block::get_frame(int32_t which, char *data, int32_t n){ + assert(framesize_ > 0 && numframes_ > 0); + if (which >= 0 && which < numframes_){ + auto onset = which * framesize_; + auto minsize = (which == numframes_ - 1) ? size() - onset : framesize_; + if (n >= minsize){ + int32_t nbytes; + if (which == numframes_ - 1){ // last frame + nbytes = size() - onset; + } else { + nbytes = framesize_; + } + auto ptr = buffer_.data() + onset; + std::copy(ptr, ptr + n, data); + return nbytes; + } else { + LOG_ERROR("buffer too small! got " << n << ", need " << minsize); + } + } else { + LOG_ERROR("frame number " << which << " out of range!"); + } + return 0; +} + +int32_t sent_block::frame_size(int32_t which) const { + assert(which < numframes_); + if (which == numframes_ - 1){ // last frame + return size() - which * framesize_; + } else { + return framesize_; + } +} + +/*////////////////////////// history_buffer ///////////////////////////*/ + +void history_buffer::clear(){ + head_ = 0; + size_ = 0; +} + +void history_buffer::resize(int32_t n){ + buffer_.resize(n); + clear(); +} + +sent_block * history_buffer::find(int32_t seq){ + // the code below only works if the buffer is not empty! + if (size_ > 0){ + // check if sequence number is outdated + // (tail always starts at buffer begin and becomes + // equal to head once the buffer is full) + auto head = buffer_.begin() + head_; + auto tail = (size_ == capacity()) ? head : buffer_.begin(); + if (seq < tail->sequence){ + LOG_DEBUG("history buffer: block " << seq << " too old"); + return nullptr; + } + + #if 0 + // linear search + auto end = buffer_.begin() + size_; + for (auto it = buffer_.begin(); it != end; ++it){ + if (it->sequence == seq){ + return &(*it); + } + } + #else + // binary search + auto dofind = [&](auto begin, auto end) -> sent_block * { + auto result = std::lower_bound(begin, end, seq, [](auto& a, auto& b){ + return a.sequence < b; + }); + if (result != end && result->sequence == seq){ + return &(*result); + } else { + return nullptr; + } + }; + if (head != tail){ + // buffer not full, just search range [tail, head] + auto result = dofind(tail, head); + if (result){ + return result; + } + } else { + // blocks are always pushed in chronological order, + // so the ranges [begin, head] and [head, end] will always be sorted. + auto result = dofind(buffer_.begin(), head); + if (!result){ + result = dofind(head, buffer_.end()); + } + if (result){ + return result; + } + } + #endif + } + + LOG_ERROR("history buffer: couldn't find block " << seq); + return nullptr; +} + +sent_block * history_buffer::push() +{ + assert(!buffer_.empty()); + auto old = head_++; + if (head_ >= capacity()){ + head_ = 0; + } + if (size_ < capacity()){ + ++size_; + } + return &buffer_[old]; +} + +/*////////////////////// received_block //////////////////////*/ + +void received_block::reserve(int32_t size){ + buffer_.reserve(size); +} + +void received_block::init(int32_t seq, double sr, int32_t chn, + int32_t nbytes, int32_t nframes) +{ + assert(nbytes > 0); + assert(nframes <= (int32_t)frames_.size()); + // keep timestamp and numtries if we're actually reiniting + if (seq != sequence){ + timestamp_ = 0; + numtries_ = 0; + } + sequence = seq; + samplerate = sr; + channel = chn; + buffer_.resize(nbytes); + numframes_ = nframes; + framesize_ = 0; + dropped_ = false; + frames_.reset(); + for (int i = 0; i < nframes; ++i){ + frames_[i] = true; + } +} + +void received_block::init(int32_t seq, bool dropped) +{ + sequence = seq; + samplerate = 0; + channel = 0; + buffer_.clear(); + numframes_ = 0; + framesize_ = 0; + timestamp_ = 0; + numtries_ = 0; + dropped_ = dropped; + if (dropped){ + frames_.reset(); // complete + } else { + frames_.set(); // has_frame() always returns false + } +} + +bool received_block::dropped() const { + return dropped_; +} + +bool received_block::complete() const { + return frames_.none(); +} + +int32_t received_block::count_frames() const { + return std::max(0, numframes_ - frames_.count()); +} + +int32_t received_block::resend_count() const { + return numtries_; +} + +void received_block::add_frame(int32_t which, const char *data, int32_t n){ + assert(!buffer_.empty()); + assert(which < numframes_); + if (which == numframes_ - 1){ + #if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG("jitter buffer: copy last frame with " << n << " bytes"); + #endif + std::copy(data, data + n, buffer_.end() - n); + } else { + #if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG("jitter buffer: copy frame " << which << " with " << n << " bytes"); + #endif + std::copy(data, data + n, buffer_.data() + (which * n)); + framesize_ = n; // LATER allow varying framesizes + } + frames_[which] = false; +} + +bool received_block::has_frame(int32_t which) const { + return !frames_[which]; +} + +bool received_block::update(double time, double interval){ + if (timestamp_ > 0 && (time - timestamp_) < interval){ + return false; + } + timestamp_ = time; + numtries_++; +#if AOO_DEBUG_JITTER_BUFFER + DO_LOG_DEBUG( + "jitter buffer: request block " << sequence); +#endif + return true; +} + +/*////////////////////////// jitter_buffer /////////////////////////////*/ + +void jitter_buffer::clear(){ + head_ = tail_ = size_ = 0; + last_popped_ = last_pushed_ = -1; +} + +void jitter_buffer::resize(int32_t n, int32_t maxblocksize){ + data_.resize(n); + for (auto& b : data_){ + b.reserve(maxblocksize); + } + clear(); +} + +received_block* jitter_buffer::find(int32_t seq){ + // first try the end, as we most likely have to complete the most recent block + if (empty()){ + return nullptr; + } else if (back().sequence == seq){ + return &back(); + } +#if 0 + // linear search + if (head_ > tail_){ + for (int32_t i = tail_; i < head_; ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + } else { + for (int32_t i = 0; i < head_; ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + for (int32_t i = tail_; i < capacity(); ++i){ + if (data_[i].sequence == seq){ + return &data_[i]; + } + } + } + return nullptr; +#else + // binary search + // (blocks are always pushed in chronological order) + auto dofind = [&](auto begin, auto end) -> received_block * { + auto result = std::lower_bound(begin, end, seq, [](auto& a, auto& b){ + return a.sequence < b; + }); + if (result != end && result->sequence == seq){ + return &(*result); + } else { + return nullptr; + } + }; + + auto begin = data_.data(); + if (head_ > tail_){ + // [tail, head] + return dofind(begin + tail_, begin + head_); + } else { + // [begin, head] + [tail, end] + auto result = dofind(begin, begin + head_); + if (!result){ + result = dofind(begin + tail_, begin + data_.capacity()); + } + return result; + } +#endif +} + +received_block* jitter_buffer::push_back(int32_t seq){ + assert(!full()); + auto old = head_; + if (++head_ == capacity()){ + head_ = 0; + } + size_++; + last_pushed_ = seq; + return &data_[old]; +} + +void jitter_buffer::pop_front(){ + assert(!empty()); + last_popped_ = data_[tail_].sequence; + if (++tail_ == capacity()){ + tail_ = 0; + } + size_--; +} + +received_block& jitter_buffer::front(){ + assert(!empty()); + return data_[tail_]; +} + +const received_block& jitter_buffer::front() const { + assert(!empty()); + return data_[tail_]; +} + +received_block& jitter_buffer::back(){ + assert(!empty()); + auto index = head_ - 1; + if (index < 0){ + index = capacity() - 1; + } + return data_[index]; +} + +const received_block& jitter_buffer::back() const { + assert(!empty()); + auto index = head_ - 1; + if (index < 0){ + index = capacity() - 1; + } + return data_[index]; +} + +jitter_buffer::iterator jitter_buffer::begin(){ + if (empty()){ + return end(); + } else { + return iterator(this, &data_[tail_]); + } +} + +jitter_buffer::const_iterator jitter_buffer::begin() const { + if (empty()){ + return end(); + } else { + return const_iterator(this, &data_[tail_]); + } +} + +jitter_buffer::iterator jitter_buffer::end(){ + return iterator(this); +} + +jitter_buffer::const_iterator jitter_buffer::end() const { + return const_iterator(this); +} + +std::ostream& operator<<(std::ostream& os, const jitter_buffer& jb){ + os << "jitterbuffer (" << jb.size() << " / " << jb.capacity() << "): "; + for (auto& b : jb){ + os << b.sequence << " " << "(" << b.count_frames() << "/" << b.num_frames() << ") "; + } + return os; +} + +} // aoo diff --git a/deps/aoo/aoo/src/buffer.hpp b/deps/aoo/aoo/src/buffer.hpp new file mode 100644 index 00000000..ae75f1e2 --- /dev/null +++ b/deps/aoo/aoo/src/buffer.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include "aoo/aoo.h" + +#include "imp.hpp" + +#include +#include +#include + +namespace aoo { + +struct data_packet { + int32_t sequence; + int32_t channel; + int32_t totalsize; + int32_t nframes; + int32_t frame; + int32_t size; + const char *data; + double samplerate; +}; + +/*///////////////////// history_buffer /////////////////////////*/ + +class sent_block { +public: + // methods + void set(int32_t seq, double sr, + const char *data, int32_t nbytes, + int32_t nframes, int32_t framesize); + + const char* data() const { return buffer_.data(); } + int32_t size() const { return buffer_.size(); } + + int32_t num_frames() const { return numframes_; } + int32_t frame_size(int32_t which) const; + int32_t get_frame(int32_t which, char * data, int32_t n); + + // data + int32_t sequence = -1; + double samplerate = 0; +protected: + std::vector> buffer_; + int32_t numframes_ = 0; + int32_t framesize_ = 0; +}; + +class history_buffer { +public: + void clear(); + bool empty() const { + return size_ == 0; + } + int32_t size() const { + return size_; + } + int32_t capacity() const { + return buffer_.size(); + } + void resize(int32_t n); + sent_block * find(int32_t seq); + sent_block * push(); +private: + using block_buffer = std::vector>; + block_buffer buffer_; + int32_t head_ = 0; + int32_t size_ = 0; +}; + +/*///////////////// jitter_buffer ///////////////////////*/ + +class received_block { +public: + void reserve(int32_t size); + + void init(int32_t seq, bool dropped); + void init(int32_t seq, double sr, int32_t chn, + int32_t nbytes, int32_t nframes); + + const char* data() const { return buffer_.data(); } + int32_t size() const { return buffer_.size(); } + + int32_t num_frames() const { return numframes_; } + bool has_frame(int32_t which) const; + int32_t count_frames() const; + void add_frame(int32_t which, const char *data, int32_t n); + + int32_t resend_count() const; + bool dropped() const; + bool complete() const; + bool update(double time, double interval); + + // data + int32_t sequence = -1; + int32_t channel = 0; + double samplerate = 0; +protected: + std::vector> buffer_; + int32_t numframes_ = 0; + int32_t framesize_ = 0; + std::bitset<256> frames_ = 0; + double timestamp_ = 0; + int32_t numtries_ = 0; + bool dropped_ = false; +}; + +class jitter_buffer { +public: + template + class base_iterator { + T *data_; + U *owner_; + public: + base_iterator(U* owner) + : data_(nullptr), owner_(owner){} + base_iterator(U* owner, T* data) + : data_(data), owner_(owner){} + base_iterator(const base_iterator&) = default; + base_iterator& operator=(const base_iterator&) = default; + T& operator*() { return *data_; } + T* operator->() { return data_; } + base_iterator& operator++() { + auto begin = owner_->data_.data(); + auto end = begin + owner_->data_.size(); + auto next = data_ + 1; + if (next == end){ + next = begin; + } + if (next == (begin + owner_->head_)){ + next = nullptr; // sentinel + } + data_ = next; + return *this; + } + base_iterator operator++(int) { + base_iterator old = *this; + operator++(); + return old; + } + bool operator==(const base_iterator& other){ + return data_ == other.data_; + } + bool operator!=(const base_iterator& other){ + return data_ != other.data_; + } + }; + + using iterator = base_iterator; + using const_iterator = base_iterator; + + void clear(); + void resize(int32_t n, int32_t maxblocksize); + + bool empty() const { + return size_ == 0; + } + bool full() const { + return size_ == capacity(); + } + int32_t size() const { + return size_; + } + int32_t capacity() const { + return data_.size(); + } + + received_block* find(int32_t seq); + received_block* push_back(int32_t seq); + void pop_front(); + + int32_t last_pushed() const { + return last_pushed_; + } + int32_t last_popped() const { + return last_popped_; + } + + received_block& front(); + const received_block& front() const; + received_block& back(); + const received_block& back() const; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + friend std::ostream& operator<<(std::ostream& os, const jitter_buffer& b); +private: + std::vector> data_; + int32_t size_ = 0; + int32_t head_ = 0; + int32_t tail_ = 0; + int32_t last_pushed_ = -1; + int32_t last_popped_ = -1; +}; + +} // aoo diff --git a/deps/aoo/aoo/src/codec.hpp b/deps/aoo/aoo/src/codec.hpp new file mode 100644 index 00000000..48eac9ad --- /dev/null +++ b/deps/aoo/aoo/src/codec.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "aoo/aoo.h" +#include "imp.hpp" + +#include + +namespace aoo { + +class encoder; +class decoder; + +class codec { +public: + codec(const aoo_codec *c) + : codec_(c){} + + const char *name() const { + return codec_->name; + } + + std::unique_ptr create_encoder() const; + + std::unique_ptr create_decoder() const; + + aoo_error serialize(const aoo_format& f, + char *buf, int32_t &n) const { + return codec_->serialize(&f, buf, &n); + } + + aoo_error deserialize(const aoo_format& header, + const char *data, int32_t n, + aoo_format& f, int32_t size) const { + return codec_->deserialize(&header, data, n, &f, size); + } +protected: + const aoo_codec *codec_; +}; + +class base_codec : public codec { +public: + base_codec(const aoo_codec *c, void *obj) + : codec(c), obj_(obj){} + base_codec(const aoo_codec&) = delete; + + int32_t nchannels() const { return nchannels_; } + + int32_t samplerate() const { return samplerate_; } + + int32_t blocksize() const { return blocksize_; } +protected: + void *obj_; + int32_t nchannels_ = 0; + int32_t samplerate_ = 0; + int32_t blocksize_ = 0; + + void save_format(const aoo_format& f){ + nchannels_ = f.nchannels; + samplerate_ = f.samplerate; + blocksize_ = f.blocksize; + } +}; + +class encoder : public base_codec { +public: + using base_codec::base_codec; + + ~encoder(){ + codec_->encoder_free(obj_); + } + + aoo_error set_format(aoo_format& fmt){ + auto result = codec_->encoder_ctl(obj_, + AOO_CODEC_SET_FORMAT, &fmt, sizeof(aoo_format)); + if (result == AOO_OK){ + save_format(fmt); // after validation! + } + return result; + } + + aoo_error get_format(aoo_format& fmt, size_t size) const { + return codec_->encoder_ctl(obj_, AOO_CODEC_GET_FORMAT, + &fmt, size); + } + + bool compare(const aoo_format& fmt) const { + return codec_->encoder_ctl(obj_, AOO_CODEC_FORMAT_EQUAL, + (void *)&fmt, fmt.size); + } + + aoo_error reset() { + return codec_->encoder_ctl(obj_, AOO_CODEC_RESET, nullptr, 0); + } + + aoo_error encode(const aoo_sample *s, int32_t n, char *buf, int32_t &size){ + return codec_->encoder_encode(obj_, s, n, buf, &size); + } +}; + +inline std::unique_ptr codec::create_encoder() const { + return std::make_unique(codec_, codec_->encoder_new()); +} + +class decoder : public base_codec { +public: + using base_codec::base_codec; + ~decoder(){ + codec_->decoder_free(obj_); + } + + aoo_error set_format(aoo_format& fmt){ + auto result = codec_->decoder_ctl(obj_, + AOO_CODEC_SET_FORMAT, &fmt, sizeof(aoo_format)); + if (result == AOO_OK){ + save_format(fmt); // after validation! + } + return result; + } + + aoo_error get_format(aoo_format& fmt, size_t size) const { + return codec_->decoder_ctl(obj_, AOO_CODEC_GET_FORMAT, + &fmt, size); + } + + bool compare(const aoo_format& fmt) const { + return codec_->decoder_ctl(obj_, AOO_CODEC_FORMAT_EQUAL, + (void *)&fmt, fmt.size); + } + + aoo_error reset() { + return codec_->decoder_ctl(obj_, AOO_CODEC_RESET, nullptr, 0); + } + + aoo_error decode(const char *buf, int32_t size, aoo_sample *s, int32_t &n){ + return codec_->decoder_decode(obj_, buf, size, s, &n); + } +}; + +inline std::unique_ptr codec::create_decoder() const { + return std::make_unique(codec_, codec_->decoder_new()); +} + +const codec * find_codec(const char * name); + +} // aoo diff --git a/deps/aoo/aoo/src/codec/opus.cpp b/deps/aoo/aoo/src/codec/opus.cpp new file mode 100644 index 00000000..6ab9a0a7 --- /dev/null +++ b/deps/aoo/aoo/src/codec/opus.cpp @@ -0,0 +1,499 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "aoo/codec/aoo_opus.h" +#include "common/utils.hpp" + +#include +#include +#include + +namespace { + +aoo_allocator g_allocator { + [](size_t n, void *){ return operator new(n); }, + nullptr, + [](void *ptr, size_t, void *){ operator delete(ptr); }, + nullptr +}; + +void *allocate(size_t n){ + return g_allocator.alloc(n, g_allocator.context); +} + +void deallocate(void *ptr, size_t n){ + g_allocator.free(ptr, n, g_allocator.context); +} + +void print_settings(const aoo_format_opus& f){ + const char *application, *type; + + switch (f.application_type){ + case OPUS_APPLICATION_VOIP: + application = "VOIP"; + break; + case OPUS_APPLICATION_RESTRICTED_LOWDELAY: + application = "low delay"; + break; + default: + application = "audio"; + break; + } + + switch (f.signal_type){ + case OPUS_SIGNAL_MUSIC: + type = "music"; + break; + case OPUS_SIGNAL_VOICE: + type = "voice"; + break; + default: + type = "auto"; + break; + } + + LOG_VERBOSE("Opus settings: " + << "nchannels = " << f.header.nchannels + << ", blocksize = " << f.header.blocksize + << ", samplerate = " << f.header.samplerate + << ", application = " << application + << ", bitrate = " << f.bitrate + << ", complexity = " << f.complexity + << ", signal type = " << type); +} + +/*/////////////////////// codec base ////////////////////////*/ + +struct codec { + codec(){ + memset(&format, 0, sizeof(format)); + } + aoo_format_opus format; +}; + +void validate_format(aoo_format_opus& f, bool loud = true) +{ + f.header.codec = AOO_CODEC_OPUS; // static string! + f.header.size = sizeof(aoo_format_opus); // actual size! + // validate samplerate + switch (f.header.samplerate){ + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + break; + default: + if (loud){ + LOG_VERBOSE("Opus: samplerate " << f.header.samplerate + << " not supported - using 48000"); + } + f.header.samplerate = 48000; + break; + } + // validate channels (LATER support multichannel!) + if (f.header.nchannels < 1 || f.header.nchannels > 255){ + if (loud){ + LOG_WARNING("Opus: channel count " << f.header.nchannels << + " out of range - using 1 channels"); + } + f.header.nchannels = 1; + } + // validate blocksize + const int minblocksize = f.header.samplerate / 400; // 2.5 ms (e.g. 120 samples @ 48 kHz) + const int maxblocksize = minblocksize * 24; // 60 ms (e.g. 2880 samples @ 48 kHz) + int blocksize = f.header.blocksize; + if (blocksize <= minblocksize){ + f.header.blocksize = minblocksize; + } else if (blocksize >= maxblocksize){ + f.header.blocksize = maxblocksize; + } else { + // round down to nearest multiple of 2.5 ms (in power of 2 steps) + int result = minblocksize; + while (result <= blocksize){ + result *= 2; + } + f.header.blocksize = result / 2; + } + // validate application type + if (f.application_type != OPUS_APPLICATION_VOIP + && f.application_type != OPUS_APPLICATION_AUDIO + && f.application_type != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + { + if (loud){ + LOG_WARNING("Opus: bad application type, using OPUS_APPLICATION_AUDIO"); + } + f.application_type = OPUS_APPLICATION_AUDIO; + } + // bitrate, complexity and signal type should be validated by opus +} + +aoo_error compare(codec *c, const aoo_format_opus *fmt) +{ + // copy and validate! + aoo_format_opus f1; + memcpy(&f1, fmt, sizeof(aoo_format_opus)); + + auto& f2 = c->format; + auto& h1 = f1.header; + auto& h2 = f2.header; + + // check before validate()! + if (strcmp(h1.codec, h2.codec) || + h1.size != h2.size) { + return false; + } + + validate_format(f1, false); + + return h1.blocksize == h2.blocksize && + h1.samplerate == h2.samplerate && + h1.nchannels == h2.nchannels && + f1.application_type == f2.application_type && + f1.bitrate == f2.bitrate && + f1.complexity == f2.complexity && + f1.signal_type == f2.signal_type; +} + +aoo_error get_format(codec *c, aoo_format *f, size_t size) +{ + // check if format has been set + if (c->format.header.codec){ + if (size >= c->format.header.size){ + memcpy(f, &c->format, sizeof(aoo_format_opus)); + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +/*/////////////////////////// encoder //////////////////////*/ + +struct encoder : codec { + ~encoder(){ + if (state){ + deallocate(state, size); + } + } + OpusMSEncoder *state = nullptr; + size_t size = 0; +}; + +void *encoder_new(){ + auto obj = allocate(sizeof(encoder)); + new (obj) encoder {}; + return obj; +} + +void encoder_free(void *enc){ + static_cast(enc)->~encoder(); + deallocate(enc, sizeof(encoder)); +} + +aoo_error encode(void *enc, + const aoo_sample *s, int32_t n, + char *buf, int32_t *size) +{ + auto c = static_cast(enc); + if (c->state){ + auto framesize = n / c->format.header.nchannels; + auto result = opus_multistream_encode_float( + c->state, s, framesize, (unsigned char *)buf, *size); + if (result > 0){ + *size = result; + return AOO_OK; + } else { + LOG_VERBOSE("Opus: opus_encode_float() failed with error code " << result); + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error encoder_set_format(encoder *c, aoo_format_opus *f){ + if (strcmp(f->header.codec, AOO_CODEC_OPUS)){ + return AOO_ERROR_UNSPECIFIED; + } + if (f->header.size < sizeof(aoo_format_opus)){ + return AOO_ERROR_UNSPECIFIED; + } + + validate_format(*f); + + // LATER only deallocate if channels, sr and application type + // have changed, otherwise simply reset the encoder. + if (c->state){ + deallocate(c->state, c->size); + c->state = nullptr; + c->size = 0; + } + // setup channel mapping + // only use decoupled streams (what's the point of coupled streams?) + auto nchannels = f->header.nchannels; + unsigned char mapping[256]; + for (int i = 0; i < nchannels; ++i){ + mapping[i] = i; + } + memset(mapping + nchannels, 255, 256 - nchannels); + // create state + size_t size = opus_multistream_encoder_get_size(nchannels, 0); + auto state = (OpusMSEncoder *)allocate(size); + if (!state){ + return AOO_ERROR_UNSPECIFIED; + } + auto error = opus_multistream_encoder_init(state, f->header.samplerate, + nchannels, nchannels, 0, mapping, f->application_type); + if (error != OPUS_OK){ + LOG_ERROR("Opus: opus_encoder_create() failed with error code " << error); + return AOO_ERROR_UNSPECIFIED; + } + c->state = state; + c->size = size; + // apply settings + // complexity + opus_multistream_encoder_ctl(c->state, OPUS_SET_COMPLEXITY(f->complexity)); + opus_multistream_encoder_ctl(c->state, OPUS_GET_COMPLEXITY(&f->complexity)); + // bitrate + opus_multistream_encoder_ctl(c->state, OPUS_SET_BITRATE(f->bitrate)); +#if 0 + // This control is broken in opus_multistream_encoder (as of opus v1.3.2) + // because it would always return the default bitrate. + // The only thing we can do is omit the function and just keep the input value. + // This means that clients have to explicitly check for OPUS_AUTO and + // OPUS_BITRATE_MAX when reading the 'bitrate' value after encoder_setformat(). + opus_multistream_encoder_ctl(c->state, OPUS_GET_BITRATE(&f->bitrate)); +#endif + // signal type + opus_multistream_encoder_ctl(c->state, OPUS_SET_SIGNAL(f->signal_type)); + opus_multistream_encoder_ctl(c->state, OPUS_GET_SIGNAL(&f->signal_type)); + + // save and print settings + memcpy(&c->format, f, sizeof(aoo_format_opus)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error encoder_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return encoder_set_format((encoder *)x, (aoo_format_opus *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((codec *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + if (opus_multistream_encoder_ctl(static_cast(x)->state, + OPUS_RESET_STATE) == OPUS_OK) { + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (const aoo_format_opus *)ptr); + default: + LOG_WARNING("Opus: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*/////////////////////// decoder ///////////////////////////*/ + +struct decoder : codec { + ~decoder(){ + if (state){ + deallocate(state, size); + } + } + OpusMSDecoder * state = nullptr; + size_t size = 0; +}; + +void *decoder_new(){ + auto obj = allocate(sizeof(decoder)); + new (obj) decoder {}; + return obj; +} + +void decoder_free(void *dec){ + static_cast(dec)->~decoder(); + deallocate(dec, sizeof(decoder)); +} + +aoo_error decode(void *dec, + const char *buf, int32_t size, + aoo_sample *s, int32_t *n) +{ + auto c = static_cast(dec); + if (c->state){ + auto framesize = *n / c->format.header.nchannels; + auto result = opus_multistream_decode_float( + c->state, (const unsigned char *)buf, size, s, framesize, 0); + if (result > 0){ + *n = result; + return AOO_OK; + } else if (result < 0) { + LOG_VERBOSE("Opus: opus_decode_float() failed with error code " << result); + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error decoder_set_format(decoder *c, aoo_format *f) +{ + if (strcmp(f->codec, AOO_CODEC_OPUS)){ + return AOO_ERROR_UNSPECIFIED; + } + if (f->size < sizeof(aoo_format_opus)){ + return AOO_ERROR_UNSPECIFIED; + } + + auto fmt = reinterpret_cast(f); + + validate_format(*fmt); + + // LATER only deallocate if channels and sr have changed, + // otherwise simply reset the decoder. + if (c->state){ + deallocate(c->state, c->size); + c->state = nullptr; + c->size = 0; + } + // setup channel mapping + // only use decoupled streams (what's the point of coupled streams?) + auto nchannels = fmt->header.nchannels; + unsigned char mapping[256]; + for (int i = 0; i < nchannels; ++i){ + mapping[i] = i; + } + memset(mapping + nchannels, 255, 256 - nchannels); + // create state + size_t size = opus_multistream_decoder_get_size(nchannels, 0); + auto state = (OpusMSDecoder *)allocate(size); + if (!state){ + return AOO_ERROR_UNSPECIFIED; + } + auto error = opus_multistream_decoder_init(state, fmt->header.samplerate, + nchannels, nchannels, 0, mapping); + if (error != OPUS_OK){ + LOG_ERROR("Opus: opus_decoder_create() failed with error code " << error); + return AOO_ERROR_UNSPECIFIED; + } + c->state = state; + c->size = size; + // these are actually encoder settings and don't do anything on the decoder +#if 0 + // complexity + opus_multistream_decoder_ctl(c->state, OPUS_SET_COMPLEXITY(f->complexity)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_COMPLEXITY(&f->complexity)); + // bitrate + opus_multistream_decoder_ctl(c->state, OPUS_SET_BITRATE(f->bitrate)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_BITRATE(&f->bitrate)); + // signal type + opus_multistream_decoder_ctl(c->state, OPUS_SET_SIGNAL(f->signal_type)); + opus_multistream_decoder_ctl(c->state, OPUS_GET_SIGNAL(&f->signal_type)); +#endif + + // save and print settings + memcpy(&c->format, fmt, sizeof(aoo_format_opus)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error decoder_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return decoder_set_format((decoder *)x, (aoo_format *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((decoder *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + if (opus_multistream_decoder_ctl(static_cast(x)->state, + OPUS_RESET_STATE) == OPUS_OK) { + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (const aoo_format_opus *)ptr); + default: + LOG_WARNING("Opus: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*////////////////////// codec ////////////////////*/ + +aoo_error serialize(const aoo_format *f, char *buf, int32_t *size){ + if (*size >= 16){ + auto fmt = (const aoo_format_opus *)f; + aoo::to_bytes(fmt->application_type, buf); + aoo::to_bytes(fmt->bitrate, buf + 4); + aoo::to_bytes(fmt->complexity, buf + 8); + aoo::to_bytes(fmt->signal_type, buf + 12); + *size = 16; + + return AOO_OK; + } else { + LOG_WARNING("Opus: couldn't write settings"); + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error deserialize(const aoo_format *header, const char *buf, + int32_t nbytes, aoo_format *f, int32_t size){ + if (nbytes < 16){ + LOG_ERROR("Opus: couldn't read format - not enough data!"); + return AOO_ERROR_UNSPECIFIED; + } + if (size < sizeof(aoo_format_opus)){ + LOG_ERROR("Opus: output format storage too small"); + return AOO_ERROR_UNSPECIFIED; + } + auto fmt = (aoo_format_opus *)f; + // header + fmt->header.codec = AOO_CODEC_OPUS; // static string! + fmt->header.size = sizeof(aoo_format_opus); // actual size! + fmt->header.blocksize = header->blocksize; + fmt->header.nchannels = header->nchannels; + fmt->header.samplerate = header->samplerate; + // options + fmt->application_type = aoo::from_bytes(buf); + fmt->bitrate = aoo::from_bytes(buf + 4); + fmt->complexity = aoo::from_bytes(buf + 8); + fmt->signal_type = aoo::from_bytes(buf + 12); + + return AOO_OK; +} + +aoo_codec codec_class = { + AOO_CODEC_OPUS, + // encoder + encoder_new, + encoder_free, + encoder_ctl, + encode, + // decoder + decoder_new, + decoder_free, + decoder_ctl, + decode, + // helper + serialize, + deserialize +}; + +} // namespace + +void aoo_codec_opus_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc){ + if (alloc){ + g_allocator = *alloc; + } + fn(AOO_CODEC_OPUS, &codec_class); +} + diff --git a/deps/aoo/aoo/src/codec/pcm.cpp b/deps/aoo/aoo/src/codec/pcm.cpp new file mode 100644 index 00000000..8ccc569c --- /dev/null +++ b/deps/aoo/aoo/src/codec/pcm.cpp @@ -0,0 +1,438 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "aoo/codec/aoo_pcm.h" +#include "common/utils.hpp" + +#include +#include + +namespace { + +aoo_allocator g_allocator { + [](size_t n, void *){ return operator new(n); }, + nullptr, + [](void *ptr, size_t, void *){ operator delete(ptr); }, + nullptr +}; + +// conversion routines between aoo_sample and PCM data +union convert { + int8_t b[8]; + int16_t i16; + int32_t i32; + int64_t i64; + float f; + double d; +}; + +int32_t bytes_per_sample(int32_t bd) +{ + switch (bd){ + case AOO_PCM_INT16: + return 2; + case AOO_PCM_INT24: + return 3; + case AOO_PCM_FLOAT32: + return 4; + case AOO_PCM_FLOAT64: + return 8; + default: + assert(false); + return 0; + } +} + +void sample_to_int16(aoo_sample in, char *out) +{ + convert c; + int32_t temp = in * 0x7fff + 0.5f; + c.i16 = (temp > INT16_MAX) ? INT16_MAX : (temp < INT16_MIN) ? INT16_MIN : temp; +#if BYTE_ORDER == BIG_ENDIAN + memcpy(out, c.b, 2); // optimized away +#else + out[0] = c.b[1]; + out[1] = c.b[0]; +#endif +} + +void sample_to_int24(aoo_sample in, char *out) +{ + convert c; + int32_t temp = in * 0x7fffffff + 0.5f; + c.i32 = (temp > INT32_MAX) ? INT32_MAX : (temp < INT32_MIN) ? INT32_MIN : temp; + // only copy the highest 3 bytes! +#if BYTE_ORDER == BIG_ENDIAN + out[0] = c.b[0]; + out[1] = c.b[1]; + out[2] = c.b[2]; +#else + out[0] = c.b[3]; + out[1] = c.b[2]; + out[2] = c.b[1]; +#endif +} + +void sample_to_float32(aoo_sample in, char *out) +{ + aoo::to_bytes(in, out); +} + +void sample_to_float64(aoo_sample in, char *out) +{ + aoo::to_bytes(in, out); +} + +aoo_sample int16_to_sample(const char *in){ + convert c; +#if BYTE_ORDER == BIG_ENDIAN + memcpy(c.b, in, 2); // optimized away +#else + c.b[0] = in[1]; + c.b[1] = in[0]; +#endif + return(aoo_sample)c.i16 / 32768.f; +} + +aoo_sample int24_to_sample(const char *in) +{ + convert c; + // copy to the highest 3 bytes! +#if BYTE_ORDER == BIG_ENDIAN + c.b[0] = in[0]; + c.b[1] = in[1]; + c.b[2] = in[2]; + c.b[3] = 0; +#else + c.b[0] = 0; + c.b[1] = in[2]; + c.b[2] = in[1]; + c.b[3] = in[0]; +#endif + return (aoo_sample)c.i32 / 0x7fffffff; +} + +aoo_sample float32_to_sample(const char *in) +{ + return aoo::from_bytes(in); +} + +aoo_sample float64_to_sample(const char *in) +{ + return aoo::from_bytes(in); +} + +void print_settings(const aoo_format_pcm& f) +{ + LOG_VERBOSE("PCM settings: " + << "nchannels = " << f.header.nchannels + << ", blocksize = " << f.header.blocksize + << ", samplerate = " << f.header.samplerate + << ", bitdepth = " << bytes_per_sample(f.bitdepth)); +} + +/*//////////////////// codec //////////////////////////*/ + +struct codec { + codec(){ + memset(&format, 0, sizeof(aoo_format_pcm)); + } + aoo_format_pcm format; +}; + +void validate_format(aoo_format_pcm& f, bool loud = true) +{ + f.header.codec = AOO_CODEC_PCM; // static string! + f.header.size = sizeof(aoo_format_pcm); // actual size! + + // validate blocksize + if (f.header.blocksize <= 0){ + if (loud){ + LOG_WARNING("PCM: bad blocksize " << f.header.blocksize + << ", using 64 samples"); + } + f.header.blocksize = 64; + } + // validate samplerate + if (f.header.samplerate <= 0){ + if (loud){ + LOG_WARNING("PCM: bad samplerate " << f.header.samplerate + << ", using 44100"); + } + f.header.samplerate = 44100; + } + // validate channels + if (f.header.nchannels <= 0 || f.header.nchannels > 255){ + if (loud){ + LOG_WARNING("PCM: bad channel count " << f.header.nchannels + << ", using 1 channel"); + } + f.header.nchannels = 1; + } + // validate bitdepth + if (f.bitdepth < 0 || f.bitdepth > AOO_PCM_BITDEPTH_SIZE){ + if (loud){ + LOG_WARNING("PCM: bad bitdepth, using 32bit float"); + } + f.bitdepth = AOO_PCM_FLOAT32; + } +} + +aoo_error compare(codec *c, const aoo_format_pcm *fmt) +{ + // copy and validate! + aoo_format_pcm f1; + memcpy(&f1, fmt, sizeof(aoo_format_pcm)); + + auto& f2 = c->format; + auto& h1 = f1.header; + auto& h2 = f2.header; + + // check before validate()! + if (strcmp(h1.codec, h2.codec) || + h1.size != h2.size) { + return false; + } + + validate_format(f1, false); + + return h1.blocksize == h2.blocksize && + h1.samplerate == h2.samplerate && + h1.nchannels == h2.nchannels && + f1.bitdepth == f2.bitdepth; +} + +aoo_error set_format(codec *c, aoo_format_pcm *fmt) +{ + if (strcmp(fmt->header.codec, AOO_CODEC_PCM)){ + return AOO_ERROR_UNSPECIFIED; + } + if (fmt->header.size < sizeof(aoo_format_pcm)){ + return AOO_ERROR_UNSPECIFIED; + } + + validate_format(*fmt); + + // save and print settings + memcpy(&c->format, fmt, sizeof(aoo_format_pcm)); + print_settings(c->format); + + return AOO_OK; +} + +aoo_error get_format(codec *c, aoo_format *f, size_t size) +{ + // check if format has been set + if (c->format.header.codec){ + if (size >= c->format.header.size){ + memcpy(f, &c->format, sizeof(aoo_format_pcm)); + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error pcm_ctl(void *x, int32_t ctl, void *ptr, int32_t size){ + switch (ctl){ + case AOO_CODEC_SET_FORMAT: + assert(size >= sizeof(aoo_format)); + return set_format((codec *)x, (aoo_format_pcm *)ptr); + case AOO_CODEC_GET_FORMAT: + return get_format((codec *)x, (aoo_format *)ptr, size); + case AOO_CODEC_RESET: + // no op + return AOO_OK; + case AOO_CODEC_FORMAT_EQUAL: + assert(size >= sizeof(aoo_format)); + return compare((codec *)x, (aoo_format_pcm *)ptr); + default: + LOG_WARNING("PCM: unsupported codec ctl " << ctl); + return AOO_ERROR_UNSPECIFIED; + } +} + +void *encoder_new(){ + auto obj = g_allocator.alloc(sizeof(codec), g_allocator.context); + new (obj) codec {}; + return obj; +} + +void encoder_free(void *enc){ + static_cast(enc)->~codec(); + g_allocator.free(enc, sizeof(codec), g_allocator.context); +} + +aoo_error encode(void *enc, + const aoo_sample *s, int32_t n, + char *buf, int32_t *size) +{ + auto bitdepth = static_cast(enc)->format.bitdepth; + auto samplesize = bytes_per_sample(bitdepth); + auto nbytes = samplesize * n; + + if (*size < nbytes){ + LOG_WARNING("PCM: size mismatch! input bytes: " + << nbytes << ", output bytes " << *size); + return AOO_ERROR_UNSPECIFIED; + } + + auto samples_to_blob = [&](auto fn){ + auto b = buf; + for (int i = 0; i < n; ++i){ + fn(s[i], b); + b += samplesize; + } + }; + + switch (bitdepth){ + case AOO_PCM_INT16: + samples_to_blob(sample_to_int16); + break; + case AOO_PCM_INT24: + samples_to_blob(sample_to_int24); + break; + case AOO_PCM_FLOAT32: + samples_to_blob(sample_to_float32); + break; + case AOO_PCM_FLOAT64: + samples_to_blob(sample_to_float64); + break; + default: + // unknown bitdepth + break; + } + + *size = nbytes; + + return AOO_OK; +} + +void *decoder_new(){ + return new codec; +} + +void decoder_free(void *dec){ + delete (codec *)dec; +} + +aoo_error decode(void *dec, + const char *buf, int32_t size, + aoo_sample *s, int32_t *n) +{ + auto c = static_cast(dec); + assert(c->format.header.blocksize != 0); + + if (!buf){ + for (int i = 0; i < *n; ++i){ + s[i] = 0; + } + return AOO_OK; // dropped block + } + + auto samplesize = bytes_per_sample(c->format.bitdepth); + auto nsamples = size / samplesize; + + if (*n < nsamples){ + LOG_WARNING("PCM: size mismatch! input samples: " + << nsamples << ", output samples " << *n); + return AOO_ERROR_UNSPECIFIED; + } + + auto blob_to_samples = [&](auto convfn){ + auto b = buf; + for (int i = 0; i < *n; ++i, b += samplesize){ + s[i] = convfn(b); + } + }; + + switch (c->format.bitdepth){ + case AOO_PCM_INT16: + blob_to_samples(int16_to_sample); + break; + case AOO_PCM_INT24: + blob_to_samples(int24_to_sample); + break; + case AOO_PCM_FLOAT32: + blob_to_samples(float32_to_sample); + break; + case AOO_PCM_FLOAT64: + blob_to_samples(float64_to_sample); + break; + default: + // unknown bitdepth + return AOO_ERROR_UNSPECIFIED; + } + + *n = nsamples; + + return AOO_OK; +} + +aoo_error serialize(const aoo_format *f, char *buf, int32_t *size) +{ + if (*size >= 4){ + auto fmt = (const aoo_format_pcm *)f; + aoo::to_bytes(fmt->bitdepth, buf); + *size = 4; + + return AOO_OK; + } else { + LOG_ERROR("PCM: couldn't write settings - buffer too small!"); + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error deserialize(const aoo_format *header, const char *buf, + int32_t nbytes, aoo_format *f, int32_t size) +{ + if (nbytes < 4){ + LOG_ERROR("PCM: couldn't read format - not enough data!"); + return AOO_ERROR_UNSPECIFIED; + } + if (size < sizeof(aoo_format_pcm)){ + LOG_ERROR("PCM: output format storage too small"); + return AOO_ERROR_UNSPECIFIED; + } + auto fmt = (aoo_format_pcm *)f; + // header + fmt->header.codec = AOO_CODEC_PCM; // static string! + fmt->header.size = sizeof(aoo_format_pcm); // actual size! + fmt->header.blocksize = header->blocksize; + fmt->header.nchannels = header->nchannels; + fmt->header.samplerate = header->samplerate; + // options + fmt->bitdepth = (aoo_pcm_bitdepth)aoo::from_bytes(buf); + + return AOO_OK; +} + +aoo_codec codec_class = { + AOO_CODEC_PCM, + // encoder + encoder_new, + encoder_free, + pcm_ctl, + encode, + // decoder + decoder_new, + decoder_free, + pcm_ctl, + decode, + // helper + serialize, + deserialize +}; + +} // namespace + +void aoo_codec_pcm_setup(aoo_codec_registerfn fn, const aoo_allocator *alloc){ + if (alloc){ + g_allocator = *alloc; + } + fn(AOO_CODEC_PCM, &codec_class); +} + diff --git a/deps/aoo/aoo/src/imp.hpp b/deps/aoo/aoo/src/imp.hpp new file mode 100644 index 00000000..414e3654 --- /dev/null +++ b/deps/aoo/aoo/src/imp.hpp @@ -0,0 +1,171 @@ +#pragma once + +#include "aoo/aoo.h" + +#include +#include +#include +#include + +namespace aoo { + +uint32_t make_version(); + +bool check_version(uint32_t version); + +char * copy_string(const char *s); + +void free_string(char *s); + +void * copy_sockaddr(const void *sa, int32_t len); + +void free_sockaddr(void *sa, int32_t len); + +namespace net { + +aoo_error parse_pattern(const char *msg, int32_t n, aoo_type& type, int32_t& offset); + +} // net + +/*///////////////////// allocator ////////////////////*/ + + +#if AOO_CUSTOM_ALLOCATOR || AOO_DEBUG_MEMORY + +void * allocate(size_t size); + +template +T * construct(U&&... args){ + auto ptr = allocate(sizeof(T)); + new (ptr) T(std::forward(args)...); + return (T *)ptr; +} + +void deallocate(void *ptr, size_t size); + +template +void destroy(T *x){ + x->~T(); + deallocate(x, sizeof(T)); +} + +template +class allocator { +public: + using value_type = T; + + allocator() noexcept = default; + + template + allocator(const allocator&) noexcept {} + + template + allocator& operator=(const allocator&) noexcept {} + + template + struct rebind { + typedef allocator other; + }; + + value_type* allocate(size_t n) { + return (value_type *)aoo::allocate(sizeof(T) * n); + } + + void deallocate(value_type* p, size_t n) noexcept { + aoo::deallocate(p, sizeof(T) * n); + } +}; + +template +bool operator==(allocator const&, allocator const&) noexcept +{ + return true; +} + +template +bool operator!=(allocator const& x, allocator const& y) noexcept +{ + return !(x == y); +} + +#else + +inline void * allocate(size_t size){ + return operator new(size); +} + +template +T * construct(U&&... args){ + return new T(std::forward(args)...); +} + +inline void deallocate(void *ptr, size_t size){ + operator delete(ptr); +} + +template +void destroy(T *x){ + delete x; +} + +template +using allocator = std::allocator; + +#endif + +/*////////////// memory //////////////*/ + +struct memory_block { + struct { + memory_block *next; + size_t size; + } header; + char mem[1]; + + static memory_block * allocate(size_t size); + + static void free(memory_block *mem); + + static memory_block * from_bytes(void *bytes){ + return (memory_block *)((char *)bytes - sizeof(memory_block::header)); + } + + size_t full_size() const { + return header.size + sizeof(header); + } + + size_t size() const { + return header.size; + } + + void * data() { + return mem; + } +}; + +class memory_list { +public: + memory_list() = default; + ~memory_list(); + memory_list(memory_list&& other) + : memlist_(other.memlist_.exchange(nullptr)){} + memory_list& operator=(memory_list&& other){ + memlist_.store(other.memlist_.exchange(nullptr)); + return *this; + } + memory_block* alloc(size_t size); + void free(memory_block* b); +private: + std::atomic memlist_{nullptr}; +}; + +/*///////////////// misc ///////////////////*/ + +struct format_deleter { + void operator() (void *x) const { + auto f = static_cast(x); + aoo::deallocate(x, f->size); + } +}; + +} // aoo diff --git a/deps/aoo/lib/src/SLIP.hpp b/deps/aoo/aoo/src/net/SLIP.hpp similarity index 87% rename from deps/aoo/lib/src/SLIP.hpp rename to deps/aoo/aoo/src/net/SLIP.hpp index fe4e45a1..02332f21 100644 --- a/deps/aoo/lib/src/SLIP.hpp +++ b/deps/aoo/aoo/src/net/SLIP.hpp @@ -5,8 +5,12 @@ namespace aoo { +template> class SLIP { public: + SLIP(const Alloc& alloc = Alloc {}) + : buffer_(alloc) {} + static const uint8_t END = 192; static const uint8_t ESC = 219; static const uint8_t ESC_END = 220; @@ -25,24 +29,27 @@ class SLIP { bool write_packet(const uint8_t *data, int32_t size); private: - std::vector buffer_; + std::vector buffer_; int32_t rdhead_ = 0; int32_t wrhead_ = 0; int32_t balance_ = 0; }; -inline void SLIP::setup(int32_t buffersize){ +template +inline void SLIP::setup(int32_t buffersize){ buffer_.resize(buffersize); reset(); } -inline void SLIP::reset(){ +template +inline void SLIP::reset(){ rdhead_ = 0; wrhead_ = 0; balance_ = 0; } -inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ +template +inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ auto capacity = (int32_t)buffer_.size(); if (size > balance_){ size = balance_; @@ -66,7 +73,8 @@ inline int32_t SLIP::read_bytes(uint8_t *buffer, int32_t size){ return size; } -inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ +template +inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ auto capacity = (int32_t)buffer_.size(); auto space = capacity - balance_; if (size > space){ @@ -89,7 +97,8 @@ inline int32_t SLIP::write_bytes(const uint8_t *data, int32_t size){ return size; } -inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ +template +inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ int32_t nbytes = 0; int32_t head = rdhead_; @@ -159,7 +168,8 @@ inline int32_t SLIP::read_packet(uint8_t *buffer, int32_t size){ return packetsize; } -inline bool SLIP::write_packet(const uint8_t *data, int32_t size){ +template +inline bool SLIP::write_packet(const uint8_t *data, int32_t size){ int32_t available = buffer_.size() - balance_; int32_t nbytes = 0; int32_t head = wrhead_; diff --git a/deps/aoo/aoo/src/net/client.cpp b/deps/aoo/aoo/src/net/client.cpp new file mode 100644 index 00000000..65c9db43 --- /dev/null +++ b/deps/aoo/aoo/src/net/client.cpp @@ -0,0 +1,1927 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "client.hpp" + +#include +#include +#include +#include + +#include "md5/md5.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#define AOO_NET_MSG_SERVER_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_PING + +#define AOO_NET_MSG_PEER_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_PING + +#define AOO_NET_MSG_PEER_REPLY \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_REPLY + +#define AOO_NET_MSG_PEER_MESSAGE \ + AOO_MSG_DOMAIN AOO_NET_MSG_PEER AOO_NET_MSG_MESSAGE + +#define AOO_NET_MSG_SERVER_LOGIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_LOGIN + +#define AOO_NET_MSG_SERVER_REQUEST \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_REQUEST + +#define AOO_NET_MSG_SERVER_GROUP_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_SERVER_GROUP_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_SERVER AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_GROUP_JOIN \ + AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_GROUP_LEAVE \ + AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_PEER_JOIN \ + AOO_NET_MSG_PEER AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_PEER_LEAVE \ + AOO_NET_MSG_PEER AOO_NET_MSG_LEAVE + +// debugging + +#define FORCE_RELAY 0 + +#define DEBUG_RELAY 0 + +namespace aoo { + +namespace net { + +std::string encrypt(const std::string& input){ + uint8_t result[16]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, (uint8_t *)input.data(), input.size()); + MD5_Final(result, &ctx); + + char output[33]; + for (int i = 0; i < 16; ++i){ + sprintf(&output[i * 2], "%02X", result[i]); + } + + return output; +} + +void copy_string(const std::string& src, char *dst, int32_t size){ + const auto limit = size - 1; // leave space for '\0' + auto n = (src.size() > limit) ? limit : src.size(); + memcpy(dst, src.data(), n); + dst[n] = '\0'; +} + +/*//////////////////// OSC ////////////////////////////*/ + +// optimized version of aoo_parse_pattern() for client/server +aoo_error parse_pattern(const char *msg, int32_t n, aoo_type& type, int32_t& offset) +{ + int32_t count = 0; + if (n >= AOO_BIN_MSG_HEADER_SIZE && + !memcmp(msg, AOO_BIN_MSG_DOMAIN, AOO_BIN_MSG_DOMAIN_SIZE)) + { + // domain (int32), type (int16), cmd (int16), id (int32) ... + type = aoo::from_bytes(msg + 4); + offset = 12; + + return AOO_OK; + } else if (n >= AOO_MSG_DOMAIN_LEN + && !memcmp(msg, AOO_MSG_DOMAIN, AOO_MSG_DOMAIN_LEN)) + { + count += AOO_MSG_DOMAIN_LEN; + if (n >= (count + AOO_NET_MSG_SERVER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_SERVER, AOO_NET_MSG_SERVER_LEN)) + { + type = AOO_TYPE_SERVER; + count += AOO_NET_MSG_SERVER_LEN; + } + else if (n >= (count + AOO_NET_MSG_CLIENT_LEN) + && !memcmp(msg + count, AOO_NET_MSG_CLIENT, AOO_NET_MSG_CLIENT_LEN)) + { + type = AOO_TYPE_CLIENT; + count += AOO_NET_MSG_CLIENT_LEN; + } + else if (n >= (count + AOO_NET_MSG_PEER_LEN) + && !memcmp(msg + count, AOO_NET_MSG_PEER, AOO_NET_MSG_PEER_LEN)) + { + type = AOO_TYPE_PEER; + count += AOO_NET_MSG_PEER_LEN; + } + else if (n >= (count + AOO_NET_MSG_RELAY_LEN) + && !memcmp(msg + count, AOO_NET_MSG_RELAY, AOO_NET_MSG_RELAY_LEN)) + { + type = AOO_TYPE_RELAY; + count += AOO_NET_MSG_RELAY_LEN; + } else { + return AOO_ERROR_UNSPECIFIED; + } + + offset = count; + + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; // not an AoO message + } +} + +} // net +} // aoo + +/*//////////////////// AoO client /////////////////////*/ + +aoo_net_client * aoo_net_client_new(const void *address, int32_t addrlen, + uint32_t flags) { + return aoo::construct(address, addrlen, flags); +} + +aoo::net::client_imp::client_imp(const void *address, int32_t addrlen, uint32_t flags) +{ + ip_address addr((const sockaddr *)address, addrlen); + udp_client_ = std::make_unique(*this, addr.port(), flags); + type_ = addr.type(); + + eventsocket_ = socket_udp(0); + if (eventsocket_ < 0){ + // TODO handle error + socket_error_print("socket_udp"); + } + + sendbuffer_.setup(65536); + recvbuffer_.setup(65536); + + // commands_.reserve(256); + // messages_.reserve(256); + // events_.reserve(256); +} + +void aoo_net_client_free(aoo_net_client *client){ + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(client)); +} + +aoo::net::client_imp::~client_imp() { + if (socket_ >= 0){ + socket_close(socket_); + } +} + +aoo_error aoo_net_client_run(aoo_net_client *client){ + return client->run(); +} + +aoo_error aoo::net::client_imp::run(){ + start_time_ = time_tag::now(); + + while (!quit_.load()){ + double timeout = 0; + + time_tag now = time_tag::now(); + auto elapsed_time = time_tag::duration(start_time_, now); + + if (state_.load() == client_state::connected){ + auto delta = elapsed_time - last_ping_time_; + auto interval = ping_interval(); + if (delta >= interval){ + // send ping + if (socket_ >= 0){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_PING) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); + } else { + LOG_ERROR("aoo_client: bug send_ping()"); + } + + last_ping_time_ = elapsed_time; + timeout = interval; + } else { + timeout = interval - delta; + } + } else { + timeout = -1; + } + + if (!wait_for_event(timeout)){ + break; + } + + // handle commands + std::unique_ptr cmd; + while (commands_.try_pop(cmd)){ + cmd->perform(*this); + } + + // handle messages + std::unique_ptr msg; + while (tcp_messages_.try_pop(msg)){ + // call with dummy reply function + msg->perform(*this, sendfn{}); + } + + if (!peers_.try_free()){ + LOG_DEBUG("aoo::client: try_free() would block"); + } + } + return AOO_OK; +} + +aoo_error aoo_net_client_quit(aoo_net_client *client){ + return client->quit(); +} + +aoo_error aoo::net::client_imp::quit(){ + quit_.store(true); + if (!signal()){ + // force wakeup by closing the socket. + // this is not nice and probably undefined behavior, + // the MSDN docs explicitly forbid it! + socket_close(eventsocket_); + } + return AOO_OK; +} + +aoo_error aoo_net_client_add_source(aoo_net_client *client, + aoo_source *src, aoo_id id) +{ + return client->add_source(src, id); +} + +aoo_error aoo::net::client_imp::add_source(source *src, aoo_id id) +{ +#if 1 + for (auto& s : sources_){ + if (s.source == src){ + LOG_ERROR("aoo_client: source already added"); + return AOO_ERROR_UNSPECIFIED; + } else if (s.id == id){ + LOG_WARNING("aoo_client: source with id " << id + << " already added!"); + return AOO_ERROR_UNSPECIFIED; + } + } +#endif + sources_.push_back({ src, id }); + return AOO_OK; +} + +aoo_error aoo_net_client_remove_source(aoo_net_client *client, + aoo_source *src) +{ + return client->remove_source(src); +} + +aoo_error aoo::net::client_imp::remove_source(source *src) +{ + for (auto it = sources_.begin(); it != sources_.end(); ++it){ + if (it->source == src){ + sources_.erase(it); + return AOO_OK; + } + } + LOG_ERROR("aoo_client: source not found"); + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_add_sink(aoo_net_client *client, + aoo_sink *sink, aoo_id id) +{ + return client->add_sink(sink, id); +} + +aoo_error aoo::net::client_imp::add_sink(sink *sink, aoo_id id) +{ +#if 1 + for (auto& s : sinks_){ + if (s.sink == sink){ + LOG_ERROR("aoo_client: sink already added"); + return AOO_OK; + } else if (s.id == id){ + LOG_WARNING("aoo_client: sink with id " << id + << " already added!"); + return AOO_ERROR_UNSPECIFIED; + } + } +#endif + sinks_.push_back({ sink, id }); + return AOO_OK; +} + +aoo_error aoo_net_client_remove_sink(aoo_net_client *client, + aoo_sink *sink) +{ + return client->remove_sink(sink); +} + +aoo_error aoo::net::client_imp::remove_sink(sink *sink) +{ + for (auto it = sinks_.begin(); it != sinks_.end(); ++it){ + if (it->sink == sink){ + sinks_.erase(it); + return AOO_OK; + } + } + LOG_ERROR("aoo_client: sink not found"); + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_get_peer_address(aoo_net_client *client, + const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) +{ + return client->get_peer_address(group, user, address, addrlen, flags); +} + +aoo_error aoo::net::client_imp::get_peer_address(const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) +{ + peer_lock lock(peers_); + for (auto& p : peers_){ + // we can only access the address if the peer is connected! + if (p.match(group, user) && p.connected()){ + if (address){ + auto& addr = p.address(); + if (*addrlen < addr.length()){ + return AOO_ERROR_UNSPECIFIED; + } + memcpy(address, addr.address(), addr.length()); + *addrlen = addr.length(); + } + if (flags){ + *flags = p.flags(); + } + return AOO_OK; + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_get_peer_info(aoo_net_client *client, + const void *address, int32_t addrlen, + aoo_net_peer_info *info) +{ + return client->get_peer_info(address, addrlen, info); +} + +aoo_error aoo::net::client_imp::get_peer_info(const void *address, int32_t addrlen, + aoo_net_peer_info *info) +{ + peer_lock lock(peers_); + for (auto& p : peers_){ + ip_address addr((const sockaddr *)address, addrlen); + if (p.match(addr)){ + aoo::net::copy_string(p.group(), info->group_name, sizeof(info->group_name)); + aoo::net::copy_string(p.user(), info->user_name, sizeof(info->user_name)); + info->user_id = p.id(); + info->flags = p.flags(); + + return AOO_OK; + } + } + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_request(aoo_net_client *client, + aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user) { + return client->send_request(request, data, callback, user); +} + +aoo_error aoo::net::client_imp::send_request(aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user){ + switch (request){ + case AOO_NET_CONNECT_REQUEST: + { + auto d = (aoo_net_connect_request *)data; + do_connect(d->host, d->port, d->user_name, d->user_pwd, callback, user); + break; + } + case AOO_NET_DISCONNECT_REQUEST: + do_disconnect(callback, user); + break; + case AOO_NET_GROUP_JOIN_REQUEST: + { + auto d = (aoo_net_group_request *)data; + do_join_group(d->group_name, d->group_pwd, callback, user); + break; + } + case AOO_NET_GROUP_LEAVE_REQUEST: + { + auto d = (aoo_net_group_request *)data; + do_leave_group(d->group_name, callback, user); + break; + } + default: + LOG_ERROR("aoo client: unknown request " << request); + return AOO_ERROR_UNSPECIFIED; + } + return AOO_OK; +} + +aoo_error aoo_net_client_send_message(aoo_net_client *client, const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) +{ + return client->send_message(data, size, addr, len, flags); +} + +aoo_error aoo::net::client_imp::send_message(const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) +{ + // for now, we simply achieve 'reliable' messages by relaying over TCP + // LATER implement ack mechanism over UDP. + bool reliable = flags & AOO_NET_MESSAGE_RELIABLE; + + std::unique_ptr msg; + if (addr){ + if (len > 0){ + // peer message + msg = std::make_unique( + data, size, (const sockaddr *)addr, len, flags); + } else { + // group message + msg = std::make_unique( + data, size, (const char *)addr, flags); + } + } else { + msg = std::make_unique(data, size, flags); + } + + if (reliable){ + // add to TCP message queue + tcp_messages_.push(std::move(msg)); + signal(); + } else { + // add to UDP message queue + udp_messages_.push(std::move(msg)); + } + return AOO_OK; +} + +aoo_error aoo_net_client_handle_message(aoo_net_client *client, const char *data, + int32_t n, const void *addr, int32_t len) +{ + return client->handle_message(data, n, addr, len); +} + +aoo_error aoo::net::client_imp::handle_message(const char *data, int32_t n, + const void *addr, int32_t len){ + int32_t type; + aoo_id id; + int32_t onset; + auto err = aoo_parse_pattern(data, n, &type, &id, &onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_client: not an AOO NET message!"); + return AOO_ERROR_UNSPECIFIED; + } + + if (type == AOO_TYPE_SOURCE){ + // forward to matching source + for (auto& s : sources_){ + if (s.id == id){ + return s.source->handle_message(data, n, addr, len); + } + } + LOG_WARNING("aoo_client: handle_message(): source not found"); + } else if (type == AOO_TYPE_SINK){ + // forward to matching sink + for (auto& s : sinks_){ + if (s.id == id){ + return s.sink->handle_message(data, n, addr, len); + } + } + LOG_WARNING("aoo_client: handle_message(): sink not found"); + } else if (udp_client_){ + // forward to UDP client + ip_address address((const sockaddr *)addr, len); + return udp_client_->handle_message(data, n, address, type, onset); + } + + return AOO_ERROR_UNSPECIFIED; +} + +aoo_error aoo_net_client_send(aoo_net_client *client, aoo_sendfn fn, void *user){ + return client->send(fn, user); +} + +aoo_error aoo::net::client_imp::send(aoo_sendfn fn, void *user){ + struct proxy_data { + client_imp *self; + aoo_sendfn fn; + void *user; + }; + + auto proxy = [](void *x, const char *data, int32_t n, + const void *address, int32_t addrlen, uint32_t flags){ + auto y = static_cast(x); + + bool relay = flags & AOO_ENDPOINT_RELAY; + if (relay){ + // relay via server + ip_address addr((const sockaddr *)address, addrlen); + + sendfn reply(y->fn, y->user); + + y->self->udp().send_peer_message(data, n, addr, reply, true); + + return (int32_t)0; + } else { + // just forward + return y->fn(y->user, data, n, address, addrlen, flags); + } + }; + + proxy_data data {this, fn, user}; + + // send sources and sinks + for (auto& s : sources_){ + s.source->send(proxy, &data); + } + for (auto& s : sinks_){ + s.sink->send(proxy, &data); + } + // send server messages + if (state_.load() != client_state::disconnected){ + time_tag now = time_tag::now(); + sendfn reply(fn, user); + + if (udp_client_){ + udp_client_->update(reply, now); + } + + // send outgoing peer/group messages + std::unique_ptr msg; + while (udp_messages_.try_pop(msg)){ + msg->perform(*this, reply); + } + + // update peers + peer_lock lock(peers_); + for (auto& p : peers_){ + p.send(reply, now); + } + } + return AOO_OK; +} + +aoo_error aoo_net_client_set_eventhandler(aoo_net_client *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::net::client_imp::set_eventhandler(aoo_eventhandler fn, void *user, + int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_net_client_events_available(aoo_net_server *client){ + return client->events_available(); +} + +aoo_bool aoo::net::client_imp::events_available(){ + return !events_.empty(); +} + +aoo_error aoo_net_client_poll_events(aoo_net_client *client){ + return client->poll_events(); +} + +aoo_error aoo::net::client_imp::poll_events(){ + // always thread-safe + std::unique_ptr e; + while (events_.try_pop(e)){ + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_UNKNOWN); + } + return AOO_OK; +} + +aoo_error aoo_net_client_ctl(aoo_net_client *client, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return client->control(ctl, index, p, size); +} + +aoo_error aoo::net::client_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + LOG_WARNING("aoo_client: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; +} + +namespace aoo { +namespace net { + +bool client_imp::handle_peer_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr) +{ + bool success = false; + // NOTE: we have to loop over *all* peers because there can + // be more than 1 peer on a given IP endpoint, since a single + // user can join multiple groups. + // LATER make this more efficient, e.g. by associating IP endpoints + // with peers instead of having them all in a single list. + peer_lock lock(peers_); + for (auto& p : peers_){ + // forward to matching or unconnected peers! + if (p.match(addr, true)){ + p.handle_message(msg, onset, addr); + success = true; + } + } + return success; +} + +template +void client_imp::perform_send_message(const char *data, int32_t size, int32_t flags, + const sendfn& fn, T&& filter) +{ + bool reliable = flags & AOO_NET_MESSAGE_RELIABLE; + // embed inside an OSC message: + // /aoo/peer/msg' (b) + try { + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_MESSAGE) + << osc::Blob(data, size) << osc::EndMessage; + + peer_lock lock(peers_); + for (auto& peer : peers_){ + if (filter(peer)){ + auto& addr = peer.address(); + LOG_DEBUG("aoo_client: send message " << data + << " to " << addr.name() << ":" << addr.port()); + // Note: reliable messages are dispatched in the TCP network thread, + // unreliable messages are dispatched in the UDP network thread. + if (reliable){ + send_peer_message(msg.Data(), msg.Size(), addr); + } else if (udp_client_){ + udp_client_->send_peer_message(msg.Data(), msg.Size(), + addr, fn, peer.relay()); + } + } + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: error sending OSC message: " << e.what()); + } +} + +void client_imp::do_connect(const char *host, int port, + const char *name, const char *pwd, + aoo_net_callback cb, void *user) +{ + auto cmd = std::make_unique(cb, user, host, port, + name, encrypt(pwd)); + + push_command(std::move(cmd)); +} + +void client_imp::perform_connect(const std::string& host, int port, + const std::string& name, const std::string& pwd, + aoo_net_callback cb, void *user) +{ + auto state = state_.load(); + if (state != client_state::disconnected){ + aoo_net_error_reply reply; + reply.error_code = 0; + if (state == client_state::connected){ + reply.error_message = "already connected"; + } else { + reply.error_message = "already connecting"; + } + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + state_.store(client_state::connecting); + + int err = try_connect(host, port); + if (err != 0){ + // event + std::string errmsg = socket_strerror(err); + + close(); + + aoo_net_error_reply reply; + reply.error_code = err; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + username_ = name; + password_ = pwd; + callback_ = cb; + userdata_ = user; + + state_.store(client_state::handshake); +} + +int client_imp::try_connect(const std::string &host, int port){ + socket_ = socket_tcp(0); + if (socket_ < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't create socket (" << err << ")"); + return err; + } + // resolve host name + auto result = ip_address::resolve(host, port, type_); + if (result.empty()){ + int err = socket_errno(); + // LATER think about best way for error handling. Maybe exception? + LOG_ERROR("aoo_client: couldn't resolve hostname (" << socket_errno() << ")"); + return err; + } + + LOG_DEBUG("aoo_client: server address list:"); + for (auto& addr : result){ + LOG_DEBUG("\t" << addr.name() << " " << addr.port()); + } + + // for actual TCP connection, just pick the first result + auto& remote = result.front(); + LOG_VERBOSE("try to connect to " << remote.name() << "/" << port); + + // try to connect (LATER make timeout configurable) + if (socket_connect(socket_, remote, 5) < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't connect (" << err << ")"); + return err; + } + + // get local network interface + ip_address temp; + if (socket_address(socket_, temp) < 0){ + int err = socket_errno(); + LOG_ERROR("aoo_client: couldn't get socket name (" << err << ")"); + return err; + } + ip_address local(temp.name(), udp_client_->port(), type_); + + LOG_VERBOSE("aoo_client: successfully connected to " + << remote.name() << " on port " << remote.port()); + LOG_VERBOSE("aoo_client: local address: " << local.name()); + + udp_client_->start_handshake(local, std::move(result)); + + return 0; +} + +void client_imp::do_disconnect(aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user); + + push_command(std::move(cmd)); +} + +void client_imp::perform_disconnect(aoo_net_callback cb, void *user){ + auto state = state_.load(); + if (state != client_state::connected){ + aoo_net_error_reply reply; + reply.error_message = (state == client_state::disconnected) + ? "not connected" : "still connecting"; + reply.error_code = 0; + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + + return; + } + + close(true); + + if (cb) cb(user, AOO_OK, nullptr); // always succeeds +} + +void client_imp::perform_login(const ip_address_list& addrlist){ + state_.store(client_state::login); + + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_LOGIN) + << (int32_t)make_version() + << username_.c_str() << password_.c_str(); + for (auto& addr : addrlist){ + // unmap IPv4 addresses for IPv4 only servers + msg << addr.name_unmapped() << addr.port(); + } + msg << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::perform_timeout(){ + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = "UDP handshake time out"; + + callback_(userdata_, AOO_ERROR_UNSPECIFIED, &reply); + + if (state_.load() == client_state::handshake){ + close(); + } +} + +void client_imp::do_join_group(const char *group, const char *pwd, + aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user, group, encrypt(pwd)); + + push_command(std::move(cmd)); +} + +void client_imp::perform_join_group(const std::string &group, const std::string &pwd, + aoo_net_callback cb, void *user) +{ + + auto request = [group, cb, user]( + const char *pattern, + const osc::ReceivedMessage& msg) + { + if (!strcmp(pattern, AOO_NET_MSG_GROUP_JOIN)){ + auto it = msg.ArgumentsBegin(); + std::string g = (it++)->AsString(); + if (g == group){ + int32_t status = (it++)->AsInt32(); + if (status > 0){ + LOG_VERBOSE("aoo_client: successfully joined group " << group); + if (cb) cb(user, AOO_OK, nullptr); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 2){ + errmsg = (it++)->AsString(); + LOG_WARNING("aoo_client: couldn't join group " + << group << ": " << errmsg); + } else { + errmsg = "unknown error"; + } + // reply + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + } + + return true; + } + } + return false; + }; + pending_requests_.push_back(request); + + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_GROUP_JOIN) + << group.c_str() << pwd.c_str() << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::do_leave_group(const char *group, + aoo_net_callback cb, void *user){ + auto cmd = std::make_unique(cb, user, group); + + push_command(std::move(cmd)); +} + +void client_imp::perform_leave_group(const std::string &group, + aoo_net_callback cb, void *user) +{ + auto request = [this, group, cb, user]( + const char *pattern, + const osc::ReceivedMessage& msg) + { + if (!strcmp(pattern, AOO_NET_MSG_GROUP_LEAVE)){ + auto it = msg.ArgumentsBegin(); + std::string g = (it++)->AsString(); + if (g == group){ + int32_t status = (it++)->AsInt32(); + if (status > 0){ + LOG_VERBOSE("aoo_client: successfully left group " << group); + + // remove all peers from this group + peer_lock lock(peers_); + for (auto it = peers_.begin(); it != peers_.end(); ){ + if (it->match(group)){ + it = peers_.erase(it); + } else { + ++it; + } + } + lock.unlock(); + + if (cb) cb(user, AOO_OK, nullptr); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 2){ + errmsg = (it++)->AsString(); + LOG_WARNING("aoo_client: couldn't leave group " + << group << ": " << errmsg); + } else { + errmsg = "unknown error"; + } + // reply + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + if (cb) cb(user, AOO_ERROR_UNSPECIFIED, &reply); + } + + return true; + } + } + return false; + }; + pending_requests_.push_back(request); + + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_GROUP_LEAVE) + << group.c_str() << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::send_event(std::unique_ptr e) +{ + switch (eventmode_){ + case AOO_EVENT_POLL: + events_.push(std::move(e)); + break; + case AOO_EVENT_CALLBACK: + // client only has network threads + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_NETWORK); + break; + default: + break; + } +} + +void client_imp::push_command(std::unique_ptr&& cmd){ + commands_.push(std::move(cmd)); + + signal(); +} + +bool client_imp::wait_for_event(float timeout){ + // LOG_DEBUG("aoo_client: wait " << timeout << " seconds"); + + struct pollfd fds[2]; + fds[0].fd = eventsocket_; + fds[0].events = POLLIN; + fds[0].revents = 0; + fds[1].fd = socket_; + fds[1].events = POLLIN; + fds[1].revents = 0; + + // round up to 1 ms! -1: block indefinitely + // NOTE: macOS requires the negative timeout to exactly -1! +#ifdef _WIN32 + int result = WSAPoll(fds, 2, timeout < 0 ? -1 : timeout * 1000.0 + 0.5); +#else + int result = poll(fds, 2, timeout < 0 ? -1 : timeout * 1000.0 + 0.5); +#endif + if (result < 0){ + int err = socket_errno(); + if (err == EINTR){ + return true; // ? + } else { + LOG_ERROR("aoo_client: poll failed (" << err << ")"); + socket_error_print("poll"); + return false; + } + } + + // event socket + if (fds[0].revents){ + // read empty packet + char buf[64]; + recv(eventsocket_, buf, sizeof(buf), 0); + // LOG_DEBUG("aoo_client: got signalled"); + } + + // tcp socket + if (socket_ >= 0 && fds[1].revents){ + receive_data(); + } + + return true; +} + +void client_imp::receive_data(){ + char buffer[AOO_MAXPACKETSIZE]; + auto result = recv(socket_, buffer, sizeof(buffer), 0); + if (result > 0){ + recvbuffer_.write_bytes((uint8_t *)buffer, result); + + // handle packets + uint8_t buf[AOO_MAXPACKETSIZE]; + while (true){ + auto size = recvbuffer_.read_packet(buf, sizeof(buf)); + if (size > 0){ + try { + osc::ReceivedPacket packet((const char *)buf, size); + if (packet.IsBundle()){ + osc::ReceivedBundle bundle(packet); + handle_server_bundle(bundle); + } else { + handle_server_message(packet.Contents(), packet.Size()); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception in receive_data: " << e.what()); + on_exception("server TCP message", e); + } + } else { + break; + } + } + } else if (result == 0){ + // connection closed by the remote server + on_socket_error(0); + } else { + int err = socket_errno(); + LOG_ERROR("aoo_client: recv() failed (" << err << ")"); + on_socket_error(err); + } +} + +void client_imp::send_server_message(const char *data, int32_t size){ + if (sendbuffer_.write_packet((const uint8_t *)data, size)){ + while (sendbuffer_.read_available()){ + uint8_t buf[1024]; + int32_t total = sendbuffer_.read_bytes(buf, sizeof(buf)); + + int32_t nbytes = 0; + while (nbytes < total){ + auto res = ::send(socket_, (char *)buf + nbytes, total - nbytes, 0); + if (res >= 0){ + nbytes += res; + #if 0 + LOG_DEBUG("aoo_client: sent " << res << " bytes"); + #endif + } else { + auto err = socket_errno(); + + LOG_ERROR("aoo_client: send() failed (" << err << ")"); + on_socket_error(err); + return; + } + } + } + LOG_DEBUG("aoo_client: sent " << data << " to server"); + } else { + LOG_ERROR("aoo_client: couldn't send " << data << " to server"); + } +} + +void client_imp::send_peer_message(const char *data, int32_t size, + const ip_address& addr) { + // /aoo/relay + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + // send unmapped IP address in case peer is IPv4 only! + msg << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << addr.name_unmapped() << addr.port() << osc::Blob(data, size) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size()); +} + +void client_imp::handle_server_message(const char *data, int32_t n){ + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, n, type, onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_client: not an AOO NET message!"); + return; + } + + try { + if (type == AOO_TYPE_CLIENT){ + // now compare subpattern + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_client: got message " << pattern << " from server"); + + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got TCP ping from server"); + } else if (!strcmp(pattern, AOO_NET_MSG_PEER_JOIN)){ + handle_peer_add(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_PEER_LEAVE)){ + handle_peer_remove(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_LOGIN)){ + handle_login(msg); + } else { + // handle reply + for (auto it = pending_requests_.begin(); it != pending_requests_.end();){ + if ((*it)(pattern, msg)){ + it = pending_requests_.erase(it); + return; + } else { + ++it; + } + } + LOG_ERROR("aoo_client: couldn't handle reply " << pattern); + } + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg); + } else { + LOG_WARNING("aoo_client: got unsupported message " << msg.AddressPattern()); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception on handling " << msg.AddressPattern() + << " message: " << e.what()); + on_exception("server TCP message", e, msg.AddressPattern()); + } +} + +void client_imp::handle_server_bundle(const osc::ReceivedBundle &bundle){ + auto it = bundle.ElementsBegin(); + while (it != bundle.ElementsEnd()){ + if (it->IsBundle()){ + osc::ReceivedBundle b(*it); + handle_server_bundle(b); + } else { + handle_server_message(it->Contents(), it->Size()); + } + ++it; + } +} + +void client_imp::handle_login(const osc::ReceivedMessage& msg){ + // make sure that state hasn't changed + if (state_.load() == client_state::login){ + auto it = msg.ArgumentsBegin(); + int32_t status = (it++)->AsInt32(); + + if (status > 0){ + int32_t id = (it++)->AsInt32(); + // for backwards compatibility (LATER remove check) + uint32_t flags = (it != msg.ArgumentsEnd()) ? + (uint32_t)(it++)->AsInt32() : 0; + // connected! + state_.store(client_state::connected); + LOG_VERBOSE("aoo_client: successfully logged in (user ID: " + << id << " )"); + // notify + aoo_net_connect_reply reply; + reply.user_id = id; + reply.server_flags = flags; + server_flags_ = flags; + + callback_(userdata_, AOO_OK, &reply); + } else { + std::string errmsg; + if (msg.ArgumentCount() > 1){ + errmsg = (it++)->AsString(); + } else { + errmsg = "unknown error"; + } + LOG_WARNING("aoo_client: login failed: " << errmsg); + + // cache callback and userdata + auto cb = callback_; + auto ud = userdata_; + + close(); + + // notify + aoo_net_error_reply reply; + reply.error_code = 0; + reply.error_message = errmsg.c_str(); + + cb(ud, AOO_ERROR_UNSPECIFIED, &reply); + } + } +} + +static osc::ReceivedPacket unwrap_message(const osc::ReceivedMessage& msg, + ip_address& addr, ip_address::ip_type type) +{ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + + addr = ip_address(ip, port, type); + + const void *blobData; + osc::osc_bundle_element_size_t blobSize; + (it++)->AsBlob(blobData, blobSize); + + return osc::ReceivedPacket((const char *)blobData, blobSize); +} + +void client_imp::handle_relay_message(const osc::ReceivedMessage &msg){ + ip_address addr; + auto packet = unwrap_message(msg, addr, type()); + osc::ReceivedMessage relayMsg(packet); + + // for now, we only handle peer OSC messages + if (!strcmp(relayMsg.AddressPattern(), AOO_NET_MSG_PEER_MESSAGE)){ + // get embedded OSC message + const void *data; + osc::osc_bundle_element_size_t size; + relayMsg.ArgumentsBegin()->AsBlob(data, size); + + LOG_DEBUG("aoo_client: got relayed peer message " << (const char *)data + << " from " << addr.name() << ":" << addr.port()); + + auto e = std::make_unique( + (const char *)data, size, addr); + + send_event(std::move(e)); + } else { + LOG_WARNING("aoo_client: got unexpected relay message " << relayMsg.AddressPattern()); + } +} + +void client_imp::handle_peer_add(const osc::ReceivedMessage& msg){ + auto count = msg.ArgumentCount(); + auto it = msg.ArgumentsBegin(); + std::string group = (it++)->AsString(); + std::string user = (it++)->AsString(); + int32_t id = (it++)->AsInt32(); + count -= 3; + + ip_address_list addrlist; + while (count >= 2){ + std::string ip = (it++)->AsString(); + int32_t port = (it++)->AsInt32(); + ip_address addr(ip, port, type_); + if (addr.valid()){ + addrlist.push_back(addr); + } + count -= 2; + } + + peer_lock lock(peers_); + // check if peer already exists (shouldn't happen) + for (auto& p: peers_){ + if (p.match(group, id)){ + LOG_ERROR("aoo_client: peer " << p << " already added"); + return; + } + } + peers_.emplace_front(*this, id, group, user, std::move(addrlist)); + + auto e = std::make_unique( + AOO_NET_PEER_HANDSHAKE_EVENT, + group.c_str(), user.c_str(), id); + send_event(std::move(e)); + + LOG_VERBOSE("aoo_client: new peer " << peers_.front()); +} + +void client_imp::handle_peer_remove(const osc::ReceivedMessage& msg){ + auto it = msg.ArgumentsBegin(); + std::string group = (it++)->AsString(); + std::string user = (it++)->AsString(); + int32_t id = (it++)->AsInt32(); + + peer_lock lock(peers_); + auto result = std::find_if(peers_.begin(), peers_.end(), + [&](auto& p){ return p.match(group, id); }); + if (result == peers_.end()){ + LOG_ERROR("aoo_client: couldn't remove " << group << "|" << user); + return; + } + + // only send event if we're connected, which means + // that an AOO_NET_PEER_JOIN_EVENT has been sent. + if (result->connected()){ + ip_address addr = result->address(); + uint32_t flags = result->flags(); + + auto e = std::make_unique( + AOO_NET_PEER_LEAVE_EVENT, addr, + group.c_str(), user.c_str(), id, flags); + send_event(std::move(e)); + } + + peers_.erase(result); + + LOG_VERBOSE("aoo_client: peer " << group << "|" << user << " left"); +} + +bool client_imp::signal(){ + // LOG_DEBUG("aoo_client signal"); + return socket_signal(eventsocket_); +} + +void client_imp::close(bool manual){ + if (socket_ >= 0){ + socket_close(socket_); + socket_ = -1; + LOG_VERBOSE("aoo_client: connection closed"); + } + + username_.clear(); + password_.clear(); + callback_ = nullptr; + userdata_ = nullptr; + + sendbuffer_.reset(); + recvbuffer_.reset(); + + // remove all peers + peer_lock lock(peers_); + peers_.clear(); + + // clear pending request + // LATER call them all with some dummy input + // to avoid memleak if clients pass heap + // allocated request data, which is supposed + // to be freed in the callback. + pending_requests_.clear(); + + if (!manual && state_.load() == client_state::connected){ + auto e = std::make_unique(AOO_NET_DISCONNECT_EVENT); + send_event(std::move(e)); + } + state_.store(client_state::disconnected); +} + +void client_imp::on_socket_error(int err){ + char msg[256]; + if (err != 0) { + socket_strerror(err, msg, sizeof(msg)); + } else { + snprintf(msg, sizeof(msg), "connection closed by server"); + } + auto e = std::make_unique(err, msg); + + send_event(std::move(e)); + + close(); +} + +void client_imp::on_exception(const char *what, const osc::Exception &err, + const char *pattern){ + char msg[256]; + if (pattern){ + snprintf(msg, sizeof(msg), "exception in %s (%s): %s", + what, pattern, err.what()); + } else { + snprintf(msg, sizeof(msg), "exception in %s: %s", + what, err.what()); + } + + auto e = std::make_unique(0, msg); + + send_event(std::move(e)); + + close(); +} + +/*///////////////////// events ////////////////////////*/ + +client_imp::error_event::error_event(int32_t code, const char *msg) +{ + error_event_.type = AOO_NET_ERROR_EVENT; + error_event_.error_code = code; + error_event_.error_message = aoo::copy_string(msg); +} + +client_imp::error_event::~error_event() +{ + free_string((char *)error_event_.error_message); +} + +client_imp::ping_event::ping_event(int32_t type, const ip_address& addr, + uint64_t tt1, uint64_t tt2, uint64_t tt3) +{ + ping_event_.type = type; + ping_event_.address = copy_sockaddr(addr.address(), addr.length()); + ping_event_.addrlen = addr.length(); + ping_event_.tt1 = tt1; + ping_event_.tt2 = tt2; + ping_event_.tt3 = tt3; +} + +client_imp::ping_event::~ping_event() +{ + free_sockaddr((void *)ping_event_.address, ping_event_.addrlen); +} + +client_imp::peer_event::peer_event(int32_t type, const ip_address& addr, + const char *group, const char *user, + int32_t id, uint32_t flags) +{ + peer_event_.type = type; + peer_event_.address = copy_sockaddr(addr.address(), addr.length()); + peer_event_.addrlen = addr.length(); + peer_event_.group_name = aoo::copy_string(group); + peer_event_.user_name = aoo::copy_string(user); + peer_event_.user_id = id; + peer_event_.flags = flags; +} + +client_imp::peer_event::peer_event(int32_t type, const char *group, + const char *user, int32_t id) +{ + peer_event_.type = type; + peer_event_.address = nullptr; + peer_event_.addrlen = 0; + peer_event_.group_name = aoo::copy_string(group); + peer_event_.user_name = aoo::copy_string(user); + peer_event_.user_id = id; + peer_event_.flags = 0; +} + + +client_imp::peer_event::~peer_event() +{ + free_string((char *)peer_event_.user_name); + free_string((char *)peer_event_.group_name); + free_sockaddr((void *)peer_event_.address, peer_event_.addrlen); +} + +client_imp::message_event::message_event(const char *data, int32_t size, + const ip_address& addr) +{ + message_event_.type = AOO_NET_PEER_MESSAGE_EVENT; + message_event_.address = copy_sockaddr(addr.address(), addr.length()); + message_event_.addrlen = addr.length(); + auto msg = (char *)aoo::allocate(size); + memcpy(msg, data, size); + message_event_.data = msg; + message_event_.size = size; +} + +client_imp::message_event::~message_event() +{ + aoo::deallocate((char *)message_event_.data, message_event_.size); + free_sockaddr((void *)message_event_.address, message_event_.addrlen); +} + +/*///////////////////// udp_client ////////////////////*/ + +void udp_client::update(const sendfn& reply, time_tag now){ + auto elapsed_time = client_->elapsed_time_since(now); + auto delta = elapsed_time - last_ping_time_; + + auto state = client_->current_state(); + + if (state == client_state::handshake){ + // check for time out + // "first_ping_time_" is guaranteed to be set to 0 + // before the state changes to "handshake" + auto start = first_ping_time_.load(); + if (start == 0){ + first_ping_time_.store(elapsed_time); + } else if ((elapsed_time - start) > client_->request_timeout()){ + // handshake has timed out! + auto cmd = std::make_unique(); + + client_->push_command(std::move(cmd)); + + return; + } + // send handshakes in fast succession + if (delta >= client_->request_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_REQUEST) << osc::EndMessage; + + scoped_shared_lock lock(mutex_); + for (auto& addr : server_addrlist_){ + reply(msg.Data(), msg.Size(), addr, 0); + } + last_ping_time_ = elapsed_time; + } + } else if (state == client_state::connected){ + // send regular pings + if (delta >= client_->ping_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_SERVER_PING) + << osc::EndMessage; + + send_server_message(msg.Data(), msg.Size(), reply); + last_ping_time_ = elapsed_time; + } + } +} + +aoo_error udp_client::handle_message(const char *data, int32_t n, + const ip_address &addr, + aoo_type type, int32_t onset){ + try { + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + LOG_DEBUG("aoo_client: handle UDP message " << msg.AddressPattern() + << " from " << addr.name() << ":" << addr.port()); + + if (type == AOO_TYPE_PEER){ + // peer message + // + // NOTE: during the handshake process it is expected that + // we receive UDP messages which we have to ignore: + // a) pings from a peer which we haven't had the chance to add yet + // b) pings sent to alternative endpoint addresses + if (!client_->handle_peer_message(msg, onset, addr)){ + LOG_VERBOSE("aoo_client: ignoring UDP message " + << msg.AddressPattern() << " from endpoint " + << addr.name() << ":" << addr.port()); + } + } else if (type == AOO_TYPE_CLIENT){ + // server message + if (is_server_address(addr)){ + handle_server_message(msg, onset); + } else { + LOG_WARNING("aoo_client: got message from unknown server " << addr.name()); + } + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg); + } else { + LOG_WARNING("aoo_client: got unexpected message " << msg.AddressPattern()); + return AOO_ERROR_UNSPECIFIED; + } + + return AOO_OK; + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception in handle_message: " << e.what()); + #if 0 + on_exception("UDP message", e); + #endif + return AOO_ERROR_UNSPECIFIED; + } +} + +void udp_client::handle_relay_message(const osc::ReceivedMessage &msg){ + ip_address addr; + auto packet = unwrap_message(msg, addr, client_->type()); +#if DEBUG_RELAY + LOG_DEBUG("aoo_client: got relayed message " << packet.Contents()); +#endif + + client_->handle_message(packet.Contents(), packet.Size(), + addr.address(), addr.length()); +} + +void udp_client::send_peer_message(const char *data, int32_t size, + const ip_address& addr, + const sendfn& fn, bool relay) +{ + if (relay){ + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + // send unmapped address in case the peer is IPv4 only! + msg << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << addr.name_unmapped() << addr.port() << osc::Blob(data, size) + << osc::EndMessage; + send_server_message(msg.Data(), msg.Size(), fn); + } else { + fn(data, size, addr, 0); + } +} + +void udp_client::start_handshake(const ip_address& local, + ip_address_list&& remote) +{ + scoped_lock lock(mutex_); // to be really safe + first_ping_time_ = 0; + local_address_ = local; + public_addrlist_.clear(); + server_addrlist_ = std::move(remote); +} + +void udp_client::send_server_message(const char *data, int32_t size, const sendfn& fn) +{ + scoped_shared_lock lock(mutex_); + if (!server_addrlist_.empty()){ + auto& remote = server_addrlist_.front(); + if (remote.valid()){ + fn(data, size, remote, 0); + } + } +} + +void udp_client::handle_server_message(const osc::ReceivedMessage& msg, int onset){ + auto pattern = msg.AddressPattern() + onset; + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got UDP ping from server"); + } else if (!strcmp(pattern, AOO_NET_MSG_REPLY)){ + if (client_->current_state() == client_state::handshake){ + // retrieve public IP + port + auto it = msg.ArgumentsBegin(); + std::string ip = (it++)->AsString(); + int port = (it++)->AsInt32(); + + ip_address addr(ip, port, client_->type()); + + scoped_lock lock(mutex_); + for (auto& a : public_addrlist_){ + if (a == addr){ + LOG_DEBUG("aoo_client: public address " << addr.name() + << " already received"); + return; // already received + } + } + public_addrlist_.push_back(addr); + LOG_VERBOSE("aoo_client: got public address " + << addr.name() << " " << addr.port()); + + // check if we got all public addresses + // LATER improve this + if (public_addrlist_.size() == server_addrlist_.size()){ + // now we can try to login + ip_address_list addrlist; + addrlist.reserve(public_addrlist_.size() + 1); + + // local address first (for backwards compatibility with older versions) + addrlist.push_back(local_address_); + addrlist.insert(addrlist.end(), + public_addrlist_.begin(), public_addrlist_.end()); + + auto cmd = std::make_unique(std::move(addrlist)); + + client_->push_command(std::move(cmd)); + } + } + } else { + LOG_WARNING("aoo_client: received unexpected UDP message " + << pattern << " from server"); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: exception on handling " << pattern + << " message: " << e.what()); + #if 0 + on_exception("server UDP message", e, pattern); + #endif + } +} + +bool udp_client::is_server_address(const ip_address& addr){ + // server message + scoped_shared_lock lock(mutex_); + for (auto& remote : server_addrlist_){ + if (remote == addr){ + return true; + } + } + return false; +} + +/*///////////////////// peer //////////////////////////*/ + +peer::peer(client_imp& client, int32_t id, const std::string& group, + const std::string& user, ip_address_list&& addrlist) + : client_(&client), id_(id), group_(group), user_(user), + addresses_(std::move(addrlist)) +{ + start_time_ = time_tag::now(); + + LOG_DEBUG("create peer " << *this); +} + +peer::~peer(){ + LOG_DEBUG("destroy peer " << *this); +} + +bool peer::match(const ip_address& addr, bool unconnected) const { + if (connected()){ + return real_address_ == addr; + } else { + return unconnected; + } +} + +bool peer::match(const std::string& group) const { + return group_ == group; // immutable! +} + +bool peer::match(const std::string& group, const std::string& user) const { + return group_ == group && user_ == user; // immutable! +} + +bool peer::match(const std::string& group, int32_t id) +{ + return id_ == id && group_ == group; // immutable! +} + +std::ostream& operator << (std::ostream& os, const peer& p) +{ + os << p.group_ << "|" << p.user_; + return os; +} + +void peer::send(const sendfn& reply, time_tag now){ + auto elapsed_time = time_tag::duration(start_time_, now); + auto delta = elapsed_time - last_pingtime_; + + if (connected()){ + // send regular ping + if (delta >= client_->ping_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_PING) << osc::EndMessage; + + client_->udp().send_peer_message(msg.Data(), msg.Size(), + real_address_, reply, relay()); + + last_pingtime_ = elapsed_time; + } + // reply to /ping message + if (got_ping_.exchange(false)){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_PEER_REPLY) << osc::EndMessage; + + client_->udp().send_peer_message(msg.Data(), msg.Size(), + real_address_, reply, relay()); + } + } else if (!timeout_) { + // try to establish UDP connection with peer + if (elapsed_time > client_->request_timeout()){ + // time out + bool can_relay = client_->have_server_flag(AOO_NET_SERVER_RELAY); + if (can_relay && !relay()){ + LOG_VERBOSE("aoo_client: try to relay " << *this); + // try to relay traffic over server + start_time_ = now; // reset timer + last_pingtime_ = 0; + flags_ |= AOO_ENDPOINT_RELAY; + return; + } + + // couldn't establish relay connection! + const char *what = relay() ? "relay" : "peer-to-peer"; + LOG_ERROR("aoo_client: couldn't establish UDP " << what + << " connection to " << *this << "; timed out after " + << client_->request_timeout() << " seconds"); + + std::stringstream ss; + ss << "couldn't establish connection with peer " << *this; + + auto e1 = std::make_unique(0, ss.str().c_str()); + client_->send_event(std::move(e1)); + + auto e2 = std::make_unique( + AOO_NET_PEER_TIMEOUT_EVENT, + group().c_str(), user().c_str(), id()); + client_->send_event(std::move(e2)); + + timeout_ = true; + + return; + } + // send handshakes in fast succession to all addresses + // until we get a reply from one of them (see handle_message()) + if (delta >= client_->request_interval()){ + char buf[64]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + // Include user ID, so peers can identify us even + // if we're behind a symmetric NAT. + // NOTE: This trick doesn't work if both parties are + // behind a symmetrict NAT; in that case, UDP hole punching + // simply doesn't work. + msg << osc::BeginMessage(AOO_NET_MSG_PEER_PING) + << (int32_t)id_ << osc::EndMessage; + + for (auto& addr : addresses_){ + client_->udp().send_peer_message(msg.Data(), msg.Size(), + addr, reply, relay()); + } + + // LOG_DEBUG("send ping to " << *this); + + last_pingtime_ = elapsed_time; + } + } +} + +bool peer::handle_first_message(const osc::ReceivedMessage &msg, int onset, + const ip_address &addr) +{ +#if FORCE_RELAY + // force relay + if (!relay()){ + return false; + } +#endif + // Try to find matching address + for (auto& a : addresses_){ + if (a == addr){ + real_address_ = addr; + connected_.store(true, std::memory_order_release); + return true; + } + } + + // We might get a message from a peer behind a symmetric NAT. + // To be sure, check user ID, but only if provided + // (for backwards compatibility with older AOO clients) + auto pattern = msg.AddressPattern() + onset; + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + if (msg.ArgumentCount() > 0){ + try { + auto it = msg.ArgumentsBegin(); + int32_t id = it->AsInt32(); + if (id == id_){ + real_address_ = addr; + connected_.store(true); + LOG_WARNING("aoo_client: peer " << *this + << " is located behind a symmetric NAT!"); + return true; + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_client: got bad " << pattern + << " message from peer: " << e.what()); + } + } else { + // ignore silently! + } + } else { + LOG_ERROR("aoo_client: got " << pattern + << " message from unknown peer"); + } + return false; +} + +void peer::handle_message(const osc::ReceivedMessage &msg, int onset, + const ip_address& addr) +{ + if (!connected()){ + if (!handle_first_message(msg, onset, addr)){ + return; + } + + // push event + auto e = std::make_unique( + AOO_NET_PEER_JOIN_EVENT, addr, + group().c_str(), user().c_str(), id(), flags()); + + client_->send_event(std::move(e)); + + LOG_VERBOSE("aoo_client: successfully established connection with " + << *this << " [" << addr.name() << "]:" << addr.port() + << (relay() ? " (relayed)" : "")); + } + + auto pattern = msg.AddressPattern() + onset; + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + LOG_DEBUG("aoo_client: got ping from " << *this); + got_ping_.store(true); + } else if (!strcmp(pattern, AOO_NET_MSG_REPLY)){ + LOG_DEBUG("aoo_client: got reply from " << *this); + } else if (!strcmp(pattern, AOO_NET_MSG_MESSAGE)){ + // get embedded OSC message + const void *data; + osc::osc_bundle_element_size_t size; + msg.ArgumentsBegin()->AsBlob(data, size); + + LOG_DEBUG("aoo_client: got message " << (const char *)data + << " from " << addr.name() << ":" << addr.port()); + + auto e = std::make_unique( + (const char *)data, size, addr); + + client_->send_event(std::move(e)); + } else { + LOG_WARNING("aoo_client: received unknown message " + << pattern << " from " << *this); + } + } catch (const osc::Exception& e){ + // peer exceptions are not fatal! + LOG_ERROR("aoo_client: " << *this << ": exception on handling " + << pattern << " message: " << e.what()); + } +} + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/client.hpp b/deps/aoo/aoo/src/net/client.hpp new file mode 100644 index 00000000..553601a0 --- /dev/null +++ b/deps/aoo/aoo/src/net/client.hpp @@ -0,0 +1,549 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#pragma once + +#include "aoo/aoo_net.hpp" +#include "aoo/aoo.hpp" + +#include "common/utils.hpp" +#include "common/sync.hpp" +#include "common/time.hpp" +#include "common/lockfree.hpp" +#include "common/net_utils.hpp" + +#include "../imp.hpp" +#include "commands.hpp" +#include "SLIP.hpp" + +#include "oscpack/osc/OscOutboundPacketStream.h" +#include "oscpack/osc/OscReceivedElements.h" + +#ifdef _WIN32 +#include +#endif + +#include +#include + +#ifndef AOO_NET_CLIENT_PING_INTERVAL + #define AOO_NET_CLIENT_PING_INTERVAL 5000 +#endif + +#ifndef AOO_NET_CLIENT_REQUEST_INTERVAL + #define AOO_NET_CLIENT_REQUEST_INTERVAL 100 +#endif + +#ifndef AOO_NET_CLIENT_REQUEST_TIMEOUT + #define AOO_NET_CLIENT_REQUEST_TIMEOUT 5000 +#endif + +namespace aoo { +namespace net { + +class client_imp; + +#if 0 +using ip_address_list = std::vector>; +#else +using ip_address_list = std::vector; +#endif + + +/*/////////////////////////// peer /////////////////////////*/ +class peer { +public: + peer(client_imp& client, int32_t id, const std::string& group, + const std::string& user, ip_address_list&& addrlist); + + ~peer(); + + bool connected() const { + return connected_.load(std::memory_order_acquire); + } + + bool match(const ip_address& addr, bool unconnected = false) const; + + bool match(const std::string& group) const; + + bool match(const std::string& group, const std::string& user) const; + + bool match(const std::string& group, int32_t id); + + int32_t id() const { return id_; } + + uint32_t flags() const { return flags_; } + + bool relay() const { return flags_ & AOO_ENDPOINT_RELAY; } + + const std::string& group() const { return group_; } + + const std::string& user() const { return user_; } + + const ip_address& address() const { + return real_address_; + } + + void send(const sendfn& reply, time_tag now); + + void handle_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); + + friend std::ostream& operator << (std::ostream& os, const peer& p); +private: + client_imp *client_; + const int32_t id_; + uint32_t flags_ = 0; + const std::string group_; + const std::string user_; + ip_address_list addresses_; + ip_address real_address_; + time_tag start_time_; + double last_pingtime_ = 0; + std::atomic connected_{false}; + std::atomic got_ping_{false}; + bool timeout_ = false; + + bool handle_first_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); +}; + +/*///////////////////////// udp_client ///////////////////////////*/ + +class udp_client { +public: + udp_client(client_imp& c, int port, uint32_t flags) + : client_(&c), port_(port) {} + + int port() const { return port_; } + + aoo_error handle_message(const char *data, int32_t n, + const ip_address& addr, + int32_t type, aoo_type onset); + + void handle_relay_message(const osc::ReceivedMessage& msg); + + void update(const sendfn& reply, time_tag now); + + void send_server_message(const char *data, int32_t size, const sendfn& fn); + + void send_peer_message(const char *data, int32_t size, + const ip_address& addr, const sendfn& fn, bool relay); + + void start_handshake(const ip_address& local, ip_address_list&& remote); +private: + using scoped_lock = sync::scoped_lock; + using scoped_shared_lock = sync::scoped_shared_lock; + + client_imp *client_; + int port_; + ip_address local_address_; + ip_address_list server_addrlist_; + ip_address_list public_addrlist_; + sync::shared_mutex mutex_; + + double last_ping_time_ = 0; + std::atomic first_ping_time_{0}; + + void send_ping(); + + void handle_server_message(const osc::ReceivedMessage& msg, int onset); + + bool is_server_address(const ip_address& addr); +}; + +/*/////////////////////////////// client /////////////////////////////*/ + +enum class client_state { + disconnected, + connecting, + handshake, + login, + connected +}; + +class client_imp final : public client +{ +public: + struct icommand { + virtual ~icommand(){} + virtual void perform(client_imp&) = 0; + }; + + struct imessage { + virtual ~imessage(){} + virtual void perform(client_imp&, const sendfn& fn) = 0; + }; + + struct ievent { + virtual ~ievent(){} + + union { + aoo_event event_; + aoo_net_error_event error_event_; + aoo_net_ping_event ping_event_; + aoo_net_peer_event peer_event_; + aoo_net_message_event message_event_; + }; + }; + + client_imp(const void *address, int32_t addrlen, uint32_t flags); + + ~client_imp(); + + aoo_error run() override; + + aoo_error quit() override; + + aoo_error add_source(source *src, aoo_id id) override; + + aoo_error remove_source(source *src) override; + + aoo_error add_sink(sink *sink, aoo_id id) override; + + aoo_error remove_sink(sink *sink) override; + + aoo_error get_peer_address(const char *group, const char *user, + void *address, int32_t *addrlen, uint32_t *flags) override; + + aoo_error get_peer_info(const void *address, int32_t addrlen, + aoo_net_peer_info *info) override; + + aoo_error send_request(aoo_net_request_type request, void *data, + aoo_net_callback callback, void *user) override; + + aoo_error send_message(const char *data, int32_t size, + const void *addr, int32_t len, int32_t flags) override; + + template + void perform_send_message(const char *data, int32_t size, int32_t flags, + const sendfn& fn, T&& filter); + + aoo_error handle_message(const char *data, int32_t n, + const void *addr, int32_t len) override; + + bool handle_peer_message(const osc::ReceivedMessage& msg, int onset, + const ip_address& addr); + + aoo_error send(aoo_sendfn fn, void *user) override; + + aoo_error set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) override; + + aoo_bool events_available() override; + + aoo_error poll_events() override; + + aoo_error control(int32_t ctl, intptr_t index, void *ptr, size_t size) override; + + void do_connect(const char *host, int port, + const char *name, const char *pwd, + aoo_net_callback cb, void *user); + + void perform_connect(const std::string& host, int port, + const std::string& name, const std::string& pwd, + aoo_net_callback cb, void *user); + + int try_connect(const std::string& host, int port); + + void perform_login(const ip_address_list& addrlist); + + void perform_timeout(); + + void do_disconnect(aoo_net_callback cb, void *user); + + void perform_disconnect(aoo_net_callback cb, void *user); + + void do_join_group(const char *name, const char *pwd, + aoo_net_callback cb, void *user); + + void perform_join_group(const std::string& group, const std::string& pwd, + aoo_net_callback cb, void *user); + + void do_leave_group(const char *name, aoo_net_callback cb, void *user); + + void perform_leave_group(const std::string& group, + aoo_net_callback cb, void *user); + + double ping_interval() const { return ping_interval_.load(std::memory_order_relaxed); } + + double request_interval() const { return request_interval_.load(std::memory_order_relaxed); } + + double request_timeout() const { return request_timeout_.load(std::memory_order_relaxed); } + + void send_event(std::unique_ptr e); + + void push_command(std::unique_ptr&& cmd); + + udp_client& udp() { return *udp_client_; } + + ip_address::ip_type type() const { return type_; } + + double elapsed_time_since(time_tag now) const { + return time_tag::duration(start_time_, now); + } + + client_state current_state() const { return state_.load(); } + + bool have_server_flag(uint32_t flag) const { + return flag & server_flags_; + } +private: + std::unique_ptr udp_client_; + int socket_ = -1; + ip_address::ip_type type_ = ip_address::Unspec; + // dependants + struct source_desc { + aoo::source *source; + aoo_id id; + }; + std::vector> sources_; + struct sink_desc { + aoo::sink *sink; + aoo_id id; + }; + std::vector> sinks_; + // SLIP buffers + SLIP> sendbuffer_; + SLIP> recvbuffer_; + // event + std::atomic quit_{false}; + int eventsocket_ = -1; + // peers + using peer_list = lockfree::simple_list>; + using peer_lock = std::unique_lock; + peer_list peers_; + // time + time_tag start_time_; + double last_ping_time_ = 0; + // handshake + std::atomic state_{client_state::disconnected}; + std::string username_; + std::string password_; + uint32_t server_flags_ = 0; + aoo_net_callback callback_ = nullptr; + void *userdata_ = nullptr; + // commands + using icommand_ptr = std::unique_ptr; + using command_queue = lockfree::unbounded_mpsc_queue>; + command_queue commands_; + // peer/group messages + using imessage_ptr = std::unique_ptr; + using message_queue = lockfree::unbounded_mpsc_queue>; + message_queue udp_messages_; + message_queue tcp_messages_; + // pending request + using request = std::function; + std::vector> pending_requests_; + // events + using ievent_ptr = std::unique_ptr; + using event_queue = lockfree::unbounded_mpsc_queue>; + event_queue events_; + aoo_eventhandler eventhandler_ = nullptr; + void *eventcontext_ = nullptr; + aoo_event_mode eventmode_ = AOO_EVENT_NONE; + // options + std::atomic ping_interval_{AOO_NET_CLIENT_PING_INTERVAL * 0.001}; + std::atomic request_interval_{AOO_NET_CLIENT_REQUEST_INTERVAL * 0.001}; + std::atomic request_timeout_{AOO_NET_CLIENT_REQUEST_TIMEOUT * 0.001}; + + // methods + bool wait_for_event(float timeout); + + void receive_data(); + + bool signal(); + + void send_server_message(const char *data, int32_t size); + + void send_peer_message(const char *data, int32_t size, + const ip_address& addr); + + void handle_server_bundle(const osc::ReceivedBundle& bundle); + + void handle_server_message(const char *data, int32_t n); + + void handle_login(const osc::ReceivedMessage& msg); + + void handle_relay_message(const osc::ReceivedMessage& msg); + + void handle_peer_add(const osc::ReceivedMessage& msg); + + void handle_peer_remove(const osc::ReceivedMessage& msg); + + void on_socket_error(int err); + + void on_exception(const char *what, const osc::Exception& err, + const char *pattern = nullptr); + + void close(bool manual = false); + + /*////////////////////// events /////////////////////*/ +public: + struct event : ievent + { + event(int32_t type){ + event_.type = type; + } + }; + + struct error_event : ievent + { + error_event(int32_t code, const char *msg); + ~error_event(); + }; + + struct ping_event : ievent + { + ping_event(int32_t type, const ip_address& addr, + uint64_t tt1, uint64_t tt2, uint64_t tt3); + ~ping_event(); + }; + + struct peer_event : ievent + { + peer_event(int32_t type, const ip_address& addr, + const char *group, const char *user, + int32_t id, uint32_t flags); + peer_event(int32_t type, const char *group, + const char *user, int32_t id); + ~peer_event(); + }; + + struct message_event : ievent + { + message_event(const char *data, int32_t size, + const ip_address& addr); + ~message_event(); + }; + + /*////////////////////// commands ///////////////////*/ + struct request_cmd : icommand + { + request_cmd(aoo_net_callback cb, void *user) + : cb_(cb), user_(user){} + protected: + aoo_net_callback cb_; + void *user_; + }; + + struct connect_cmd : request_cmd + { + connect_cmd(aoo_net_callback cb, void *user, + const std::string &host, int port, + const std::string& name, const std::string& pwd) + : request_cmd(cb, user), host_(host), port_(port), + name_(name), pwd_(pwd) {} + + void perform(client_imp& obj) override { + obj.perform_connect(host_, port_, name_, pwd_, cb_, user_); + } + private: + std::string host_; + int port_; + std::string name_; + std::string pwd_; + }; + + struct disconnect_cmd : request_cmd + { + disconnect_cmd(aoo_net_callback cb, void *user) + : request_cmd(cb, user) {} + + void perform(client_imp& obj) override { + obj.perform_disconnect(cb_, user_); + } + }; + + struct login_cmd : icommand + { + login_cmd(ip_address_list&& addrlist) + : addrlist_(std::move(addrlist)) {} + + void perform(client_imp& obj) override { + obj.perform_login(addrlist_); + } + private: + ip_address_list addrlist_; + }; + + struct timeout_cmd : icommand + { + void perform(client_imp& obj) override { + obj.perform_timeout(); + } + }; + + struct group_join_cmd : request_cmd + { + group_join_cmd(aoo_net_callback cb, void *user, + const std::string& group, const std::string& pwd) + : request_cmd(cb, user), group_(group), password_(pwd){} + + void perform(client_imp& obj) override { + obj.perform_join_group(group_, password_, cb_, user_); + } + private: + std::string group_; + std::string password_; + }; + + struct group_leave_cmd : request_cmd + { + group_leave_cmd(aoo_net_callback cb, void *user, + const std::string& group) + : request_cmd(cb, user), group_(group){} + + void perform(client_imp& obj) override { + obj.perform_leave_group(group_, cb_, user_); + } + private: + std::string group_; + }; + + /*////////////////// messages ////////////////////*/ + + struct message : imessage { + message(const char *data, int32_t size, int32_t flags) + : flags_(flags) { + data_.assign(data, data + size); + } + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [](auto&){ return true; }); + } + protected: + std::vector> data_; + int32_t flags_; + }; + + struct peer_message : message { + peer_message(const char *data, int32_t size, + const sockaddr *addr, int32_t len, int32_t flags) + : message(data, size, flags), address_(addr, len) {} + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [&](auto& peer){ return peer.match(address_); }); + } + protected: + ip_address address_; + }; + + struct group_message : message { + group_message(const char *data, int32_t size, + const char *group, int32_t flags) + : message(data, size, flags), group_(group) {} + + void perform(client_imp& obj, const sendfn& fn) override { + obj.perform_send_message(data_.data(), data_.size(), + flags_, fn, [&](auto& peer){ return peer.match(group_); }); + } + protected: + std::string group_; + }; +}; + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/commands.hpp b/deps/aoo/aoo/src/net/commands.hpp new file mode 100644 index 00000000..26599e57 --- /dev/null +++ b/deps/aoo/aoo/src/net/commands.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "aoo/aoo_net.h" + +#define AOO_NET_MSG_PING "/ping" +#define AOO_NET_MSG_PING_LEN 5 + +#define AOO_NET_MSG_MESSAGE "/msg" +#define AOO_NET_MSG_MESSAGE_LEN 4 + +#define AOO_NET_MSG_LOGIN "/login" +#define AOO_NET_MSG_LOGIN_LEN 6 + +#define AOO_NET_MSG_REQUEST "/request" +#define AOO_NET_MSG_REQUEST_LEN 8 + +#define AOO_NET_MSG_REPLY "/reply" +#define AOO_NET_MSG_REPLY_LEN 6 + +#define AOO_NET_MSG_GROUP "/group" +#define AOO_NET_MSG_GROUP_LEN 6 + +#define AOO_NET_MSG_JOIN "/join" +#define AOO_NET_MSG_JOIN_LEN 5 + +#define AOO_NET_MSG_LEAVE "/leave" +#define AOO_NET_MSG_LEAVE_LEN 6 diff --git a/deps/aoo/aoo/src/net/server.cpp b/deps/aoo/aoo/src/net/server.cpp new file mode 100644 index 00000000..79be51c5 --- /dev/null +++ b/deps/aoo/aoo/src/net/server.cpp @@ -0,0 +1,1118 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "server.hpp" + +#include "aoo/aoo.h" + +#include +#include +#include + +#define AOO_NET_MSG_CLIENT_PING \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PING + +#define AOO_NET_MSG_CLIENT_LOGIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_LOGIN + +#define AOO_NET_MSG_CLIENT_REPLY \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_REPLY + +#define AOO_NET_MSG_CLIENT_GROUP_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_CLIENT_GROUP_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_CLIENT_PEER_JOIN \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PEER AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_CLIENT_PEER_LEAVE \ + AOO_MSG_DOMAIN AOO_NET_MSG_CLIENT AOO_NET_MSG_PEER AOO_NET_MSG_LEAVE + +#define AOO_NET_MSG_GROUP_JOIN \ + AOO_NET_MSG_GROUP AOO_NET_MSG_JOIN + +#define AOO_NET_MSG_GROUP_LEAVE \ + AOO_NET_MSG_GROUP AOO_NET_MSG_LEAVE + +/*//////////////////// AoO server /////////////////////*/ + +aoo_net_server * aoo_net_server_new(int port, uint32_t flags, aoo_error *err) { + // create UDP socket + int udpsocket = aoo::socket_udp(port); + if (udpsocket < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: couldn't create UDP socket (" << e << ")"); + return nullptr; + } + + // increase UDP receive buffer size to 16 MB + if (aoo::socket_setrecvbufsize(udpsocket, 1<<24) < 0){ + aoo::socket_error_print("setrecvbufsize"); + } + + // create TCP socket + int tcpsocket = aoo::socket_tcp(port); + if (tcpsocket < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: couldn't create TCP socket (" << e << ")"); + aoo::socket_close(udpsocket); + return nullptr; + } + + // listen + if (listen(tcpsocket, 32) < 0){ + auto e = aoo::socket_errno(); + *err = AOO_ERROR_UNSPECIFIED; + LOG_ERROR("aoo_server: listen() failed (" << e << ")"); + aoo::socket_close(tcpsocket); + aoo::socket_close(udpsocket); + return nullptr; + } + + return aoo::construct(tcpsocket, udpsocket); +} + +aoo::net::server_imp::server_imp(int tcpsocket, int udpsocket) + : tcpsocket_(tcpsocket), udpserver_(udpsocket) +{ + eventsocket_ = socket_udp(0); + type_ = socket_family(udpsocket); + // commands_.reserve(256); + // events_.reserve(256); +} + +void aoo_net_server_free(aoo_net_server *server){ + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(server)); +} + +aoo::net::server_imp::~server_imp() { + socket_close(tcpsocket_); + tcpsocket_ = -1; + + // clear explicitly to avoid crash! + clients_.clear(); +} + +aoo_error aoo_net_server_run(aoo_net_server *server){ + return server->run(); +} + +aoo_error aoo::net::server_imp::run(){ + // wait for networking or other events + while (!quit_.load()){ + if (!receive()){ + break; + } + } + + if (!notify_on_shutdown_.load()){ + // JC: need to close all the clients sockets without + // having them send anything out, so that active communication + // between connected peers can continue if the server goes down for maintainence + for (auto& client : clients_){ + client.close(false); + } + } + + return AOO_OK; +} + +aoo_error aoo_net_server_quit(aoo_net_server *server){ + return server->quit(); +} + +aoo_error aoo::net::server_imp::quit(){ + // set quit and wake up receive thread + quit_.store(true); + if (!socket_signal(eventsocket_)){ + // force wakeup by closing the socket. + // this is not nice and probably undefined behavior, + // the MSDN docs explicitly forbid it! + socket_close(eventsocket_); + } + return AOO_OK; +} + +aoo_error aoo_net_server_set_eventhandler(aoo_net_client *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::net::server_imp::set_eventhandler(aoo_eventhandler fn, void *user, + int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_net_server_events_available(aoo_net_server *server){ + return server->events_available(); +} + +aoo_bool aoo::net::server_imp::events_available(){ + return !events_.empty(); +} + +aoo_error aoo_net_server_poll_events(aoo_net_server *server){ + return server->poll_events(); +} + +aoo_error aoo::net::server_imp::poll_events(){ + // always thread-safe + std::unique_ptr e; + while (events_.try_pop(e)){ + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_UNKNOWN); + } + return AOO_OK; +} + +aoo_error aoo_net_server_ctl(aoo_net_server *server, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return server->control(ctl, index, p, size); +} + +aoo_error aoo::net::server_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + LOG_WARNING("aoo_server: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; +} + +namespace aoo { +namespace net { + +std::string server_imp::error_to_string(error e){ + switch (e){ + case server_imp::error::access_denied: + return "access denied"; + case server_imp::error::permission_denied: + return "permission denied"; + case server_imp::error::wrong_password: + return "wrong password"; + default: + return "unknown error"; + } +} + +std::shared_ptr server_imp::get_user(const std::string& name, + const std::string& pwd, + uint32_t version, error& e) +{ + auto usr = find_user(name); + if (usr){ + // check if someone is already logged in + if (usr->active()){ + e = error::access_denied; + return nullptr; + } + // check password for existing user + if (usr->password == pwd){ + e = error::none; + return usr; + } else { + e = error::wrong_password; + return nullptr; + } + } else { + // create new user (LATER add option to disallow this) + if (true){ + auto id = get_next_user_id(); + usr = std::make_shared(name, pwd, id, version); + users_.push_back(usr); + e = error::none; + return usr; + } else { + e = error::permission_denied; + return nullptr; + } + } +} + +std::shared_ptr server_imp::find_user(const std::string& name) +{ + for (auto& usr : users_){ + if (usr->name == name){ + return usr; + } + } + return nullptr; +} + +std::shared_ptr server_imp::get_group(const std::string& name, + const std::string& pwd, error& e) +{ + auto grp = find_group(name); + if (grp){ + // check password for existing group + if (grp->password == pwd){ + e = error::none; + return grp; + } else { + e = error::wrong_password; + return nullptr; + } + } else { + // create new group (LATER add option to disallow this) + if (true){ + grp = std::make_shared(name, pwd); + groups_.push_back(grp); + e = error::none; + return grp; + } else { + e = error::permission_denied; + return nullptr; + } + } +} + +std::shared_ptr server_imp::find_group(const std::string& name) +{ + for (auto& grp : groups_){ + if (grp->name == name){ + return grp; + } + } + return nullptr; +} + + +void server_imp::on_user_joined(user &usr){ + auto e = std::make_unique(AOO_NET_USER_JOIN_EVENT, + usr.name.c_str(), usr.id, + usr.endpoint()->local_address()); // do we need this? + send_event(std::move(e)); +} + +void server_imp::on_user_left(user &usr){ + auto e = std::make_unique(AOO_NET_USER_LEAVE_EVENT, + usr.name.c_str(), usr.id, + usr.endpoint()->local_address()); // do we need this? + send_event(std::move(e)); +} + +void server_imp::on_user_joined_group(user& usr, group& grp){ + // 1) send the new member to existing group members + // 2) send existing group members to the new member + for (auto& peer : grp.users()){ + if (peer->id != usr.id){ + char buf[AOO_MAXPACKETSIZE]; + + auto notify = [&](client_endpoint* dest, user& u){ + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_CLIENT_PEER_JOIN) + << grp.name.c_str() << u.name.c_str(); + // only v0.2-pre3 and abvoe + if (peer->version > 0){ + msg << u.id; + } + // send *unmapped* addresses in case the client is IPv4 only + for (auto& addr : u.endpoint()->public_addresses()){ + msg << addr.name_unmapped() << addr.port(); + } + msg << osc::EndMessage; + + dest->send_message(msg.Data(), msg.Size()); + }; + + // notify new member + notify(usr.endpoint(), *peer); + + // notify existing member + notify(peer->endpoint(), usr); + } + } + + auto e = std::make_unique(AOO_NET_GROUP_JOIN_EVENT, + grp.name.c_str(), + usr.name.c_str(), usr.id); + send_event(std::move(e)); +} + +void server_imp::on_user_left_group(user& usr, group& grp){ + if (tcpsocket_ < 0){ + return; // prevent sending messages during shutdown + } + // notify group members + for (auto& peer : grp.users()){ + if (peer->id != usr.id){ + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream msg(buf, sizeof(buf)); + msg << osc::BeginMessage(AOO_NET_MSG_CLIENT_PEER_LEAVE) + << grp.name.c_str() << usr.name.c_str() << usr.id + << osc::EndMessage; + + peer->endpoint()->send_message(msg.Data(), msg.Size()); + } + } + + auto e = std::make_unique(AOO_NET_GROUP_LEAVE_EVENT, + grp.name.c_str(), + usr.name.c_str(), usr.id); + send_event(std::move(e)); +} + +void server_imp::handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src){ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + ip_address dst(ip, port, type()); + + const void *msgData; + osc::osc_bundle_element_size_t msgSize; + (it++)->AsBlob(msgData, msgSize); + + // forward message to matching client + // send unmapped address in case the client is IPv4 only! + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream out(buf, sizeof(buf)); + out << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << src.name_unmapped() << src.port() << osc::Blob(msgData, msgSize) + << osc::EndMessage; + + for (auto& client : clients_){ + if (client.match(dst)) { + client.send_message(out.Data(), out.Size()); + return; + } + } + LOG_WARNING("aoo_server: couldn't find matching client for relay message"); +} + +void server_imp::send_event(std::unique_ptr e){ + switch (eventmode_){ + case AOO_EVENT_POLL: + events_.push(std::move(e)); + break; + case AOO_EVENT_CALLBACK: + // server only has network threads + eventhandler_(eventcontext_, &e->event_, AOO_THREAD_NETWORK); + break; + default: + break; + } +} + +int32_t server_imp::get_next_user_id(){ + // LATER make random user ID + return next_user_id_++; +} + +bool server_imp::receive(){ + bool didclose = false; + int numclients = clients_.size(); + + // initialize pollfd array + // allocate extra slots for main TCP socket and event socket + pollarray_.resize(numclients + 2); + + for (int i = 0; i < (int)pollarray_.size(); ++i){ + pollarray_[i].events = POLLIN; + pollarray_[i].revents = 0; + } + + int index = 0; + for (auto& c : clients_){ + pollarray_[index++].fd = c.socket(); + } + + auto& tcp_fd = pollarray_[numclients]; + tcp_fd.fd = tcpsocket_; + + auto& event_fd = pollarray_[numclients+1]; + event_fd.fd = eventsocket_; + + // NOTE: macOS/BSD requires the negative timeout to be exactly -1! +#ifdef _WIN32 + int result = WSAPoll(pollarray_.data(), pollarray_.size(), -1); +#else + int result = poll(pollarray_.data(), pollarray_.size(), -1); +#endif + if (result < 0){ + int err = errno; + if (err == EINTR){ + return true; // ? + } else { + LOG_ERROR("aoo_server: poll failed (" << err << ")"); + return false; + } + } + + if (event_fd.revents){ + return false; // quit + } + + if (tcp_fd.revents){ + // accept new client + ip_address addr; + auto sock = accept(tcpsocket_, addr.address_ptr(), addr.length_ptr()); + if (sock >= 0){ + clients_.emplace_back(*this, sock, addr); + LOG_VERBOSE("aoo_server: accepted client (IP: " + << addr.name() << ", port: " << addr.port() << ")"); + } else { + int err = socket_errno(); + LOG_ERROR("aoo_server: couldn't accept client (" << err << ")"); + } + } + + // use original number of clients! + index = 0; + for (auto it = clients_.begin(); index < numclients; ++index, ++it){ + if (pollarray_[index].revents){ + // receive data from client + if (!it->receive_data()){ + LOG_VERBOSE("aoo_server: close client"); + it->close(); + didclose = true; + } + } + } + + if (didclose){ + update(); + } + + return true; +} + +void server_imp::update(){ + clients_.remove_if([](auto& c){ return !c.active(); }); + // automatically purge stale users + // LATER add an option so that users will persist + for (auto it = users_.begin(); it != users_.end(); ){ + if (!(*it)->active()){ + it = users_.erase(it); + } else { + ++it; + } + } + // automatically purge empty groups + // LATER add an option so that groups will persist + for (auto it = groups_.begin(); it != groups_.end(); ){ + if ((*it)->num_users() == 0){ + it = groups_.erase(it); + } else { + ++it; + } + } +} + +uint32_t server_imp::flags() const { + uint32_t flags = 0; + if (allow_relay_.load(std::memory_order_relaxed)){ + flags |= AOO_NET_SERVER_RELAY; + } + return flags; +} + +/*////////////////////////// udp_server /////////////////////*/ + +udp_server::udp_server(int socket) { + socket_ = socket; + type_ = socket_family(socket); + receivethread_ = std::thread(&udp_server::receive_packets, this); + workerthread_ = std::thread(&udp_server::handle_packets, this); +} + +udp_server::~udp_server(){ + quit_ = true; + event_.set(); + socket_signal(socket_); + + if (receivethread_.joinable()){ + receivethread_.join(); + } + + if (workerthread_.joinable()){ + workerthread_.join(); + } + + socket_close(socket_); +} + +void udp_server::receive_packets(){ + while (!quit_.load(std::memory_order_relaxed)){ + ip_address addr; + char buf[AOO_MAXPACKETSIZE]; + int nbytes = socket_receive(socket_, buf, AOO_MAXPACKETSIZE, &addr, -1); + if (nbytes > 0){ + // add packet to queue + recvbuffer_.produce([&](udp_packet& p){ + p.address = addr; + p.data.assign(buf, buf + nbytes); + }); + #if DEBUG_THREADS + recvbufferfill_.fetch_add(1, std::memory_order_relaxed); + #endif + event_.set(); + } else if (nbytes < 0) { + // ignore errors when quitting + if (!quit_){ + socket_error_print("recv"); + } + break; + } + // ignore empty packets (used for quit signalling) + #if DEBUG_THREADS + std::cout << "receive_packets: waiting" << std::endl; + #endif + } +} + +void udp_server::handle_packets(){ + while (!quit_.load(std::memory_order_relaxed)){ + event_.wait(); + + while (!recvbuffer_.empty()){ + #if DEBUG_THREADS + std::cout << "perform_io: handle_message" << std::endl; + #endif + recvbuffer_.consume([&](const udp_packet& p){ + handle_packet(p.data.data(), p.data.size(), p.address); + }); + #if DEBUG_THREADS + auto fill = recvbufferfill_.fetch_sub(1, std::memory_order_relaxed) - 1; + std::cerr << "receive buffer fill: " << fill << std::endl; + #endif + } + } +} + +void udp_server::handle_packet(const char *data, int32_t size, const ip_address& addr){ + try { + osc::ReceivedPacket packet(data, size); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, size, type, onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_server: not an AOO NET message!"); + return; + } + + if (type == AOO_TYPE_SERVER){ + handle_message(msg, onset, addr); + } else if (type == AOO_TYPE_RELAY){ + handle_relay_message(msg, addr); + } else { + LOG_WARNING("aoo_server: not a client message!"); + return; + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception in receive_udp: " << e.what()); + // ignore for now + } +} + +void udp_server::handle_message(const osc::ReceivedMessage &msg, int onset, + const ip_address& addr) +{ + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_server: handle client UDP message " << pattern); + + try { + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + // reply with /ping message + char buf[512]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_PING) + << osc::EndMessage; + + send_message(reply.Data(), reply.Size(), addr); + } else if (!strcmp(pattern, AOO_NET_MSG_REQUEST)){ + // reply with /reply message + // send *unmapped* address in case the client is IPv4 only + char buf[512]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_REPLY) + << addr.name_unmapped() << addr.port() + << osc::EndMessage; + + send_message(reply.Data(), reply.Size(), addr); + } else { + LOG_ERROR("aoo_server: unknown message " << pattern); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception on handling " << pattern + << " message: " << e.what()); + // ignore for now + } +} + +void udp_server::handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src){ + auto it = msg.ArgumentsBegin(); + + auto ip = (it++)->AsString(); + auto port = (it++)->AsInt32(); + ip_address dst(ip, port, type_); + + const void *msgData; + osc::osc_bundle_element_size_t msgSize; + (it++)->AsBlob(msgData, msgSize); + + // forward message to matching client + // send unmapped address in case the client is IPv4 only! + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream out(buf, sizeof(buf)); + out << osc::BeginMessage(AOO_MSG_DOMAIN AOO_NET_MSG_RELAY) + << src.name_unmapped() << src.port() << osc::Blob(msgData, msgSize) + << osc::EndMessage; + + send_message(out.Data(), out.Size(), dst); +} + +void udp_server::send_message(const char *msg, int32_t size, + const ip_address &addr) +{ + int result = ::sendto(socket_, msg, size, 0, + addr.address(), addr.length()); + if (result < 0){ + int err = socket_errno(); + // TODO handle error + LOG_ERROR("aoo_server: send() failed (" << err << ")"); + } +} + +/*////////////////////////// user ///////////////////////////*/ + +void user::on_close(server_imp& s){ + // disconnect user from groups + for (auto& grp : groups_){ + grp->remove_user(*this); + s.on_user_left_group(*this, *grp); + } + + s.on_user_left(*this); + + groups_.clear(); + // clear endpoint so the server knows it can remove the user + endpoint_ = nullptr; +} + +bool user::add_group(std::shared_ptr grp){ + auto it = std::find(groups_.begin(), groups_.end(), grp); + if (it == groups_.end()){ + groups_.push_back(grp); + return true; + } else { + return false; + } +} + +bool user::remove_group(const group& grp){ + // find by address + auto it = std::find_if(groups_.begin(), groups_.end(), + [&](auto& g){ return g.get() == &grp; }); + if (it != groups_.end()){ + groups_.erase(it); + return true; + } else { + return false; + } +} + +/*////////////////////////// group /////////////////////////*/ + +bool group::add_user(std::shared_ptr grp){ + auto it = std::find(users_.begin(), users_.end(), grp); + if (it == users_.end()){ + users_.push_back(grp); + return true; + } else { + LOG_ERROR("group::add_user: bug"); + return false; + } +} + +bool group::remove_user(const user& usr){ + // find by address + auto it = std::find_if(users_.begin(), users_.end(), + [&](auto& u){ return u.get() == &usr; }); + if (it != users_.end()){ + users_.erase(it); + return true; + } else { + LOG_ERROR("group::remove_user: bug"); + return false; + } +} + +/*///////////////////////// client_endpoint /////////////////////////////*/ + +client_endpoint::client_endpoint(server_imp &s, int socket, const ip_address &addr) + : server_(&s), socket_(socket), addr_(addr) +{ + // set TCP_NODELAY - do we need to do this? + int val = 1; + if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)) < 0){ + LOG_WARNING("client_endpoint: couldn't set TCP_NODELAY"); + // ignore + } + + sendbuffer_.setup(65536); + recvbuffer_.setup(65536); +} + +client_endpoint::~client_endpoint(){ + close(); +} + +bool client_endpoint::match(const ip_address& addr) const { + // match public UDP addresses! + for (auto& a : public_addresses_){ + if (a == addr){ + return true; + } + } + return false; +} + +void client_endpoint::close(bool notify){ + if (socket_ >= 0){ + LOG_VERBOSE("aoo_server: close client endpoint " + << addr_.name() << " " << addr_.port()); + socket_close(socket_); + socket_ = -1; + + if (user_ && notify){ + user_->on_close(*server_); + } + } +} + +void client_endpoint::send_message(const char *msg, int32_t size){ + if (sendbuffer_.write_packet((const uint8_t *)msg, size)){ + while (sendbuffer_.read_available()){ + uint8_t buf[1024]; + int32_t total = sendbuffer_.read_bytes(buf, sizeof(buf)); + + int32_t nbytes = 0; + while (nbytes < total){ + auto res = ::send(socket_, (char *)buf + nbytes, total - nbytes, 0); + if (res >= 0){ + nbytes += res; + #if 0 + LOG_VERBOSE("aoo_server: sent " << res << " bytes"); + #endif + } else { + auto err = socket_errno(); + // TODO handle error + LOG_ERROR("aoo_server: send() failed (" << err << ")"); + return; + } + } + } + LOG_DEBUG("aoo_server: sent " << msg << " to client"); + } else { + LOG_ERROR("aoo_server: couldn't send " << msg << " to client"); + } +} + +bool client_endpoint::receive_data(){ + char buffer[AOO_MAXPACKETSIZE]; + auto result = recv(socket_, buffer, sizeof(buffer), 0); + if (result == 0){ + LOG_WARNING("client_endpoint: connection was closed"); + return false; + } + if (result < 0){ + int err = socket_errno(); + // TODO handle error + LOG_ERROR("client_endpoint: recv() failed (" << err << ")"); + return false; + } + + recvbuffer_.write_bytes((uint8_t *)buffer, result); + + // handle packets + uint8_t buf[AOO_MAXPACKETSIZE]; + int32_t size; + while ((size = recvbuffer_.read_packet(buf, sizeof(buf))) > 0){ + try { + osc::ReceivedPacket packet((char *)buf, size); + if (packet.IsBundle()){ + osc::ReceivedBundle bundle(packet); + if (!handle_bundle(bundle)){ + return false; + } + } else { + if (!handle_message(packet.Contents(), packet.Size())){ + return false; + } + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception in client_endpoint::receive_data: " << e.what()); + return false; // close + } + } + return true; +} + +bool client_endpoint::handle_message(const char *data, int32_t n){ + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + aoo_type type; + int32_t onset; + auto err = parse_pattern(data, n, type, onset); + if (err != AOO_OK){ + LOG_WARNING("aoo_server: not an AOO NET message!"); + return false; + } + + try { + if (type == AOO_TYPE_SERVER){ + auto pattern = msg.AddressPattern() + onset; + LOG_DEBUG("aoo_server: got server message " << pattern); + if (!strcmp(pattern, AOO_NET_MSG_PING)){ + handle_ping(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_LOGIN)){ + handle_login(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_GROUP_JOIN)){ + handle_group_join(msg); + } else if (!strcmp(pattern, AOO_NET_MSG_GROUP_LEAVE)){ + handle_group_leave(msg); + } else { + LOG_ERROR("aoo_server: unknown server message " << pattern); + return false; + } + } else if (type == AOO_TYPE_RELAY){ + // use public address! + server_->handle_relay_message(msg, public_addresses().front()); + } else { + LOG_WARNING("aoo_client: got unexpected message " << msg.AddressPattern()); + return false; + } + + return true; + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_server: exception on handling " << msg.AddressPattern() + << " message: " << e.what()); + return false; + } +} + +bool client_endpoint::handle_bundle(const osc::ReceivedBundle &bundle){ + auto it = bundle.ElementsBegin(); + while (it != bundle.ElementsEnd()){ + if (it->IsBundle()){ + osc::ReceivedBundle b(*it); + if (!handle_bundle(b)){ + return false; + } + } else { + if (!handle_message(it->Contents(), it->Size())){ + return false; + } + } + ++it; + } + return true; +} + +void client_endpoint::handle_ping(const osc::ReceivedMessage& msg){ + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_PING) << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_login(const osc::ReceivedMessage& msg) +{ + int32_t result = 0; + uint32_t version = 0; + std::string errmsg; + + auto it = msg.ArgumentsBegin(); + auto count = msg.ArgumentCount(); + if (count > 6){ + version = (uint32_t)(it++)->AsInt32(); + count--; + } + // for now accept login messages without version. + // LATER they should fail, so clients have to upgrade. + if (version == 0 || check_version(version)){ + std::string username = (it++)->AsString(); + std::string password = (it++)->AsString(); + count -= 2; + + server_imp::error err; + if (!user_){ + user_ = server_->get_user(username, password, version, err); + if (user_){ + // success - collect addresses + while (count >= 2){ + std::string ip = (it++)->AsString(); + int32_t port = (it++)->AsInt32(); + ip_address addr(ip, port, server_->type()); + if (addr.valid()){ + public_addresses_.push_back(addr); + } + count -= 2; + } + user_->set_endpoint(this); + + LOG_VERBOSE("aoo_server: login: id: " << user_->id + << ", username: " << username << ", password: " << password); + + result = 1; + + server_->on_user_joined(*user_); + } else { + errmsg = server_imp::error_to_string(err); + } + } else { + errmsg = "already logged in"; // shouldn't happen + } + } else { + errmsg = "version not supported"; + } + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_LOGIN) << result; + if (result){ + reply << user_->id; + reply << (int32_t)server_->flags(); + } else { + reply << errmsg.c_str(); + } + reply << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_group_join(const osc::ReceivedMessage& msg) +{ + int result = 0; + std::string errmsg; + + auto it = msg.ArgumentsBegin(); + std::string name = (it++)->AsString(); + std::string password = (it++)->AsString(); + + server_imp::error err; + if (user_){ + auto grp = server_->get_group(name, password, err); + if (grp){ + if (user_->add_group(grp)){ + grp->add_user(user_); + server_->on_user_joined_group(*user_, *grp); + result = 1; + } else { + errmsg = "already a group member"; + } + } else { + errmsg = server_imp::error_to_string(err); + } + } else { + errmsg = "not logged in"; + } + + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_GROUP_JOIN) + << name.c_str() << result << errmsg.c_str() << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +void client_endpoint::handle_group_leave(const osc::ReceivedMessage& msg){ + int result = 0; + std::string errmsg; + + auto it = msg.ArgumentsBegin(); + std::string name = (it++)->AsString(); + + if (user_){ + auto grp = server_->find_group(name); + if (grp){ + if (user_->remove_group(*grp)){ + grp->remove_user(*user_); + server_->on_user_left_group(*user_, *grp); + result = 1; + } else { + errmsg = "not a group member"; + } + } else { + errmsg = "couldn't find group"; + } + } else { + errmsg = "not logged in"; + } + + // send reply + char buf[AOO_MAXPACKETSIZE]; + osc::OutboundPacketStream reply(buf, sizeof(buf)); + reply << osc::BeginMessage(AOO_NET_MSG_CLIENT_GROUP_LEAVE) + << name.c_str() << result << errmsg.c_str() << osc::EndMessage; + + send_message(reply.Data(), reply.Size()); +} + +/*///////////////////// events ////////////////////////*/ + +server_imp::error_event::error_event(int32_t type, int32_t code, + const char * msg) +{ + error_event_.type = type; + error_event_.error_code = code; + error_event_.error_message = copy_string(msg); +} + +server_imp::error_event::~error_event() +{ + free_string((char *)error_event_.error_message); +} + +server_imp::user_event::user_event(int32_t type, + const char *name, int32_t id, + const ip_address& address){ + user_event_.type = type; + user_event_.user_name = copy_string(name); + user_event_.user_id = id; + user_event_.address = copy_sockaddr(address.address(), address.length()); + user_event_.addrlen = address.length(); +} + +server_imp::user_event::~user_event() +{ + free_string((char *)user_event_.user_name); + free_sockaddr((void *)user_event_.address, user_event_.addrlen); +} + +server_imp::group_event::group_event(int32_t type, const char *group, + const char *user, int32_t id) +{ + group_event_.type = type; + group_event_.group_name = copy_string(group); + group_event_.user_name = copy_string(user); + group_event_.user_id = id; +} + +server_imp::group_event::~group_event() +{ + free_string((char *)group_event_.group_name); + free_string((char *)group_event_.user_name); +} + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/net/server.hpp b/deps/aoo/aoo/src/net/server.hpp new file mode 100644 index 00000000..728e40ae --- /dev/null +++ b/deps/aoo/aoo/src/net/server.hpp @@ -0,0 +1,323 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#pragma once + +#include "aoo/aoo_net.hpp" + +#include "common/sync.hpp" +#include "common/utils.hpp" +#include "common/lockfree.hpp" +#include "common/net_utils.hpp" + +#include "commands.hpp" +#include "../imp.hpp" +#include "SLIP.hpp" + +#include "oscpack/osc/OscOutboundPacketStream.h" +#include "oscpack/osc/OscReceivedElements.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#define DEBUG_THREADS 0 + +namespace aoo { +namespace net { + +class server_imp; + +using ip_address_list = std::vector>; + +struct user; +using user_ptr = std::shared_ptr; +using user_list = std::vector>; + +struct group; +using group_ptr = std::shared_ptr; +using group_list = std::vector>; + + +class client_endpoint { +public: + client_endpoint(server_imp& s, int socket, const ip_address& addr); + + ~client_endpoint(); + + client_endpoint(const client_endpoint&) = delete; + client_endpoint(client_endpoint&&) = delete; + + const ip_address& local_address() const { return addr_; } + + const ip_address_list& public_addresses() const { + return public_addresses_; + } + + bool match(const ip_address& addr) const; + + int socket() const { return socket_; } + + void close(bool notify = true); + + bool active() const { return socket_ >= 0; } + + void send_message(const char *msg, int32_t); + + bool receive_data(); +private: + server_imp *server_; + int socket_; + ip_address_list public_addresses_; + std::shared_ptr user_; + ip_address addr_; + + SLIP> sendbuffer_; + SLIP> recvbuffer_; + + bool handle_message(const char *data, int32_t n); + + bool handle_bundle(const osc::ReceivedBundle& bundle); + + void handle_ping(const osc::ReceivedMessage& msg); + + void handle_login(const osc::ReceivedMessage& msg); + + void handle_group_join(const osc::ReceivedMessage& msg); + + void handle_group_leave(const osc::ReceivedMessage& msg); +}; + +struct user { + user(const std::string& _name, const std::string& _pwd, + int32_t _id, uint32_t _version) + : name(_name), password(_pwd), id(_id), version(_version) {} + + ~user() { LOG_VERBOSE("removed user " << name); } + + bool active() const { return endpoint_ != nullptr; } + + void on_close(server_imp& s); + + bool add_group(std::shared_ptr grp); + + bool remove_group(const group& grp); + + int32_t num_groups() const { return groups_.size(); } + + const group_list& groups() { return groups_; } + + client_endpoint * endpoint() { + return endpoint_; + } + + void set_endpoint(client_endpoint *ep){ + endpoint_ = ep; + } + + // data + const std::string name; + const std::string password; + const int32_t id; + const uint32_t version; +private: + group_list groups_; + client_endpoint *endpoint_ = nullptr; +}; + +struct group { + group(const std::string& _name, const std::string& _pwd) + : name(_name), password(_pwd){} + ~group() { LOG_VERBOSE("removed group " << name); } + + bool add_user(std::shared_ptr usr); + + bool remove_user(const user& usr); + + int32_t num_users() const { return users_.size(); } + + const user_list& users() { return users_; } + + // data + const std::string name; + const std::string password; +private: + user_list users_; +}; + +class server_imp; + +class udp_server { +public: + udp_server(int socket); + ~udp_server(); +private: + int socket_; + ip_address::ip_type type_; + + std::thread receivethread_; + std::thread workerthread_; + + struct udp_packet { + std::vector data; + ip_address address; + }; + using packet_queue = lockfree::unbounded_mpsc_queue>; + packet_queue recvbuffer_; +#if DEBUG_THREADS + std::atomic recvbufferfill_{0}; +#endif + std::atomic quit_{false}; + sync::event event_; + + void receive_packets(); + + void handle_packets(); + + void handle_packet(const char *data, int32_t n, const ip_address& addr); + + void handle_message(const osc::ReceivedMessage& msg, int onset, const ip_address& addr); + + void handle_relay_message(const osc::ReceivedMessage& msg, const ip_address& src); + + void send_message(const char *data, int32_t n, const ip_address& addr); +}; + +class server_imp final : public server { +public: + enum class error { + none, + wrong_password, + permission_denied, + access_denied + }; + + static std::string error_to_string(error e); + + struct ievent { + virtual ~ievent(){} + + union { + aoo_event event_; + aoo_net_error_event error_event_; + aoo_net_user_event user_event_; + aoo_net_group_event group_event_; + }; + }; + + server_imp(int tcpsocket, int udpsocket); + + ~server_imp(); + + ip_address::ip_type type() const { return type_; } + + aoo_error run() override; + + aoo_error quit() override; + + aoo_error set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) override; + + aoo_bool events_available() override; + + aoo_error poll_events() override; + + aoo_error control(int32_t ctl, intptr_t index, void *ptr, size_t size) override; + + std::shared_ptr get_user(const std::string& name, + const std::string& pwd, + uint32_t version, error& e); + + std::shared_ptr find_user(const std::string& name); + + std::shared_ptr get_group(const std::string& name, + const std::string& pwd, error& e); + + std::shared_ptr find_group(const std::string& name); + + void on_user_joined(user& usr); + + void on_user_left(user& usr); + + void on_user_joined_group(user& usr, group& grp); + + void on_user_left_group(user& usr, group& grp); + + void handle_relay_message(const osc::ReceivedMessage& msg, + const ip_address& src); + + uint32_t flags() const; +private: + int tcpsocket_; + int eventsocket_; + ip_address::ip_type type_; + std::vector pollarray_; + udp_server udpserver_; + // clients + std::list clients_; + // users/groups + int32_t next_user_id_ = 0; + user_list users_; + group_list groups_; + // events + using ievent_ptr = std::unique_ptr; + using event_queue = lockfree::unbounded_mpsc_queue>; + event_queue events_; + aoo_eventhandler eventhandler_ = nullptr; + void *eventcontext_ = nullptr; + aoo_event_mode eventmode_ = AOO_EVENT_NONE; + + void send_event(std::unique_ptr e); + + int32_t get_next_user_id(); + + // signal + std::atomic quit_{false}; + + // options + std::atomic allow_relay_{AOO_NET_RELAY_ENABLE}; + std::atomic notify_on_shutdown_{AOO_NET_NOTIFY_ON_SHUTDOWN}; + + bool receive(); + + void update(); + + /*/////////////////// events //////////////////////*/ + + struct error_event : ievent + { + error_event(int32_t type, int32_t code, const char * msg = 0); + ~error_event(); + }; + + struct user_event : ievent + { + user_event(int32_t type, const char *name, int32_t id, + const ip_address& address); + ~user_event(); + }; + + struct group_event : ievent + { + group_event(int32_t type, const char *group, + const char *user, int32_t id); + ~group_event(); + }; +}; + +} // net +} // aoo diff --git a/deps/aoo/aoo/src/resampler.cpp b/deps/aoo/aoo/src/resampler.cpp new file mode 100644 index 00000000..c03b7b1a --- /dev/null +++ b/deps/aoo/aoo/src/resampler.cpp @@ -0,0 +1,150 @@ +#include "resampler.hpp" + +#include + +namespace aoo { + +// extra space for samplerate fluctuations and non-pow-of-2 blocksizes. +// must be larger than 2! +#define AOO_RESAMPLER_SPACE 2.5 + +void dynamic_resampler::setup(int32_t nfrom, int32_t nto, + int32_t srfrom, int32_t srto, + int32_t nchannels){ + reset(); + nchannels_ = nchannels; + ideal_ratio_ = (double)srto / (double)srfrom; + int32_t blocksize; + if (ideal_ratio_ < 1.0){ + // downsampling + blocksize = std::max(nfrom, (double)nto / ideal_ratio_ + 0.5); + } else { + blocksize = std::max(nfrom, nto); + } + blocksize *= AOO_RESAMPLER_SPACE; +#if AOO_DEBUG_RESAMLER + DO_LOG_DEBUG("resampler setup: nfrom: " << nfrom << ", srfrom: " << srfrom + << ", nto: " << nto << ", srto: " << srto << ", capacity: " << blocksize); +#endif + buffer_.resize(blocksize * nchannels); + update(srfrom, srto); +} + +void dynamic_resampler::reset(){ + // don't touch ratio_! + rdpos_ = 0; + wrpos_ = 0; + balance_ = 0; +} + +void dynamic_resampler::update(double srfrom, double srto){ + if (srfrom == srto){ + ratio_ = 1; + } else { + ratio_ = srto / srfrom; + } +#if AOO_DEBUG_RESAMLER + DO_LOG_DEBUG("srfrom: " << srfrom << ", srto: " << srto << ", ratio: " << ratio_); + DO_LOG_DEBUG("balance: " << balance_ << ", capacity: " << buffer_.size()); +#endif +} + +bool dynamic_resampler::write(const aoo_sample *data, int32_t n){ + if ((buffer_.size() - balance_) < n){ + return false; + } + auto buf = buffer_.data(); + auto size = (int32_t)buffer_.size(); + auto endpos = wrpos_ + n; + if (endpos > size){ + auto split = size - wrpos_; + std::copy(data, data + split, buf + wrpos_); + std::copy(data + split, data + n, buf); + } else { + std::copy(data, data + n, buf + wrpos_); + } + wrpos_ = endpos; + if (wrpos_ >= size){ + wrpos_ -= size; + } + balance_ += n; + return true; +} + +bool dynamic_resampler::read(aoo_sample *data, int32_t n){ + auto size = (int32_t)buffer_.size(); + auto limit = size / nchannels_; + int32_t intpos = (int32_t)rdpos_; + double advance = 1.0 / ratio_; + int32_t intadvance = (int32_t)advance; + if ((advance - intadvance) == 0.0 && (rdpos_ - intpos) == 0.0){ + // non-interpolating (faster) versions + if ((int32_t)balance_ < n * intadvance){ + return false; + } + if (intadvance == 1){ + // just copy samples + int32_t pos = intpos * nchannels_; + int32_t end = pos + n; + auto buf = buffer_.data(); + if (end > size){ + auto n1 = size - pos; + auto n2 = end - size; + std::copy(buf + pos, buf + size, data); + std::copy(buf, buf + n2, data + n1); + } else { + std::copy(buf + pos, buf + end, data); + } + pos += n; + if (pos >= size){ + pos -= size; + } + rdpos_ = pos / nchannels_; + balance_ -= n; + } else { + // skip samples + int32_t pos = rdpos_; + for (int i = 0; i < n; i += nchannels_){ + for (int j = 0; j < nchannels_; ++j){ + int32_t index = pos * nchannels_ + j; + data[i + j] = buffer_[index]; + } + pos += intadvance; + if (pos >= limit){ + pos -= limit; + } + } + rdpos_ = pos; + balance_ -= n * intadvance; + } + } else { + // interpolating version + if (static_cast(balance_ * ratio_ / nchannels_) * nchannels_ <= n){ + return false; + } + double pos = rdpos_; + for (int i = 0; i < n; i += nchannels_){ + int32_t index = (int32_t)pos; + double fract = pos - (double)index; + for (int j = 0; j < nchannels_; ++j){ + int32_t idx1 = index * nchannels_ + j; + int32_t idx2 = (index + 1) * nchannels_ + j; + if (idx2 >= size){ + idx2 -= size; + } + double a = buffer_[idx1]; + double b = buffer_[idx2]; + data[i + j] = a + (b - a) * fract; + } + pos += advance; + if (pos >= limit){ + pos -= limit; + } + } + rdpos_ = pos; + balance_ -= n * advance; + } + return true; +} + +} // aoo diff --git a/deps/aoo/aoo/src/resampler.hpp b/deps/aoo/aoo/src/resampler.hpp new file mode 100644 index 00000000..6231b7c7 --- /dev/null +++ b/deps/aoo/aoo/src/resampler.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "aoo/aoo_types.h" + +#include "imp.hpp" + +#include + +namespace aoo { + +class dynamic_resampler { +public: + void setup(int32_t nfrom, int32_t nto, + int32_t srfrom, int32_t srto, + int32_t nchannels); + void reset(); + void update(double srfrom, double srto); + + bool write(const aoo_sample* data, int32_t n); + bool read(aoo_sample* data, int32_t n); + + int32_t size() const { return balance_; } + int32_t capacity() const { return buffer_.size(); } + double ratio() const { return ideal_ratio_; } +private: + std::vector> buffer_; + int32_t nchannels_ = 0; + double rdpos_ = 0; + int32_t wrpos_ = 0; + double balance_ = 0; + double ratio_ = 1.0; + double ideal_ratio_ = 1.0; +}; + +} diff --git a/deps/aoo/aoo/src/sink.cpp b/deps/aoo/aoo/src/sink.cpp new file mode 100644 index 00000000..7ba6ff06 --- /dev/null +++ b/deps/aoo/aoo/src/sink.cpp @@ -0,0 +1,2074 @@ +/* Copyright (c) 2010-Now Christof Ressi, Winfried Ritsch and others. + * For information on usage and redistribution, and for a DISCLAIMER OF ALL + * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include "sink.hpp" + +#include +#include + +/*//////////////////// aoo_sink /////////////////////*/ + +aoo_sink * aoo_sink_new(aoo_id id, uint32_t flags) { + return aoo::construct(id, flags); +} + +aoo::sink_imp::sink_imp(aoo_id id, uint32_t flags) + : id_(id) { + eventqueue_.reserve(AOO_EVENTQUEUESIZE); +} + +void aoo_sink_free(aoo_sink *sink) { + // cast to correct type because base class + // has no virtual destructor! + aoo::destroy(static_cast(sink)); +} + +aoo::sink_imp::~sink_imp(){} + +aoo_error aoo_sink_setup(aoo_sink *sink, int32_t samplerate, + int32_t blocksize, int32_t nchannels) { + return sink->setup(samplerate, blocksize, nchannels); +} + +aoo_error aoo::sink_imp::setup(int32_t samplerate, + int32_t blocksize, int32_t nchannels){ + if (samplerate > 0 && blocksize > 0 && nchannels > 0) + { + if (samplerate != samplerate_ || blocksize != blocksize_ || + nchannels != nchannels_) + { + nchannels_ = nchannels; + samplerate_ = samplerate; + blocksize_ = blocksize; + + realsr_.store(samplerate); + + reset_sources(); + } + + // always reset timer + time DLL filter + timer_.setup(samplerate_, blocksize_, timer_check_.load()); + + return AOO_OK; + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +namespace aoo { + +template +T& as(void *p){ + return *reinterpret_cast(p); +} + +} // aoo + +#define CHECKARG(type) assert(size == sizeof(type)) + +#define GETSOURCEARG \ + source_lock lock(sources_); \ + auto src = get_source_arg(index); \ + if (!src) { \ + return AOO_ERROR_UNSPECIFIED; \ + } \ + +aoo_error aoo_sink_ctl(aoo_sink *sink, int32_t ctl, + intptr_t index, void *p, size_t size) +{ + return sink->control(ctl, index, p, size); +} + +aoo_error aoo::sink_imp::control(int32_t ctl, intptr_t index, + void *ptr, size_t size) +{ + switch (ctl){ + // invite source + case AOO_CTL_INVITE_SOURCE: + { + auto ep = (const aoo_endpoint *)index; + if (!ep){ + return AOO_ERROR_UNSPECIFIED; + } + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + + push_request(source_request { request_type::invite, addr, ep->id }); + + break; + } + // uninvite source(s) + case AOO_CTL_UNINVITE_SOURCE: + { + auto ep = (const aoo_endpoint *)index; + if (ep){ + // single source + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + + push_request(source_request { request_type::invite, addr, ep->id }); + } else { + // all sources + push_request(source_request { request_type::uninvite_all }); + } + break; + } + // id + case AOO_CTL_SET_ID: + { + CHECKARG(int32_t); + auto newid = as(ptr); + if (id_.exchange(newid) != newid){ + // LATER clear source list here + } + break; + } + case AOO_CTL_GET_ID: + CHECKARG(aoo_id); + as(ptr) = id(); + break; + // reset + case AOO_CTL_RESET: + { + if (index != 0){ + GETSOURCEARG; + src->reset(*this); + } else { + // reset all sources + reset_sources(); + // reset time DLL + timer_.reset(); + } + break; + } + // request format + case AOO_CTL_REQUEST_FORMAT: + { + CHECKARG(aoo_format); + GETSOURCEARG; + return src->request_format(*this, as(ptr)); + } + // get format + case AOO_CTL_GET_FORMAT: + { + assert(size >= sizeof(aoo_format)); + GETSOURCEARG; + return src->get_format(as(ptr), size); + } + // buffer size + case AOO_CTL_SET_BUFFERSIZE: + { + CHECKARG(int32_t); + auto bufsize = std::max(0, as(ptr)); + if (bufsize != buffersize_){ + buffersize_.store(bufsize); + reset_sources(); + } + break; + } + case AOO_CTL_GET_BUFFERSIZE: + CHECKARG(int32_t); + as(ptr) = buffersize_.load(); + break; + // get buffer fill ratio + case AOO_CTL_GET_BUFFER_FILL_RATIO: + { + CHECKARG(float); + GETSOURCEARG; + as(ptr) = src->get_buffer_fill_ratio(); + break; + } + // timer check + case AOO_CTL_SET_TIMER_CHECK: + CHECKARG(aoo_bool); + timer_check_.store(as(ptr)); + break; + case AOO_CTL_GET_TIMER_CHECK: + CHECKARG(aoo_bool); + as(ptr) = timer_check_.load(); + break; + // dynamic resampling + case AOO_CTL_SET_DYNAMIC_RESAMPLING: + CHECKARG(aoo_bool); + dynamic_resampling_.store(as(ptr)); + timer_.reset(); // ! + break; + case AOO_CTL_GET_DYNAMIC_RESAMPLING: + CHECKARG(aoo_bool); + as(ptr) = dynamic_resampling_.load(); + break; + // time DLL filter bandwidth + case AOO_CTL_SET_DLL_BANDWIDTH: + { + CHECKARG(float); + auto bw = std::max(0, std::min(1, as(ptr))); + dll_bandwidth_.store(bw); + timer_.reset(); // will update time DLL and reset timer + break; + } + case AOO_CTL_GET_DLL_BANDWIDTH: + CHECKARG(float); + as(ptr) = dll_bandwidth_.load(); + break; + // real samplerate + case AOO_CTL_GET_REAL_SAMPLERATE: + CHECKARG(double); + as(ptr) = realsr_.load(std::memory_order_relaxed); + break; + // packetsize + case AOO_CTL_SET_PACKETSIZE: + { + CHECKARG(int32_t); + const int32_t minpacketsize = 64; + auto packetsize = as(ptr); + if (packetsize < minpacketsize){ + LOG_WARNING("packet size too small! setting to " << minpacketsize); + packetsize_.store(minpacketsize); + } else if (packetsize > AOO_MAXPACKETSIZE){ + LOG_WARNING("packet size too large! setting to " << AOO_MAXPACKETSIZE); + packetsize_.store(AOO_MAXPACKETSIZE); + } else { + packetsize_.store(packetsize); + } + break; + } + case AOO_CTL_GET_PACKETSIZE: + CHECKARG(int32_t); + as(ptr) = packetsize_.load(); + break; + // resend data + case AOO_CTL_SET_RESEND_DATA: + CHECKARG(aoo_bool); + resend_.store(as(ptr)); + break; + case AOO_CTL_GET_RESEND_DATA: + CHECKARG(aoo_bool); + as(ptr) = resend_.load(); + break; + // resend interval + case AOO_CTL_SET_RESEND_INTERVAL: + { + CHECKARG(int32_t); + auto interval = std::max(0, as(ptr)) * 0.001; + resend_interval_.store(interval); + break; + } + case AOO_CTL_GET_RESEND_INTERVAL: + CHECKARG(int32_t); + as(ptr) = resend_interval_.load() * 1000.0; + break; + // resend limit + case AOO_CTL_SET_RESEND_LIMIT: + { + CHECKARG(int32_t); + auto limit = std::max(1, as(ptr)); + resend_limit_.store(limit); + break; + } + case AOO_CTL_GET_RESEND_LIMIT: + CHECKARG(int32_t); + as(ptr) = resend_limit_.load(); + break; + // source timeout + case AOO_CTL_SET_SOURCE_TIMEOUT: + { + CHECKARG(int32_t); + auto timeout = std::max(0, as(ptr)) * 0.001; + source_timeout_.store(timeout); + break; + } + case AOO_CTL_GET_SOURCE_TIMEOUT: + CHECKARG(int32_t); + as(ptr) = source_timeout_.load() * 1000.0; + break; + // unknown + default: + LOG_WARNING("aoo_sink: unsupported control " << ctl); + return AOO_ERROR_UNSPECIFIED; + } + return AOO_OK; +} + +aoo_error aoo_sink_handle_message(aoo_sink *sink, const char *data, int32_t n, + const void *address, int32_t addrlen) { + return sink->handle_message(data, n, address, addrlen); +} + +aoo_error aoo::sink_imp::handle_message(const char *data, int32_t n, + const void *address, int32_t addrlen) { + if (samplerate_ == 0){ + return AOO_ERROR_UNSPECIFIED; // not setup yet + } + + aoo_type type; + aoo_id sinkid; + int32_t onset; + auto err = aoo_parse_pattern(data, n, &type, &sinkid, &onset); + if (err != AOO_OK){ + LOG_WARNING("not an AoO message!"); + return AOO_ERROR_UNSPECIFIED; + } + + if (type != AOO_TYPE_SINK){ + LOG_WARNING("not a sink message!"); + return AOO_ERROR_UNSPECIFIED; + } + if (sinkid != id()){ + LOG_WARNING("wrong sink ID!"); + return AOO_ERROR_UNSPECIFIED; + } + + ip_address addr((const sockaddr *)address, addrlen); + + if (data[0] == 0){ + // binary message + auto cmd = aoo::from_bytes(data + AOO_BIN_MSG_DOMAIN_SIZE + 2); + switch (cmd){ + case AOO_BIN_MSG_CMD_DATA: + return handle_data_message(data + onset, n - onset, addr); + default: + return AOO_ERROR_UNSPECIFIED; + } + } else { + // OSC message + try { + osc::ReceivedPacket packet(data, n); + osc::ReceivedMessage msg(packet); + + auto pattern = msg.AddressPattern() + onset; + if (!strcmp(pattern, AOO_MSG_FORMAT)){ + return handle_format_message(msg, addr); + } else if (!strcmp(pattern, AOO_MSG_DATA)){ + return handle_data_message(msg, addr); + } else if (!strcmp(pattern, AOO_MSG_PING)){ + return handle_ping_message(msg, addr); + } else { + LOG_WARNING("unknown message " << pattern); + } + } catch (const osc::Exception& e){ + LOG_ERROR("aoo_sink: exception in handle_message: " << e.what()); + } + return AOO_ERROR_UNSPECIFIED; + } +} + +aoo_error aoo_sink_send(aoo_sink *sink, aoo_sendfn fn, void *user){ + return sink->send(fn, user); +} + +aoo_error aoo::sink_imp::send(aoo_sendfn fn, void *user){ + sendfn reply(fn, user); + + // handle requests + source_request r; + while (requestqueue_.try_pop(r)){ + switch (r.type) { + case request_type::invite: + { + // try to find existing source + // we might want to invite an existing source, + // e.g. when it is currently uninviting + // NOTE that sources can also be added in the network + // receive thread (see handle_data() or handle_format()), + // so we have to lock a mutex to avoid the ABA problem. + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(r.address, r.id); + if (!src){ + src = add_source(r.address, r.id); + } + src->invite(*this); + break; + } + case request_type::uninvite: + { + // try to find existing source + source_lock lock(sources_); + auto src = find_source(r.address, r.id); + if (src){ + src->uninvite(*this); + } else { + LOG_WARNING("aoo: can't uninvite - source not found"); + } + break; + } + case request_type::uninvite_all: + { + source_lock lock(sources_); + for (auto& src : sources_){ + src.uninvite(*this); + } + break; + } + default: + break; + } + } + + source_lock lock(sources_); + for (auto& s : sources_){ + s.send(*this, reply); + } + lock.unlock(); + + // free unused source_descs + if (!sources_.try_free()){ + // LOG_DEBUG("aoo::sink: try_free() would block"); + } + + return AOO_OK; +} + +aoo_error aoo_sink_process(aoo_sink *sink, aoo_sample **data, + int32_t nsamples, uint64_t t) { + return sink->process(data, nsamples, t); +} + +#define AOO_MAXNUMEVENTS 256 + +aoo_error aoo::sink_imp::process(aoo_sample **data, int32_t nsamples, uint64_t t){ + // clear outputs + for (int i = 0; i < nchannels_; ++i){ + std::fill(data[i], data[i] + nsamples, 0); + } + + // update timer + // always do this, even if there are no sources! + bool dynamic_resampling = dynamic_resampling_.load(std::memory_order_relaxed); + double error; + auto state = timer_.update(t, error); + if (state == timer::state::reset){ + LOG_DEBUG("setup time DLL filter for sink"); + auto bw = dll_bandwidth_.load(std::memory_order_relaxed); + dll_.setup(samplerate_, blocksize_, bw, 0); + realsr_.store(samplerate_, std::memory_order_relaxed); + } else if (state == timer::state::error){ + // recover sources + int32_t xrunsamples = error * samplerate_ + 0.5; + + // no lock needed - sources are only removed in this thread! + for (auto& s : sources_){ + s.add_xrun(xrunsamples); + } + + sink_event e(AOO_XRUN_EVENT); + e.count = (float)xrunsamples / (float)blocksize_; + send_event(e, AOO_THREAD_AUDIO); + + timer_.reset(); + } else if (dynamic_resampling) { + // update time DLL, but only if n matches blocksize! + auto elapsed = timer_.get_elapsed(); + if (nsamples == blocksize_){ + dll_.update(elapsed); + #if AOO_DEBUG_DLL + DO_LOG_DEBUG("time elapsed: " << elapsed << ", period: " + << dll_.period() << ", samplerate: " << dll_.samplerate()); + #endif + } else { + // reset time DLL with nominal samplerate + auto bw = dll_bandwidth_.load(std::memory_order_relaxed); + dll_.setup(samplerate_, blocksize_, bw, elapsed); + } + realsr_.store(dll_.samplerate(), std::memory_order_relaxed); + } + + bool didsomething = false; + + // no lock needed - sources are only removed in this thread! + for (auto it = sources_.begin(); it != sources_.end();){ + if (it->process(*this, data, nsamples, t)){ + didsomething = true; + } else if (!it->is_active(*this)){ + // move source to garbage list (will be freed in send()) + if (it->is_inviting()){ + LOG_VERBOSE("aoo::sink: invitation for " << it->address().name() + << " " << it->address().port() << " timed out"); + sink_event e(AOO_INVITE_TIMEOUT_EVENT, *it); + send_event(e, AOO_THREAD_AUDIO); + } else { + LOG_VERBOSE("aoo::sink: removed inactive source " << it->address().name() + << " " << it->address().port()); + sink_event e(AOO_SOURCE_REMOVE_EVENT, *it); + send_event(e, AOO_THREAD_AUDIO); + } + it = sources_.erase(it); + continue; + } + ++it; + } + + if (didsomething){ + #if AOO_CLIP_OUTPUT + for (int i = 0; i < nchannels_; ++i){ + auto chn = data[i]; + for (int j = 0; j < nsamples; ++j){ + if (chn[j] > 1.0){ + chn[j] = 1.0; + } else if (chn[j] < -1.0){ + chn[j] = -1.0; + } + } + } + #endif + } + return AOO_OK; +} + +aoo_error aoo_sink_set_eventhandler(aoo_sink *sink, aoo_eventhandler fn, + void *user, int32_t mode) +{ + return sink->set_eventhandler(fn, user, mode); +} + +aoo_error aoo::sink_imp::set_eventhandler(aoo_eventhandler fn, void *user, int32_t mode) +{ + eventhandler_ = fn; + eventcontext_ = user; + eventmode_ = (aoo_event_mode)mode; + return AOO_OK; +} + +aoo_bool aoo_sink_events_available(aoo_sink *sink){ + return sink->events_available(); +} + +aoo_bool aoo::sink_imp::events_available(){ + if (!eventqueue_.empty()){ + return true; + } + + source_lock lock(sources_); + for (auto& src : sources_){ + if (src.has_events()){ + return true; + } + } + + return false; +} + +aoo_error aoo_sink_poll_events(aoo_sink *sink){ + return sink->poll_events(); +} + +#define EVENT_THROTTLE 1000 + +aoo_error aoo::sink_imp::poll_events(){ + int total = 0; + sink_event e; + while (eventqueue_.try_pop(e)){ + if (e.type == AOO_XRUN_EVENT){ + aoo_xrun_event xe; + xe.type = e.type; + xe.count = e.count; + eventhandler_(eventcontext_, (const aoo_event *)&xe, + AOO_THREAD_UNKNOWN); + } else { + aoo_source_event se; + se.type = e.type; + se.ep.address = e.address.address(); + se.ep.addrlen = e.address.length(); + se.ep.id = e.id; + eventhandler_(eventcontext_, (const aoo_event *)&se, + AOO_THREAD_UNKNOWN); + } + + total++; + } + // we only need to protect against source removal + source_lock lock(sources_); + for (auto& src : sources_){ + total += src.poll_events(*this, eventhandler_, eventcontext_); + if (total > EVENT_THROTTLE){ + break; + } + } + return AOO_OK; +} + +namespace aoo { + +void sink_imp::send_event(const sink_event &e, aoo_thread_level level) { + switch (eventmode_){ + case AOO_EVENT_POLL: + eventqueue_.push(e); + break; + case AOO_EVENT_CALLBACK: + { + aoo_sink_event se; + se.type = e.type; + se.ep.address = e.address.address(); + se.ep.addrlen = e.address.length(); + se.ep.id = e.id; + eventhandler_(eventcontext_, (const aoo_event *)&se, level); + break; + } + default: + break; + } +} + +// only called if mode is AOO_EVENT_CALLBACK +void sink_imp::call_event(const event &e, aoo_thread_level level) const { + eventhandler_(eventcontext_, &e.event_, level); + // some events use dynamic memory + if (e.type_ == AOO_FORMAT_CHANGE_EVENT){ + memory.free(memory_block::from_bytes((void *)e.format.format)); + } +} + +aoo::source_desc * sink_imp::find_source(const ip_address& addr, aoo_id id){ + for (auto& src : sources_){ + if (src.match(addr, id)){ + return &src; + } + } + return nullptr; +} + +aoo::source_desc * sink_imp::get_source_arg(intptr_t index){ + auto ep = (const aoo_endpoint *)index; + if (!ep){ + LOG_ERROR("aoo_sink: missing source argument"); + return nullptr; + } + ip_address addr((const sockaddr *)ep->address, ep->addrlen); + auto src = find_source(addr, ep->id); + if (!src){ + LOG_ERROR("aoo_sink: couldn't find source"); + } + return src; +} + +source_desc * sink_imp::add_source(const ip_address& addr, aoo_id id){ + // add new source + sources_.emplace_front(addr, id, elapsed_time()); + return &sources_.front(); +} + +void sink_imp::reset_sources(){ + source_lock lock(sources_); + for (auto& src : sources_){ + src.reset(*this); + } +} + +// /format +aoo_error sink_imp::handle_format_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + aoo_id id = (it++)->AsInt32(); + int32_t version = (it++)->AsInt32(); + + // LATER handle this in the source_desc (e.g. ignoring further messages) + if (!check_version(version)){ + LOG_ERROR("aoo_sink: source version not supported"); + return AOO_ERROR_UNSPECIFIED; + } + + int32_t salt = (it++)->AsInt32(); + // get format from arguments + aoo_format f; + f.nchannels = (it++)->AsInt32(); + f.samplerate = (it++)->AsInt32(); + f.blocksize = (it++)->AsInt32(); + f.codec = (it++)->AsString(); + f.size = sizeof(aoo_format); + const void *settings; + osc::osc_bundle_element_size_t size; + (it++)->AsBlob(settings, size); + // for backwards comptability (later remove check) + uint32_t flags = (it != msg.ArgumentsEnd()) ? + (uint32_t)(it++)->AsInt32() : 0; + + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_FORMAT << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + // NOTE: sources can also be added in the network send thread, + // so we have to lock a mutex to avoid the ABA problem! + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(addr, id); + if (!src){ + src = add_source(addr, id); + } + return src->handle_format(*this, salt, f, (const char *)settings, size, flags); +} + +aoo_error sink_imp::handle_data_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + auto id = (it++)->AsInt32(); + + aoo::net_packet d; + d.salt = (it++)->AsInt32(); + d.sequence = (it++)->AsInt32(); + d.samplerate = (it++)->AsDouble(); + d.channel = (it++)->AsInt32(); + d.totalsize = (it++)->AsInt32(); + d.nframes = (it++)->AsInt32(); + d.frame = (it++)->AsInt32(); + const void *blobdata; + osc::osc_bundle_element_size_t blobsize; + (it++)->AsBlob(blobdata, blobsize); + d.data = (const char *)blobdata; + d.size = blobsize; + + return handle_data_packet(d, false, addr, id); +} + +// binary data message: +// id (int32), salt (int32), seq (int32), channel (int16), flags (int16), +// [total (int32), nframes (int16), frame (int16)], [sr (float64)], +// size (int32), data... + +aoo_error sink_imp::handle_data_message(const char *msg, int32_t n, + const ip_address& addr) +{ + // check size (excluding samplerate, frames and data) + if (n < 20){ + LOG_ERROR("handle_data_message: header too small!"); + return AOO_ERROR_UNSPECIFIED; + } + + auto it = msg; + + auto id = aoo::read_bytes(it); + + aoo::net_packet d; + d.salt = aoo::read_bytes(it); + d.sequence = aoo::read_bytes(it); + d.channel = aoo::read_bytes(it); + auto flags = aoo::read_bytes(it); + if (flags & AOO_BIN_MSG_DATA_FRAMES){ + d.totalsize = aoo::read_bytes(it); + d.nframes = aoo::read_bytes(it); + d.frame = aoo::read_bytes(it); + } else { + d.totalsize = 0; + d.nframes = 1; + d.frame = 0; + } + if (flags & AOO_BIN_MSG_DATA_SAMPLERATE){ + d.samplerate = aoo::read_bytes(it); + } else { + d.samplerate = 0; + } + + d.size = aoo::read_bytes(it); + if (d.totalsize == 0){ + d.totalsize = d.size; + } + + if (n < ((it - msg) + d.size)){ + LOG_ERROR("handle_data_bin_message: wrong data size!"); + return AOO_ERROR_UNSPECIFIED; + } + + d.data = it; + + return handle_data_packet(d, true, addr, id); +} + +aoo_error sink_imp::handle_data_packet(net_packet& d, bool binary, + const ip_address& addr, aoo_id id) +{ + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_DATA << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + // NOTE: sources can also be added in the network send thread, + // so we have to lock a mutex to avoid the ABA problem! + sync::scoped_lock lock1(source_mutex_); + source_lock lock2(sources_); + auto src = find_source(addr, id); + if (!src){ + src = add_source(addr, id); + } + return src->handle_data(*this, d, binary); +} + +aoo_error sink_imp::handle_ping_message(const osc::ReceivedMessage& msg, + const ip_address& addr) +{ + auto it = msg.ArgumentsBegin(); + + auto id = (it++)->AsInt32(); + time_tag tt = (it++)->AsTimeTag(); + + if (id < 0){ + LOG_WARNING("bad ID for " << AOO_MSG_PING << " message"); + return AOO_ERROR_UNSPECIFIED; + } + // try to find existing source + source_lock lock(sources_); + auto src = find_source(addr, id); + if (src){ + return src->handle_ping(*this, tt); + } else { + LOG_WARNING("couldn't find source " << id << " for " << AOO_MSG_PING << " message"); + return AOO_ERROR_UNSPECIFIED; + } +} + +/*////////////////////////// event ///////////////////////////////////*/ + +// 'event' is always used inside 'source_desc', so we can safely +// store a pointer to the sockaddr. the ip_address itself +// never changes during lifetime of the 'source_desc'! +// NOTE: this assumes that the event queue is polled regularly, +// i.e. before a source_desc can be possibly autoremoved. +event::event(aoo_event_type type, const source_desc& desc){ + source.type = type; + source.ep.address = desc.address().address(); + source.ep.addrlen = desc.address().length(); + source.ep.id = desc.id(); +} + +// 'sink_event' is used in 'sink' for source events that can outlive +// its corresponding 'source_desc', therefore the ip_address is copied! +sink_event::sink_event(aoo_event_type _type, const source_desc &desc) + : type(_type), address(desc.address()), id(desc.id()) {} + +/*////////////////////////// source_desc /////////////////////////////*/ + +source_desc::source_desc(const ip_address& addr, aoo_id id, double time) + : addr_(addr), id_(id), last_packet_time_(time) +{ + // reserve some memory, so we don't have to allocate memory + // when pushing events in the audio thread. + eventqueue_.reserve(AOO_EVENTQUEUESIZE); + // resendqueue_.reserve(256); + LOG_DEBUG("source_desc"); +} + +source_desc::~source_desc(){ + // flush event queue + event e; + while (eventqueue_.try_pop(e)){ + if (e.type_ == AOO_FORMAT_CHANGE_EVENT){ + auto mem = memory_block::from_bytes((void *)e.format.format); + memory_block::free(mem); + } + } + // flush packet queue + net_packet d; + while (packetqueue_.try_pop(d)){ + auto mem = memory_block::from_bytes((void *)d.data); + memory_block::free(mem); + } + LOG_DEBUG("~source_desc"); +} + +bool source_desc::is_active(const sink_imp& s) const { + auto last = last_packet_time_.load(std::memory_order_relaxed); + return (s.elapsed_time() - last) < s.source_timeout(); +} + +aoo_error source_desc::get_format(aoo_format &format, size_t size){ + // synchronize with handle_format() and update()! + scoped_shared_lock lock(mutex_); + if (decoder_){ + return decoder_->get_format(format, size); + } else { + return AOO_ERROR_UNSPECIFIED; + } +} + +void source_desc::reset(const sink_imp& s){ + // take writer lock! + scoped_lock lock(mutex_); + update(s); +} + +#define MAXHWBUFSIZE 2048 +#define MINSAMPLERATE 44100 + +void source_desc::update(const sink_imp& s){ + // resize audio ring buffer + if (decoder_ && decoder_->blocksize() > 0 && decoder_->samplerate() > 0){ + // recalculate buffersize from ms to samples + int32_t bufsize = (double)s.buffersize() * 0.001 * decoder_->samplerate(); + // number of buffers (round up!) + int32_t nbuffers = std::ceil((double)bufsize / (double)decoder_->blocksize()); + // minimum buffer size depends on resampling and reblocking! + auto downsample = (double)decoder_->samplerate() / (double)s.samplerate(); + auto reblock = (double)s.blocksize() / (double)decoder_->blocksize(); + minblocks_ = std::ceil(downsample * reblock); + nbuffers = std::max(nbuffers, minblocks_); + LOG_DEBUG("source_desc: buffersize (ms): " << s.buffersize() + << ", samples: " << bufsize << ", nbuffers: " << nbuffers + << ", minimum: " << minblocks_); + + #if 0 + // don't touch the event queue once constructed + eventqueue_.reset(); + #endif + + auto nsamples = decoder_->nchannels() * decoder_->blocksize(); + double sr = decoder_->samplerate(); // nominal samplerate + + // setup audio buffer + auto nbytes = sizeof(block_data::header) + nsamples * sizeof(aoo_sample); + // align to 8 bytes + nbytes = (nbytes + 7) & ~7; + audioqueue_.resize(nbytes, nbuffers); + // fill buffer + for (int i = 0; i < nbuffers; ++i){ + auto b = (block_data *)audioqueue_.write_data(); + // push nominal samplerate, channel + silence + b->header.samplerate = sr; + b->header.channel = 0; + std::fill(b->data, b->data + nsamples, 0); + audioqueue_.write_commit(); + } + + // setup resampler + resampler_.setup(decoder_->blocksize(), s.blocksize(), + decoder_->samplerate(), s.samplerate(), + decoder_->nchannels()); + + // setup jitter buffer. + // if we use a very small audio buffer size, we have to make sure that + // we have enough space in the jitter buffer in case the source uses + // a larger hardware buffer size and consequently sends packets in batches. + // we don't know the actual source samplerate and hardware buffer size, + // so we have to make a pessimistic guess. + auto hwsamples = (double)decoder_->samplerate() / MINSAMPLERATE * MAXHWBUFSIZE; + auto minbuffers = std::ceil(hwsamples / (double)decoder_->blocksize()); + auto jitterbufsize = std::max(nbuffers, minbuffers); + // LATER optimize max. block size + jitterbuffer_.resize(jitterbufsize, nsamples * sizeof(double)); + LOG_DEBUG("jitter buffer: " << jitterbufsize << " blocks"); + + streamstate_ = AOO_STREAM_STATE_INIT; + lost_since_ping_.store(0); + channel_ = 0; + skipblocks_ = 0; + underrun_ = false; + didupdate_ = true; + + // reset decoder to avoid garbage from previous stream + decoder_->reset(); + } +} + +// called from the network thread +void source_desc::invite(const sink_imp& s){ + // only invite when idle or uninviting! + // NOTE: state can only change in this thread (= send thread), + // so we don't need a CAS loop. + auto state = state_.load(std::memory_order_relaxed); + while (state != source_state::stream){ + // special case: (re)invite shortly after uninvite + if (state == source_state::uninvite){ + // update last packet time to reset timeout! + last_packet_time_.store(s.elapsed_time()); + // force new format, otherwise handle_format() would ignore + // the format messages and we would spam the source with + // redundant invitation messages until we time out. + // NOTE: don't use a negative value, otherwise we would get + // a redundant "add" event, see handle_format(). + scoped_lock lock(mutex_); + salt_++; + } + #if 1 + state_time_.store(0.0); // start immediately + #else + state_time_.store(s.elapsed_time()); // wait + #endif + if (state_.compare_exchange_weak(state, source_state::invite)){ + LOG_DEBUG("source_desc: invite"); + return; + } + } + LOG_WARNING("aoo: couldn't invite source - already active"); +} + +// called from the network thread +void source_desc::uninvite(const sink_imp& s){ + // state can change in different threads, so we need a CAS loop + auto state = state_.load(std::memory_order_relaxed); + while (state != source_state::idle){ + // update start time for uninvite phase, see handle_data() + state_time_.store(s.elapsed_time()); + if (state_.compare_exchange_weak(state, source_state::uninvite)){ + LOG_DEBUG("source_desc: uninvite"); + return; + } + } + LOG_WARNING("aoo: couldn't uninvite source - not active"); +} + +aoo_error source_desc::request_format(const sink_imp& s, const aoo_format &f){ + if (state_.load(std::memory_order_relaxed) == source_state::uninvite){ + // requesting a format during uninvite doesn't make sense. + // also, we couldn't use 'state_time', because it has a different + // meaning during the uninvite phase. + return AOO_ERROR_UNSPECIFIED; + } + + if (!aoo::find_codec(f.codec)){ + LOG_WARNING("request_format: codec '" << f.codec << "' not supported"); + return AOO_ERROR_UNSPECIFIED; + } + + // copy format + auto fmt = (aoo_format *)aoo::allocate(f.size); + memcpy(fmt, &f, f.size); + + LOG_DEBUG("source_desc: request format"); + + scoped_lock lock(mutex_); // writer lock! + + format_request_.reset(fmt); + + format_time_ = s.elapsed_time(); +#if 1 + state_time_.store(0.0); // start immediately +#else + state_time_.store(s.elapsed_time()); // wait +#endif + + return AOO_OK; +} + +float source_desc::get_buffer_fill_ratio(){ + scoped_shared_lock lock(mutex_); + if (decoder_){ + // consider samples in resampler! + auto nsamples = decoder_->nchannels() * decoder_->blocksize(); + auto available = (double)audioqueue_.read_available() + + (double)resampler_.size() / (double)nsamples; + auto ratio = available / (double)audioqueue_.capacity(); + LOG_DEBUG("fill ratio: " << ratio << ", audioqueue: " << audioqueue_.read_available() + << ", resampler: " << (double)resampler_.size() / (double)nsamples); + // FIXME sometimes the result is bigger than 1.0 + return std::min(1.0, ratio); + } else { + return 0.0; + } +} + +// /aoo/sink//format + +aoo_error source_desc::handle_format(const sink_imp& s, int32_t salt, const aoo_format& f, + const char *settings, int32_t size, uint32_t flags){ + LOG_DEBUG("handle_format"); + // ignore redundant format messages! + // NOTE: salt_ can only change in this thread, + // so we don't need a lock to safely *read* it! + if (salt == salt_){ + return AOO_ERROR_UNSPECIFIED; + } + + // look up codec + auto c = aoo::find_codec(f.codec); + if (!c){ + LOG_ERROR("codec '" << f.codec << "' not supported!"); + return AOO_ERROR_UNSPECIFIED; + } + + // try to deserialize format + aoo_format_storage fmt; + if (c->deserialize(f, settings, size, + fmt.header, sizeof(fmt)) != AOO_OK){ + return AOO_ERROR_UNSPECIFIED; + } + + // Create a new decoder if necessary. + // This is the only thread where the decoder can possibly + // change, so we don't need a lock to safely *read* it! + std::unique_ptr new_decoder; + bool changed = false; + + if (!decoder_ || strcmp(decoder_->name(), f.codec)){ + new_decoder = c->create_decoder(); + if (!new_decoder){ + LOG_ERROR("couldn't create decoder!"); + return AOO_ERROR_UNSPECIFIED; + } + changed = true; + } else { + changed = !decoder_->compare(fmt.header); // thread-safe + } + + unique_lock lock(mutex_); // writer lock! + if (new_decoder){ + decoder_ = std::move(new_decoder); + } + + auto oldsalt = salt_; + salt_ = salt; + flags_ = flags; + format_request_ = nullptr; + + // set format (if changed) + if (changed && decoder_->set_format(fmt.header) != AOO_OK){ + return AOO_ERROR_UNSPECIFIED; + } + + // always update! + update(s); + + lock.unlock(); + + // NOTE: state can be changed in both network threads, + // so we need a CAS loop. + auto state = state_.load(std::memory_order_relaxed); + while (state == source_state::idle || state == source_state::invite){ + if (state_.compare_exchange_weak(state, source_state::stream)){ + // only push "add" event, if this is the first format message! + if (oldsalt < 0){ + event e(AOO_SOURCE_ADD_EVENT, *this); + send_event(s, e, AOO_THREAD_AUDIO); + LOG_DEBUG("add new source with id " << id()); + } + break; + } + } + + // send format event (if changed) + // NOTE: we could just allocate 'aoo_format_storage', but it would be wasteful. + if (changed){ + auto mem = s.memory.alloc(fmt.header.size); + memcpy(mem->data(), &fmt, fmt.header.size); + + event e(AOO_FORMAT_CHANGE_EVENT, *this); + e.format.format = (const aoo_format *)mem->data(); + + send_event(s, e, AOO_THREAD_NETWORK); + } + + return AOO_OK; +} + +// /aoo/sink//data + +aoo_error source_desc::handle_data(const sink_imp& s, net_packet& d, bool binary) +{ + binary_.store(binary, std::memory_order_relaxed); + + // always update packet time to signify that we're receiving packets + last_packet_time_.store(s.elapsed_time(), std::memory_order_relaxed); + + // if we're in uninvite state, ignore data and send uninvite request. + if (state_.load(std::memory_order_acquire) == source_state::uninvite){ + // only try for a certain amount of time to avoid spamming the source. + auto delta = s.elapsed_time() - state_time_.load(std::memory_order_relaxed); + if (delta < s.source_timeout()){ + push_request(request(request_type::uninvite)); + } + // ignore data message + return AOO_OK; + } + + // the source format might have changed and we haven't noticed, + // e.g. because of dropped UDP packets. + // NOTE: salt_ can only change in this thread! + if (d.salt != salt_){ + push_request(request(request_type::format)); + return AOO_OK; + } + + // synchronize with update()! + scoped_shared_lock lock(mutex_); + +#if 1 + if (!decoder_){ + LOG_DEBUG("ignore data message"); + return AOO_ERROR_UNSPECIFIED; + } +#else + assert(decoder_ != nullptr); +#endif + // check and fix up samplerate + if (d.samplerate == 0){ + // no dynamic resampling, just use nominal samplerate + d.samplerate = decoder_->samplerate(); + } + + // copy blob data and push to queue + auto data = (char *)s.memory.alloc(d.size)->data(); + memcpy(data, d.data, d.size); + d.data = data; + + packetqueue_.push(d); + +#if AOO_DEBUG_DATA + LOG_DEBUG("got block: seq = " << d.sequence << ", sr = " << d.samplerate + << ", chn = " << d.channel << ", totalsize = " << d.totalsize + << ", nframes = " << d.nframes << ", frame = " << d.frame << ", size " << d.size); +#endif + + return AOO_OK; +} + +// /aoo/sink//ping