├── .clang-format ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG-ARCHIVE.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── before_build.sh ├── build.sh ├── build_webui.sh ├── build_windows.sh ├── keychain │ └── cert_2020.p12 ├── lib │ ├── functions.sh │ └── versions.sh ├── patches │ ├── baresip_audio_rtp_discard.patch │ ├── bluetooth_conflict.patch │ ├── config.patch │ ├── dtls_aes256.patch │ ├── entitlements.plist │ ├── fallback_dns.patch │ ├── fix_check_telev.patch │ ├── fix_check_telev_and_pthread.patch │ ├── fix_windows_ssize_t_bug.patch │ ├── opus_fmtp.patch │ ├── osx_sample_rate.patch │ ├── re_fix_authorization.patch │ ├── re_ice_bug.patch │ ├── re_pull_66.diff │ ├── re_recv_handler_win_patch.patch │ ├── re_wsapoll.patch │ └── rtcp_mux_softphone.patch ├── tools │ ├── build_debug.sh │ ├── ccheck.py │ ├── ccheck.sh │ ├── downloads │ ├── jitter.sh │ ├── s3urls │ └── sl_trace │ │ ├── sl_trace.c │ │ └── sl_trace.h ├── version.sh └── windows │ └── Makefile ├── docs └── dev │ ├── timing.drawio │ └── timing.png └── src ├── modules ├── apponair │ ├── account.c │ ├── apponair.c │ ├── module.mk │ ├── utils.c │ └── webapp.h ├── auicecast │ ├── auicecast.c │ └── module.mk ├── effect │ ├── effect.c │ └── module.mk ├── effectonair │ ├── effectonair.c │ └── module.mk ├── g722 │ ├── g722.c │ ├── g722.h │ ├── g722_decode.c │ ├── g722_encode.c │ └── module.mk ├── gpio │ ├── event_gpio.c │ ├── event_gpio.h │ ├── gpio.c │ └── module.mk ├── jack │ ├── jack.c │ └── module.mk ├── slaudio │ ├── convert.h │ ├── module.mk │ ├── record.c │ ├── slaudio.c │ └── slaudio.h ├── slogging │ ├── module.mk │ └── slogging.c ├── slrtaudio │ ├── module.mk │ ├── record.c │ ├── slrtaudio.c │ └── slrtaudio.h └── webapp │ ├── account.c │ ├── contact.c │ ├── jitter.c │ ├── module.mk │ ├── option.c │ ├── sessions.c │ ├── utils.c │ ├── webapp.c │ ├── webapp.h │ ├── websocket.c │ ├── ws_baresip.c │ ├── ws_calls.c │ ├── ws_contacts.c │ ├── ws_meter.c │ ├── ws_options.c │ └── ws_rtaudio.c └── webui ├── make.sh ├── mix-manifest.json ├── package-lock.json ├── package.json ├── src ├── app.js ├── app.scss ├── components │ └── Onboarding.vue ├── icons.js ├── images │ ├── logo_plugin.svg │ └── logo_standalone.svg ├── index.html ├── init.js ├── notify.js ├── templates │ ├── activecalls.handlebars │ ├── addcontact.handlebars │ ├── addsip.handlebars │ ├── audiointerface.handlebars │ ├── calls.handlebars │ ├── changelog.handlebars │ ├── chatlist.handlebars │ ├── chatmessages.handlebars │ ├── currentlist.handlebars │ ├── editsip.handlebars │ ├── index.handlebars │ ├── keyboard.handlebars │ ├── listcontacts.handlebars │ ├── listsip.handlebars │ ├── options.handlebars │ └── status.handlebars ├── theme │ ├── _bootswatch.scss │ └── _variables.scss └── websockets │ ├── ws_baresip.js │ ├── ws_calls.js │ ├── ws_contacts.js │ ├── ws_meter.js │ ├── ws_options.js │ ├── ws_rtaudio.js │ └── ws_sipchat.js └── webpack.mix.js /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: true 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: None 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: MultiLine 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: true 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: true 33 | AfterNamespace: true 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: true 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: true 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Custom 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 79 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 57 | ConstructorInitializerIndentWidth: 8 58 | ContinuationIndentWidth: 8 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: false 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Preserve 70 | IncludeCategories: 71 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 75 | Priority: 3 76 | SortPriority: 0 77 | - Regex: '.*' 78 | Priority: 1 79 | SortPriority: 0 80 | IncludeIsMainRegex: '(Test)?$' 81 | IncludeIsMainSourceRegex: '' 82 | IndentCaseLabels: true 83 | IndentGotoLabels: true 84 | IndentPPDirectives: None 85 | IndentWidth: 8 86 | IndentWrappedFunctionNames: false 87 | JavaScriptQuotes: Leave 88 | JavaScriptWrapImports: true 89 | KeepEmptyLinesAtTheStartOfBlocks: true 90 | MacroBlockBegin: '' 91 | MacroBlockEnd: '' 92 | MaxEmptyLinesToKeep: 2 93 | NamespaceIndentation: None 94 | ObjCBinPackProtocolList: Auto 95 | ObjCBlockIndentWidth: 2 96 | ObjCSpaceAfterProperty: false 97 | ObjCSpaceBeforeProtocolList: true 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Right 107 | ReflowComments: true 108 | SortIncludes: false 109 | SortUsingDeclarations: true 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCpp11BracedList: false 115 | SpaceBeforeCtorInitializerColon: true 116 | SpaceBeforeInheritanceColon: true 117 | SpaceBeforeParens: ControlStatements 118 | SpaceBeforeRangeBasedForLoopColon: true 119 | SpaceInEmptyBlock: false 120 | SpaceInEmptyParentheses: false 121 | SpacesBeforeTrailingComments: 1 122 | SpacesInAngles: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: true 125 | SpacesInCStyleCastParentheses: false 126 | SpacesInParentheses: false 127 | SpacesInSquareBrackets: false 128 | SpaceBeforeSquareBrackets: false 129 | Standard: Latest 130 | StatementMacros: 131 | - Q_UNUSED 132 | - QT_REQUIRE_VERSION 133 | TabWidth: 8 134 | UseCRLF: false 135 | UseTab: Always 136 | ... 137 | 138 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/src/webui" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: bootstrap 10 | versions: 11 | - 5.x 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | webui: 7 | runs-on: ubuntu-20.04 8 | env: 9 | BUILD_TARGET: webui 10 | BUILD_OS: linux 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - uses: actions/cache@v2 15 | with: 16 | path: '**/node_modules' 17 | key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }} 18 | 19 | - name: build webui 20 | run: ./dist/build_webui.sh 21 | 22 | - name: SFTP Upload 23 | uses: sreimers/sftp-deploy-action@main 24 | with: 25 | username: 'slgithub' 26 | server: 'download.studio.link' 27 | ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} 28 | local_path: './build/s3_upload/*' 29 | remote_path: '/htdocs/' 30 | port: '2222' 31 | 32 | ccheck: 33 | runs-on: ubuntu-20.04 34 | env: 35 | BUILD_TARGET: ccheck 36 | BUILD_OS: linux 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: ccheck 40 | run: ./dist/build.sh 41 | 42 | build: 43 | needs: webui 44 | runs-on: ${{ matrix.os }} 45 | 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | include: 50 | - target: windows64 51 | os: ubuntu-20.04 52 | os_name: mingw 53 | - target: windows32 54 | os: ubuntu-20.04 55 | os_name: mingw 56 | - target: linux 57 | os: ubuntu-18.04 58 | os_name: linux 59 | - target: linuxjack 60 | os: ubuntu-18.04 61 | os_name: linux 62 | - target: macos_x86_64 63 | os: macos-10.15 64 | os_name: macos 65 | - target: macos_arm64 66 | os: macos-10.15 67 | os_name: macos 68 | 69 | env: 70 | BUILD_TARGET: ${{ matrix.target }} 71 | BUILD_OS: ${{ matrix.os_name }} 72 | 73 | steps: 74 | - uses: actions/checkout@v2 75 | 76 | - uses: sreimers/action-archlinux-mingw@main 77 | if: ${{ matrix.os_name == 'mingw' }} 78 | with: 79 | run: "./dist/build_windows.sh" 80 | 81 | - name: prepare build 82 | if: ${{ matrix.os_name == 'linux' || matrix.os_name == 'macos' }} 83 | run: ./dist/before_build.sh 84 | env: 85 | KEY_PASSWORD_2020: ${{ secrets.KEY_PASSWORD_2020 }} 86 | 87 | - name: build 88 | if: ${{ matrix.os_name == 'linux' || matrix.os_name == 'macos' }} 89 | run: ./dist/build.sh 90 | 91 | - name: SFTP Upload 92 | uses: sreimers/sftp-deploy-action@main 93 | with: 94 | username: 'slgithub' 95 | server: 'download.studio.link' 96 | ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }} 97 | local_path: './build/s3_upload/*' 98 | remote_path: '/htdocs/' 99 | port: '2222' 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | .vscode 3 | /src/webui/node_modules 4 | /src/webui/dist 5 | /src/webui/headers 6 | /src/modules/webapp/assets/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE AGREEMENT FOR STUDIOLINK SOFTWARE: 2 | Copyright (c) 2013-2021 Sebastian Reimers 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Studio Link 2 | 3 |
4 | 5 | 6 | 7 |
8 |
9 | Build Status 10 |
11 | 12 | ## About Studio Link 13 | 14 | Studio Link is a SIP application to create high quality Audio over IP (AoIP) connections. 15 | 16 | ## Usage 17 | 18 | Please use the prebuilt binarys at https://doku.studio-link.de/standalone/installation-standalone.html 19 | 20 | ## Supported Platforms 21 | 22 | - Windows 64Bit 23 | - macOS 64Bit 24 | - Linux 64Bit 25 | 26 | ## Changelog 27 | 28 | See [CHANGELOG.md](CHANGELOG.md) 29 | 30 | 31 | ## Development 32 | 33 | ### CI builds 34 | 35 | https://download.studio.link/devel/ 36 | 37 | ### Build Requirements 38 | 39 | - OpenSSL 40 | - Libtool 41 | - LV2 (optional) 42 | - Header files for OpenSSL, ALSA, PulseAudio and JACK 43 | - xxd and nodejs/npm 44 | 45 | ### Build Requirements on Ubuntu 16.04/18.04 46 | 47 | The needed header files are in these packages: 48 | libssl-dev libasound2-dev libjack-jackd2-dev libtool build-essential 49 | autoconf automake libpulse0 libpulse-dev xxd 50 | 51 | And current nodejs/npm (Node.js v10.x v11.x or v12.x): 52 | 53 | https://github.com/nodesource/distributions/blob/master/README.md 54 | 55 | ### Build on Linux 56 | 57 | ```bash 58 | mkdir studio-link 59 | cd studio-link 60 | git clone https://github.com/Studio-Link/3rdparty.git 61 | cd 3rdparty 62 | export BUILD_OS="linux"; export BUILD_TARGET="linux"; dist/build.sh 63 | cd .. 64 | git clone https://github.com/Studio-Link/app.git 65 | cd app 66 | export BUILD_OS="linux"; export BUILD_TARGET="linux"; dist/build.sh 67 | ``` 68 | 69 | ### Build on Linux with JACK support 70 | 71 | ```bash 72 | mkdir studio-link 73 | cd studio-link 74 | git clone https://github.com/Studio-Link/3rdparty.git 75 | cd 3rdparty 76 | export BUILD_OS="linux"; export BUILD_TARGET="linuxjack"; dist/build.sh 77 | cd .. 78 | git clone https://github.com/Studio-Link/app.git 79 | cd app 80 | export BUILD_OS="linux"; export BUILD_TARGET="linuxjack"; dist/build.sh 81 | ``` 82 | 83 | ### Build on macOS 84 | 85 | ```bash 86 | mkdir studio-link 87 | cd studio-link 88 | git clone https://github.com/Studio-Link/3rdparty.git 89 | cd 3rdparty 90 | export BUILD_OS="macos"; export BUILD_TARGET="macos_x86_64"; dist/build.sh 91 | cd .. 92 | git clone https://github.com/Studio-Link/app.git 93 | cd app 94 | export BUILD_OS="macos"; export BUILD_TARGET="macos_x86_64"; dist/build.sh 95 | ``` 96 | 97 | ### Build for Windows on Arch Linux (only) 98 | 99 | ```bash 100 | mkdir studio-link 101 | cd studio-link 102 | git clone https://github.com/Studio-Link/3rdparty.git 103 | cd 3rdparty 104 | export BUILD_OS="mingw"; export BUILD_TARGET="windows64"; dist/build.sh 105 | cd .. 106 | git clone https://github.com/Studio-Link/app.git 107 | cd app 108 | export BUILD_OS="mingw"; export BUILD_TARGET="windows64"; dist/build_windows.sh 109 | ``` 110 | 111 | ## License 112 | 113 | The Studio Link Apps are open-sourced software licensed under the [MIT license](LICENSE). 114 | -------------------------------------------------------------------------------- /dist/before_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ "$BUILD_OS" == "linux" ]; then 4 | sudo apt-get update 5 | sudo apt-get install -y libasound2-dev libjack-jackd2-dev libpulse-dev libpulse0 6 | wget http://lv2plug.in/spec/lv2-1.14.0.tar.bz2 7 | tar xjf lv2-1.14.0.tar.bz2 8 | pushd lv2-1.14.0 && ./waf configure && ./waf build && sudo ./waf install && popd 9 | elif [ "$BUILD_OS" == "macos" ]; then 10 | security create-keychain -p travis sl-build.keychain 11 | security list-keychains -s ~/Library/Keychains/sl-build.keychain 12 | security unlock-keychain -p travis ~/Library/Keychains/sl-build.keychain 13 | security set-keychain-settings ~/Library/Keychains/sl-build.keychain 14 | #security import ./dist/keychain/apple.cer -k ~/Library/Keychains/sl-build.keychain -A 15 | #security import ./dist/keychain/cert.cer -k ~/Library/Keychains/sl-build.keychain -A 16 | security import ./dist/keychain/cert_2020.p12 -k ~/Library/Keychains/sl-build.keychain -P $KEY_PASSWORD_2020 -A 17 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k travis sl-build.keychain 18 | fi 19 | -------------------------------------------------------------------------------- /dist/build_webui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | source dist/lib/versions.sh 3 | source dist/lib/functions.sh 4 | 5 | sl_prepare 6 | sl_build_webui 7 | 8 | s3_path="s3_upload/$GIT_BRANCH/$version_t" 9 | mkdir -p $s3_path 10 | zip -r $s3_path/webui webui/headers 11 | -------------------------------------------------------------------------------- /dist/build_windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | export PATH="$PATH:/usr/bin/core_perl" 4 | 5 | source dist/lib/versions.sh 6 | source dist/lib/functions.sh 7 | 8 | sl_prepare 9 | sl_3rdparty 10 | 11 | make_opts="-j4" 12 | 13 | #mkdir -p mingw 14 | #pushd mingw 15 | #mingwurl="https://github.com/Studio-Link/mingw/releases/download/v20.03.0" 16 | #wget -N $mingwurl/mingw-w64-binutils-2.34-1-x86_64.pkg.tar.xz 17 | #wget -N $mingwurl/mingw-w64-configure-0.1.1-9-any.pkg.tar.xz 18 | #wget -N $mingwurl/mingw-w64-crt-7.0.0-1-any.pkg.tar.xz 19 | #wget -N $mingwurl/mingw-w64-environment-1-2-any.pkg.tar.xz 20 | #wget -N $mingwurl/mingw-w64-gcc-9.3.0-1-x86_64.pkg.tar.xz 21 | #wget -N $mingwurl/mingw-w64-headers-7.0.0-1-any.pkg.tar.xz 22 | #wget -N $mingwurl/mingw-w64-pkg-config-2-4-any.pkg.tar.xz 23 | #wget -N $mingwurl/mingw-w64-winpthreads-7.0.0-1-any.pkg.tar.xz 24 | #yes | LANG=C sudo pacman -U *.pkg.tar.xz 25 | #popd 26 | 27 | if [ "$BUILD_TARGET" == "windows32" ]; then 28 | _arch="i686-w64-mingw32" 29 | else 30 | _arch="x86_64-w64-mingw32" 31 | fi 32 | 33 | unset CC 34 | unset CXX 35 | 36 | 37 | # Download libre 38 | #----------------------------------------------------------------------------- 39 | if [ ! -d re-$re ]; then 40 | sl_get_libre 41 | mkdir -p my_include/re 42 | cp -a re/include/* my_include/re/ 43 | fi 44 | 45 | # Download librem 46 | #----------------------------------------------------------------------------- 47 | if [ ! -d rem-$rem ]; then 48 | sl_get_librem 49 | fi 50 | 51 | # Download baresip with studio link addons 52 | #----------------------------------------------------------------------------- 53 | if [ ! -d baresip-$baresip ]; then 54 | sl_get_baresip 55 | fi 56 | 57 | # Download overlay-vst 58 | #----------------------------------------------------------------------------- 59 | if [ ! -d overlay-vst ]; then 60 | sl_get_overlay-vst 61 | fi 62 | 63 | # Download overlay-onair-vst 64 | #----------------------------------------------------------------------------- 65 | if [ ! -d overlay-onair-vst ]; then 66 | git clone https://github.com/Studio-Link/overlay-onair-vst.git 67 | cp -a overlay-vst/vstsdk2.4 overlay-onair-vst/ 68 | fi 69 | 70 | # Build 71 | #----------------------------------------------------------------------------- 72 | 73 | 74 | cp -a ../dist/windows/Makefile . 75 | make 76 | make -C overlay-vst PREFIX=$_arch 77 | make -C overlay-onair-vst PREFIX=$_arch 78 | 79 | mkdir -p debug 80 | cp -a studio-link-standalone.exe debug 81 | cp -a overlay-vst/studio-link.dll debug 82 | cp -a overlay-onair-vst/studio-link-onair.dll debug 83 | zip -r studio-link-debug.zip debug 84 | 85 | $_arch-strip --strip-all studio-link-standalone.exe 86 | $_arch-strip --strip-all overlay-vst/studio-link.dll 87 | $_arch-strip --strip-all overlay-onair-vst/studio-link-onair.dll 88 | 89 | zip -r studio-link-plugin-$BUILD_TARGET overlay-vst/studio-link.dll 90 | zip -r studio-link-plugin-onair-$BUILD_TARGET overlay-onair-vst/studio-link-onair.dll 91 | 92 | s3_path="s3_upload/$GIT_BRANCH/$version_t/$BUILD_TARGET" 93 | mkdir -p $s3_path 94 | cp -a studio-link-standalone.exe $s3_path/studio-link-standalone-$version_t.exe 95 | cp -a studio-link-plugin-$BUILD_TARGET.zip $s3_path 96 | cp -a studio-link-plugin-onair-$BUILD_TARGET.zip $s3_path 97 | cp -a studio-link-debug.zip $s3_path 98 | -------------------------------------------------------------------------------- /dist/keychain/cert_2020.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Studio-Link/app/9a6f85baeb1942d5ea5e2a015e5fd2e00a5cadf0/dist/keychain/cert_2020.p12 -------------------------------------------------------------------------------- /dist/lib/functions.sh: -------------------------------------------------------------------------------- 1 | sl_prepare_version() { 2 | vminor_t=$(printf "%02d" $vminor) 3 | version_t="v$vmajor.$vminor_t.$vpatch-$release" 4 | version_tc="v$vmajor.$vminor_t.$vpatch" 5 | version_n="$vmajor.$vminor_t.$vpatch" 6 | } 7 | 8 | sl_prepare() { 9 | echo "start build on $BUILD_TARGET ($BUILD_TARGET)" 10 | sed_opt="-i" 11 | 12 | sl_prepare_version 13 | 14 | mkdir -p build; 15 | pushd build 16 | mkdir -p my_include 17 | 18 | if [ -z "${GITHUB_REF}" ]; then 19 | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 20 | else 21 | GIT_BRANCH=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') 22 | fi 23 | 24 | SHASUM=$(which shasum) 25 | } 26 | 27 | sl_3rdparty() { 28 | #Get 3rdparty prebuilds 29 | if [ -f ../../3rdparty/build/$BUILD_TARGET.zip ]; then 30 | cp ../../3rdparty/build/$BUILD_TARGET.zip $BUILD_TARGET.zip 31 | else 32 | wget https://github.com/Studio-Link/3rdparty/releases/download/${sl3rdparty}/$BUILD_TARGET.zip 33 | fi 34 | unzip $BUILD_TARGET.zip 35 | } 36 | 37 | sl_get_webui() { 38 | s3_path="$GIT_BRANCH/$version_t" 39 | wget https://download.studio.link/devel/$s3_path/webui.zip 40 | unzip webui.zip 41 | mkdir -p ../src/modules/webapp/assets 42 | cp -a webui/headers/*.h ../src/modules/webapp/assets/ 43 | } 44 | 45 | sl_get_overlay-vst() { 46 | git clone https://github.com/Studio-Link/overlay-vst.git 47 | sed -i s/SLVMAJOR/$vmajor/ overlay-vst/version.h 48 | sed -i s/SLVMINOR/$vminor/ overlay-vst/version.h 49 | sed -i s/SLVPATCH/$vpatch/ overlay-vst/version.h 50 | wget https://download.studio.link/tools/$vstsdk.zip 51 | unzip -q $vstsdk.zip 52 | mv VST_SDK/VST2_SDK overlay-vst/vstsdk2.4 53 | if [ "$BUILD_TARGET" == "linux" ]; then 54 | pushd overlay-vst/vstsdk2.4 55 | patch --ignore-whitespace -p1 < ../vst2_linux.patch 56 | popd 57 | fi 58 | } 59 | 60 | sl_build_webui() { 61 | cp -a ../src/webui . 62 | pushd webui 63 | npm install 64 | npm run prod 65 | mkdir -p headers 66 | xxd -i dist/index.html > headers/index_html.h 67 | xxd -i dist/app.css > headers/css.h 68 | xxd -i dist/app.js > headers/js.h 69 | find dist/images -type f | xargs -I{} xxd -i {} > headers/images.h 70 | popd 71 | } 72 | 73 | sl_get_libre() { 74 | #wget -N "http://www.creytiv.com/pub/re-${re}.tar.gz" 75 | libre_url="https://github.com/baresip/re/archive/v" 76 | wget ${libre_url}${re}.tar.gz -O re-${re}.tar.gz 77 | tar -xzf re-${re}.tar.gz 78 | ln -s re-$re re 79 | pushd re 80 | patch --ignore-whitespace -p1 < ../../dist/patches/bluetooth_conflict.patch 81 | # patch --ignore-whitespace -p1 < ../../dist/patches/re_ice_bug.patch 82 | patch --ignore-whitespace -p1 < ../../dist/patches/re_fix_authorization.patch 83 | patch --ignore-whitespace -p1 < ../../dist/patches/re_pull_66.diff 84 | # patch --ignore-whitespace -p1 < ../../dist/patches/re_recv_handler_win_patch.patch 85 | if [ "$BUILD_TARGET" == "windows32" ] || [ "$BUILD_TARGET" == "windows64" ]; then 86 | patch -p1 < ../../dist/patches/fix_windows_ssize_t_bug.patch 87 | #patch -p1 < ../../dist/patches/re_wsapoll.patch 88 | fi 89 | popd 90 | } 91 | 92 | sl_get_librem() { 93 | #wget -N "http://www.creytiv.com/pub/rem-${rem}.tar.gz" 94 | wget "https://github.com/creytiv/rem/archive/v${rem}.tar.gz" -O rem-${rem}.tar.gz 95 | tar -xzf rem-${rem}.tar.gz 96 | ln -s rem-$rem rem 97 | } 98 | 99 | sl_get_baresip() { 100 | sl_get_webui 101 | #baresip_url="https://github.com/studio-link-3rdparty/baresip/archive/v" 102 | baresip_url="https://github.com/baresip/baresip/archive/v" 103 | 104 | wget ${baresip_url}${baresip}.tar.gz -O baresip-${baresip}.tar.gz 105 | tar -xzf baresip-${baresip}.tar.gz 106 | ln -s baresip-$baresip baresip 107 | pushd baresip-$baresip 108 | 109 | ## Add patches 110 | patch -p1 < ../../dist/patches/config.patch 111 | patch -p1 < ../../dist/patches/fix_check_telev_and_pthread.patch 112 | patch -p1 < ../../dist/patches/dtls_aes256.patch 113 | patch -p1 < ../../dist/patches/rtcp_mux_softphone.patch 114 | patch -p1 < ../../dist/patches/fallback_dns.patch 115 | patch -p1 < ../../dist/patches/baresip_audio_rtp_discard.patch 116 | #patch -p1 < ../../dist/patches/osx_sample_rate.patch 117 | 118 | #fixes multiple maxaverage lines in fmtp e.g.: 119 | #fmtp: stereo=1;sprop-stereo=1;maxaveragebitrate=64000;maxaveragebitrate=64000; 120 | #after multiple module reloads it crashes because fmtp is to small(256 chars) 121 | #patch -p1 < ../../dist/patches/opus_fmtp.patch 122 | 123 | ## Link backend modules 124 | rm -Rf modules/g722 125 | ln -s $(pwd -P)/../../src/modules/g722 modules/g722 126 | ln -s $(pwd -P)/../../src/modules/slogging modules/slogging 127 | ln -s $(pwd -P)/../../src/modules/webapp modules/webapp 128 | ln -s $(pwd -P)/../../src/modules/effect modules/effect 129 | ln -s $(pwd -P)/../../src/modules/effectonair modules/effectonair 130 | ln -s $(pwd -P)/../../src/modules/apponair modules/apponair 131 | ln -s $(pwd -P)/../../src/modules/slaudio modules/slaudio 132 | 133 | sed $sed_opt s/SLVERSION_T/$version_t/ modules/webapp/webapp.h 134 | sed $sed_opt s/BARESIP_VERSION\ \"$baresip_lib\"/BARESIP_VERSION\ \"$version_n\"/ include/baresip.h 135 | cp -a include/baresip.h ../my_include/ 136 | ln -s $(pwd -P)/../../src/modules/webapp/webapp.h ../my_include/webapp.h 137 | 138 | popd 139 | } 140 | -------------------------------------------------------------------------------- /dist/lib/versions.sh: -------------------------------------------------------------------------------- 1 | vmajor=21; vminor=12; vpatch=0 2 | vbuild="$(git rev-parse --short HEAD)" 3 | release="beta-${vbuild}" 4 | #release="stable" 5 | baresip="1.0.0" 6 | baresip_lib="1.0.0" 7 | re="1.1.0" 8 | rem="0.6.0" 9 | sl3rdparty="v22.03.0" 10 | overlay="v19.09.0" 11 | juce="6.0.7" 12 | github_org="https://github.com/Studio-Link" 13 | vstsdk="vstsdk367_03_03_2017_build_352" 14 | debug="RELEASE=1" 15 | -------------------------------------------------------------------------------- /dist/patches/baresip_audio_rtp_discard.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/audio.c b/src/audio.c 2 | index 90225d2..630b19c 100644 3 | --- a/src/audio.c 4 | +++ b/src/audio.c 5 | @@ -949,33 +949,7 @@ static void stream_recv_handler(const struct rtp_header *hdr, 6 | } 7 | 8 | /* Save timestamp for incoming RTP packets */ 9 | - 10 | - if (rx->ts_recv.is_set) { 11 | - 12 | - uint64_t ext_last, ext_now; 13 | - 14 | - ext_last = timestamp_calc_extended(rx->ts_recv.num_wraps, 15 | - rx->ts_recv.last); 16 | - 17 | - ext_now = timestamp_calc_extended(rx->ts_recv.num_wraps, 18 | - hdr->ts); 19 | - 20 | - if (ext_now <= ext_last) { 21 | - uint64_t delta; 22 | - 23 | - delta = ext_last - ext_now; 24 | - 25 | - warning("audio: [time=%.3f]" 26 | - " discard old frame (%.3f seconds old)\n", 27 | - aurx_calc_seconds(rx), 28 | - timestamp_calc_seconds(delta, rx->ac->crate)); 29 | - 30 | - discard = true; 31 | - } 32 | - } 33 | - else { 34 | - timestamp_set(&rx->ts_recv, hdr->ts); 35 | - } 36 | + timestamp_set(&rx->ts_recv, hdr->ts); 37 | 38 | wrap = timestamp_wrap(hdr->ts, rx->ts_recv.last); 39 | 40 | -------------------------------------------------------------------------------- /dist/patches/bluetooth_conflict.patch: -------------------------------------------------------------------------------- 1 | diff -Naur re-0.4.15/src/sdp/attr.c re-0.4.15_patched/src/sdp/attr.c 2 | --- re-0.4.15/src/sdp/attr.c 2012-04-10 11:32:34.000000000 +0200 3 | +++ re-0.4.15_patched/src/sdp/attr.c 2016-05-15 11:25:37.714468859 +0200 4 | @@ -31,7 +31,7 @@ 5 | } 6 | 7 | 8 | -int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val) 9 | +int sdp_attr_addp(struct list *lst, struct pl *name, struct pl *val) 10 | { 11 | struct sdp_attr *attr; 12 | int err; 13 | diff -Naur re-0.4.15/src/sdp/msg.c re-0.4.15_patched/src/sdp/msg.c 14 | --- re-0.4.15/src/sdp/msg.c 2014-06-03 13:39:20.000000000 +0200 15 | +++ re-0.4.15_patched/src/sdp/msg.c 2016-05-15 11:25:40.814453575 +0200 16 | @@ -119,7 +119,7 @@ 17 | *dir = SDP_SENDRECV; 18 | 19 | else 20 | - err = sdp_attr_add(m ? &m->rattrl : &sess->rattrl, 21 | + err = sdp_attr_addp(m ? &m->rattrl : &sess->rattrl, 22 | &name, &val); 23 | 24 | return err; 25 | diff -Naur re-0.4.15/src/sdp/sdp.h re-0.4.15_patched/src/sdp/sdp.h 26 | --- re-0.4.15/src/sdp/sdp.h 2014-06-03 13:39:20.000000000 +0200 27 | +++ re-0.4.15_patched/src/sdp/sdp.h 2016-05-15 11:25:39.214461492 +0200 28 | @@ -77,7 +77,7 @@ 29 | /* attribute */ 30 | struct sdp_attr; 31 | 32 | -int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val); 33 | +int sdp_attr_addp(struct list *lst, struct pl *name, struct pl *val); 34 | int sdp_attr_addv(struct list *lst, const char *name, const char *val, 35 | va_list ap); 36 | void sdp_attr_del(const struct list *lst, const char *name); 37 | -------------------------------------------------------------------------------- /dist/patches/dtls_aes256.patch: -------------------------------------------------------------------------------- 1 | diff --git a/modules/dtls_srtp/dtls_srtp.c b/modules/dtls_srtp/dtls_srtp.c 2 | index 4791e93..11ee248 100644 3 | --- a/modules/dtls_srtp/dtls_srtp.c 4 | +++ b/modules/dtls_srtp/dtls_srtp.c 5 | @@ -65,10 +65,10 @@ struct dtls_srtp { 6 | 7 | static struct tls *tls; 8 | static const char* srtp_profiles = 9 | - "SRTP_AES128_CM_SHA1_80:" 10 | - "SRTP_AES128_CM_SHA1_32:" 11 | + "SRTP_AEAD_AES_256_GCM:" 12 | "SRTP_AEAD_AES_128_GCM:" 13 | - "SRTP_AEAD_AES_256_GCM"; 14 | + "SRTP_AES128_CM_SHA1_80:" 15 | + "SRTP_AES128_CM_SHA1_32"; 16 | 17 | 18 | static void sess_destructor(void *arg) 19 | -------------------------------------------------------------------------------- /dist/patches/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | com.apple.security.cs.allow-dyld-environment-variables 12 | 13 | com.apple.security.cs.debugger 14 | 15 | com.apple.security.device.audio-input 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /dist/patches/fallback_dns.patch: -------------------------------------------------------------------------------- 1 | From 7762de0f5af65c0a620dea13d9156761a3346cc1 Mon Sep 17 00:00:00 2001 2 | From: Sebastian Reimers 3 | Date: Mon, 4 Jan 2021 23:40:29 +0100 4 | Subject: [PATCH] net: make fallback DNS ignored message debug only 5 | 6 | --- 7 | src/net.c | 2 +- 8 | 1 file changed, 1 insertion(+), 1 deletion(-) 9 | 10 | diff --git a/src/net.c b/src/net.c 11 | index c8d7f48f7..3dc87f46b 100644 12 | --- a/src/net.c 13 | +++ b/src/net.c 14 | @@ -170,7 +170,7 @@ static int net_dns_srv_get(const struct network *net, 15 | if (net->nsnf) { 16 | offset = *n; 17 | if ((offset + net->nsnf) > limit) { 18 | - warning("net: too many DNS nameservers, " 19 | + debug("net: too many DNS nameservers, " 20 | "fallback DNS ignored\n"); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /dist/patches/fix_check_telev.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/audio.c b/src/audio.c 2 | index 11e3b3f..bf0a365 100644 3 | --- a/src/audio.c 4 | +++ b/src/audio.c 5 | @@ -663,10 +663,10 @@ static void ausrc_read_handler(const void *sampv, size_t sampc, void *arg) 6 | 7 | poll_aubuf_tx(a); 8 | } 9 | + /* Exact timing: send Telephony-Events from here */ 10 | + check_telev(a, tx); 11 | } 12 | 13 | - /* Exact timing: send Telephony-Events from here */ 14 | - check_telev(a, tx); 15 | } 16 | 17 | 18 | @@ -1267,6 +1267,9 @@ static void *tx_thread(void *arg) 19 | " (total %llu)\n", tx->stats.aubuf_underrun); 20 | } 21 | 22 | + /* Exact timing: send Telephony-Events from here */ 23 | + check_telev(a, tx); 24 | + 25 | ts += tx->ptime; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /dist/patches/fix_check_telev_and_pthread.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/audio.c b/src/audio.c 2 | index 7aa2480..36bfb09 100644 3 | --- a/src/audio.c 4 | +++ b/src/audio.c 5 | @@ -11,9 +11,7 @@ 6 | #ifdef HAVE_UNISTD_H 7 | #include 8 | #endif 9 | -#ifdef HAVE_PTHREAD 10 | #include 11 | -#endif 12 | #include 13 | #include 14 | #include 15 | @@ -110,14 +108,12 @@ struct autx { 16 | uint64_t aubuf_underrun; 17 | } stats; 18 | 19 | -#ifdef HAVE_PTHREAD 20 | union { 21 | struct { 22 | pthread_t tid;/**< Audio transmit thread */ 23 | bool run; /**< Audio transmit thread running */ 24 | } thr; 25 | } u; 26 | -#endif 27 | }; 28 | 29 | 30 | @@ -224,14 +220,12 @@ static void stop_tx(struct autx *tx, struct audio *a) 31 | 32 | switch (a->cfg.txmode) { 33 | 34 | -#ifdef HAVE_PTHREAD 35 | case AUDIO_MODE_THREAD: 36 | if (tx->u.thr.run) { 37 | tx->u.thr.run = false; 38 | pthread_join(tx->u.thr.tid, NULL); 39 | } 40 | break; 41 | -#endif 42 | default: 43 | break; 44 | } 45 | @@ -672,10 +666,10 @@ static void ausrc_read_handler(const void *sampv, size_t sampc, void *arg) 46 | 47 | poll_aubuf_tx(a); 48 | } 49 | + /* Exact timing: send Telephony-Events from here */ 50 | + check_telev(a, tx); 51 | } 52 | 53 | - /* Exact timing: send Telephony-Events from here */ 54 | - check_telev(a, tx); 55 | } 56 | 57 | 58 | @@ -1180,7 +1174,6 @@ int audio_alloc(struct audio **ap, struct list *streaml, 59 | } 60 | 61 | 62 | -#ifdef HAVE_PTHREAD 63 | static void *tx_thread(void *arg) 64 | { 65 | struct audio *a = arg; 66 | @@ -1219,12 +1212,14 @@ static void *tx_thread(void *arg) 67 | " (total %llu)\n", tx->stats.aubuf_underrun); 68 | } 69 | 70 | + /* Exact timing: send Telephony-Events from here */ 71 | + check_telev(a, tx); 72 | + 73 | ts += tx->ptime; 74 | } 75 | 76 | return NULL; 77 | } 78 | -#endif 79 | 80 | 81 | static void aufilt_param_set(struct aufilt_prm *prm, 82 | @@ -1542,7 +1537,6 @@ static int start_source(struct autx *tx, struct audio *a) 83 | case AUDIO_MODE_POLL: 84 | break; 85 | 86 | -#ifdef HAVE_PTHREAD 87 | case AUDIO_MODE_THREAD: 88 | if (!tx->u.thr.run) { 89 | tx->u.thr.run = true; 90 | @@ -1554,7 +1548,6 @@ static int start_source(struct autx *tx, struct audio *a) 91 | } 92 | } 93 | break; 94 | -#endif 95 | 96 | default: 97 | warning("audio: tx mode not supported (%d)\n", 98 | -------------------------------------------------------------------------------- /dist/patches/fix_windows_ssize_t_bug.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/re_types.h b/include/re_types.h 2 | index b3d3922..4edae22 100644 3 | --- a/include/re_types.h 4 | +++ b/include/re_types.h 5 | @@ -47,10 +47,6 @@ typedef unsigned long long int uint64_t; 6 | #endif /* __BIT_TYPES_DEFINED__ */ 7 | 8 | #endif /* __int8_t_defined */ 9 | -#ifndef __ssize_t_defined 10 | -typedef long ssize_t; 11 | -#define __ssize_t_defined 12 | -#endif 13 | 14 | #ifndef WIN32 15 | typedef uint32_t socklen_t; 16 | -------------------------------------------------------------------------------- /dist/patches/opus_fmtp.patch: -------------------------------------------------------------------------------- 1 | diff --git a/modules/opus/opus.c b/modules/opus/opus.c 2 | index d1e9c24..b794533 100644 3 | --- a/modules/opus/opus.c 4 | +++ b/modules/opus/opus.c 5 | @@ -36,7 +36,7 @@ 6 | 7 | 8 | static bool opus_mirror; 9 | -static char fmtp[256] = "stereo=1;sprop-stereo=1"; 10 | +static char fmtp[256]; 11 | static char fmtp_mirror[256]; 12 | 13 | 14 | @@ -88,10 +88,17 @@ static int module_init(void) 15 | { 16 | struct conf *conf = conf_cur(); 17 | uint32_t value; 18 | - char *p = fmtp + str_len(fmtp); 19 | + char *p = fmtp; 20 | bool b; 21 | int n = 0; 22 | 23 | + n = re_snprintf(p, sizeof(fmtp) - str_len(p), 24 | + "stereo=1;sprop-stereo=1"); 25 | + if (n <= 0) 26 | + return ENOMEM; 27 | + p += n; 28 | + 29 | + 30 | if (0 == conf_get_u32(conf, "opus_bitrate", &value)) { 31 | 32 | n = re_snprintf(p, sizeof(fmtp) - str_len(p), 33 | -------------------------------------------------------------------------------- /dist/patches/osx_sample_rate.patch: -------------------------------------------------------------------------------- 1 | From 9cfc8a683a136bbba6d627d69afb71d96dc17224 Mon Sep 17 00:00:00 2001 2 | From: Sebastian Reimers 3 | Date: Sun, 9 Oct 2016 13:41:17 +0200 4 | Subject: [PATCH] add samplerate patch 5 | 6 | --- 7 | modules/audiounit/recorder.c | 20 ++++++++++++++++++++ 8 | 1 file changed, 20 insertions(+) 9 | 10 | diff --git a/modules/audiounit/recorder.c b/modules/audiounit/recorder.c 11 | index cf6a1af..1dcf601 100644 12 | --- a/modules/audiounit/recorder.c 13 | +++ b/modules/audiounit/recorder.c 14 | @@ -114,6 +114,11 @@ int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, 15 | kAudioHardwarePropertyDefaultInputDevice, 16 | kAudioObjectPropertyScopeGlobal, 17 | kAudioObjectPropertyElementMaster }; 18 | + AudioObjectPropertyAddress srateAddress = { 19 | + kAudioDevicePropertyNominalSampleRate, 20 | + kAudioObjectPropertyScopeGlobal, 21 | + kAudioObjectPropertyElementMaster }; 22 | + Float64 sampleRate = 48000.0; 23 | #endif 24 | Float64 hw_srate = 0.0; 25 | UInt32 hw_size = sizeof(hw_srate); 26 | @@ -176,6 +181,21 @@ int audiounit_recorder_alloc(struct ausrc_st **stp, const struct ausrc *as, 27 | sizeof(inputDevice)); 28 | if (ret) 29 | goto out; 30 | + 31 | + ret = AudioObjectSetPropertyData(inputDevice, 32 | + &srateAddress, 33 | + 0, 34 | + NULL, 35 | + sizeof(sampleRate), 36 | + &sampleRate); 37 | + if (!ret) { 38 | + ret = AudioUnitSetProperty(st->au, 39 | + kAudioUnitProperty_SampleRate, 40 | + kAudioUnitScope_Input, 41 | + 0, 42 | + &sampleRate, 43 | + sizeof(sampleRate)); 44 | + } 45 | #endif 46 | 47 | fmt.mSampleRate = prm->srate; 48 | -------------------------------------------------------------------------------- /dist/patches/re_fix_authorization.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/sip/auth.c b/src/sip/auth.c 2 | index 1357cad1..21e9c3c1 100644 3 | --- a/src/sip/auth.c 4 | +++ b/src/sip/auth.c 5 | @@ -161,7 +161,9 @@ static bool auth_handler(const struct sip_hdr *hdr, const struct sip_msg *msg, 6 | goto out; 7 | } 8 | else { 9 | - if (!pl_isset(&ch.stale) || pl_strcasecmp(&ch.stale, "true")) { 10 | + /* error if first auth attempt fails */ 11 | + if ((!pl_isset(&ch.stale) || 12 | + pl_strcasecmp(&ch.stale, "true")) && (realm->nc == 2)) { 13 | err = EAUTH; 14 | goto out; 15 | } 16 | -------------------------------------------------------------------------------- /dist/patches/re_ice_bug.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/ice/comp.c b/src/ice/comp.c 2 | index 4454dd9..782e62d 100644 3 | --- a/src/ice/comp.c 4 | +++ b/src/ice/comp.c 5 | @@ -203,6 +203,7 @@ void icem_comp_set_selected(struct icem_comp *comp, struct ice_candpair *cp) 6 | DEBUG_WARNING("{%s.%u} set_selected: invalid state %s\n", 7 | comp->icem->name, comp->id, 8 | ice_candpair_state2name(cp->state)); 9 | + return; 10 | } 11 | 12 | mem_deref(comp->cp_sel); 13 | -------------------------------------------------------------------------------- /dist/patches/re_pull_66.diff: -------------------------------------------------------------------------------- 1 | diff --git a/include/re_net.h b/include/re_net.h 2 | index 04d87f3..eb0aa45 100644 3 | --- a/include/re_net.h 4 | +++ b/include/re_net.h 5 | @@ -40,6 +40,7 @@ struct sa; 6 | 7 | /* Net generic */ 8 | int net_hostaddr(int af, struct sa *ip); 9 | +int net_dst_source_addr_get(struct sa *dst, struct sa *ip); 10 | int net_default_source_addr_get(int af, struct sa *ip); 11 | int net_default_gateway_get(int af, struct sa *gw); 12 | 13 | diff --git a/src/net/net.c b/src/net/net.c 14 | index 0978b1b..4c38eb2 100644 15 | --- a/src/net/net.c 16 | +++ b/src/net/net.c 17 | @@ -17,6 +17,8 @@ 18 | #include 19 | #include 20 | #include 21 | +#include 22 | +#include 23 | 24 | 25 | #define DEBUG_MODULE "net" 26 | @@ -56,6 +58,43 @@ int net_hostaddr(int af, struct sa *ip) 27 | } 28 | 29 | 30 | +/** 31 | + * Get the source IP address for a specified destination 32 | + * 33 | + * @param dst Destination IP address 34 | + * @param ip Returned IP address 35 | + * 36 | + * @return 0 if success, otherwise errorcode 37 | + */ 38 | +int net_dst_source_addr_get(struct sa *dst, struct sa *ip) 39 | +{ 40 | + int err; 41 | + struct udp_sock *us; 42 | + 43 | + if (sa_af(dst) == AF_INET6) 44 | + err = sa_set_str(ip, "::", 0); 45 | + else 46 | + err = sa_set_str(ip, "0.0.0.0", 0); 47 | + 48 | + if (err) 49 | + return err; 50 | + 51 | + err = udp_listen(&us, ip, NULL, NULL); 52 | + if (err) 53 | + return err; 54 | + 55 | + err = udp_connect(us, dst); 56 | + if (err) 57 | + goto out; 58 | + 59 | + err = udp_local_get(us, ip); 60 | + 61 | +out: 62 | + mem_deref(us); 63 | + return err; 64 | +} 65 | + 66 | + 67 | /** 68 | * Get the default source IP address 69 | * 70 | @@ -66,10 +105,34 @@ int net_hostaddr(int af, struct sa *ip) 71 | */ 72 | int net_default_source_addr_get(int af, struct sa *ip) 73 | { 74 | + struct sa dst; 75 | + int err; 76 | +#if !defined(WIN32) 77 | + char ifname[64] = ""; 78 | +#endif 79 | + 80 | + sa_init(&dst, af); 81 | + 82 | + if (af == AF_INET6) 83 | + err = sa_set_str(&dst, "1::1", 53); 84 | + else 85 | + err = sa_set_str(&dst, "1.1.1.1", 53); 86 | + 87 | + err = net_dst_source_addr_get(&dst, ip); 88 | + 89 | + if (af == AF_INET6) { 90 | + if (sa_is_linklocal(ip)) { 91 | + sa_init(ip, af); 92 | + return 0; 93 | + } 94 | + } 95 | + 96 | + if (!err) 97 | + return 0; 98 | + 99 | #if defined(WIN32) 100 | return net_hostaddr(af, ip); 101 | #else 102 | - char ifname[64] = ""; 103 | 104 | #ifdef HAVE_ROUTE_LIST 105 | /* Get interface with default route */ 106 | -------------------------------------------------------------------------------- /dist/patches/re_recv_handler_win_patch.patch: -------------------------------------------------------------------------------- 1 | From aad58ee79c5b06cf6b2b5a36e1e77ad102efd984 Mon Sep 17 00:00:00 2001 2 | From: Sebastian Reimers 3 | Date: Thu, 26 Mar 2020 17:11:03 +0100 4 | Subject: [PATCH] tcp: close socket on windows if connection is aborted or 5 | reset 6 | 7 | --- 8 | src/tcp/tcp.c | 10 ++++++++++ 9 | 1 file changed, 10 insertions(+) 10 | 11 | diff --git a/src/tcp/tcp.c b/src/tcp/tcp.c 12 | index 1ca98edc..4b6b969f 100644 13 | --- a/src/tcp/tcp.c 14 | +++ b/src/tcp/tcp.c 15 | @@ -377,7 +377,17 @@ static void tcp_recv_handler(int flags, void *arg) 16 | return; 17 | } 18 | else if (n < 0) { 19 | +#ifdef WIN32 20 | + err = WSAGetLastError(); 21 | + DEBUG_WARNING("recv handler: recv(): %d\n", err); 22 | + if (err == WSAECONNRESET || err == WSAECONNABORTED) { 23 | + mem_deref(mb); 24 | + conn_close(tc, err); 25 | + return; 26 | + } 27 | +#else 28 | DEBUG_WARNING("recv handler: recv(): %m\n", errno); 29 | +#endif 30 | goto out; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /dist/patches/re_wsapoll.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/main/main.c b/src/main/main.c 2 | index b90c139..01baea2 100644 3 | --- a/src/main/main.c 4 | +++ b/src/main/main.c 5 | @@ -22,8 +22,14 @@ 6 | #include 7 | #endif 8 | #ifdef HAVE_POLL 9 | +#ifdef HAVE_WSAPOLL 10 | +#define _WIN32_WINNT 0x601 11 | +#define WINVER 0x601 12 | +#include 13 | +#else 14 | #include 15 | #endif 16 | +#endif 17 | #ifdef HAVE_EPOLL 18 | #include 19 | #endif 20 | @@ -101,8 +107,12 @@ struct re { 21 | struct list tmrl; /**< List of timers */ 22 | 23 | #ifdef HAVE_POLL 24 | +#ifdef HAVE_WSAPOLL 25 | + WSAPOLLFD *fds; 26 | +#else 27 | struct pollfd *fds; /**< Event set for poll() */ 28 | #endif 29 | +#endif 30 | 31 | #ifdef HAVE_EPOLL 32 | struct epoll_event *events; /**< Event set for epoll() */ 33 | @@ -669,7 +679,14 @@ static int fd_poll(struct re *re) 34 | #ifdef HAVE_POLL 35 | case METHOD_POLL: 36 | re_unlock(re); 37 | +#ifdef HAVE_WSAPOLL 38 | + /* Timer Workaround for 39 | + * https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ 40 | + */ 41 | + n = WSAPoll(re->fds, re->nfds, to ? (int)to : 1000); 42 | +#else 43 | n = poll(re->fds, re->nfds, to ? (int)to : -1); 44 | +#endif 45 | re_lock(re); 46 | break; 47 | #endif 48 | @@ -755,11 +772,13 @@ static int fd_poll(struct re *re) 49 | if (re->fds[fd].revents & (POLLERR|POLLHUP|POLLNVAL)) 50 | flags |= FD_EXCEPT; 51 | if (re->fds[fd].revents & POLLNVAL) { 52 | +#ifndef HAVE_WSAPOLL 53 | DEBUG_WARNING("event: fd=%d POLLNVAL" 54 | " (fds.fd=%d," 55 | " fds.events=0x%02x)\n", 56 | fd, re->fds[fd].fd, 57 | re->fds[fd].events); 58 | +#endif 59 | } 60 | /* Clear events */ 61 | re->fds[fd].revents = 0; 62 | -------------------------------------------------------------------------------- /dist/patches/rtcp_mux_softphone.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/stream.c b/src/stream.c 2 | index 3634f0b..4e14df5 100644 3 | --- a/src/stream.c 4 | +++ b/src/stream.c 5 | @@ -470,6 +470,7 @@ int stream_alloc(struct stream **sp, struct list *streaml, 6 | { 7 | struct stream *s; 8 | int err; 9 | + bool rtcp_mux; 10 | 11 | if (!sp || !prm || !cfg || !rtph) 12 | return EINVAL; 13 | @@ -478,6 +479,14 @@ int stream_alloc(struct stream **sp, struct list *streaml, 14 | if (!s) 15 | return ENOMEM; 16 | 17 | + if (mnat && str_cmp(mnat->id, "ice") == 0) { 18 | + rtcp_mux = true; 19 | + } 20 | + else 21 | + rtcp_mux = cfg->rtcp_mux; 22 | + 23 | + warning("rtcp_mux %d\n", rtcp_mux); 24 | + 25 | MAGIC_INIT(s); 26 | 27 | s->cfg = *cfg; 28 | @@ -532,7 +541,7 @@ int stream_alloc(struct stream **sp, struct list *streaml, 29 | rtp_sess_ssrc(s->rtp), prm->cname); 30 | 31 | /* RFC 5761 */ 32 | - if (cfg->rtcp_mux && 33 | + if (rtcp_mux && 34 | (offerer || sdp_media_rattr(s->sdp, "rtcp-mux"))) { 35 | 36 | err |= sdp_media_set_lattr(s->sdp, true, "rtcp-mux", NULL); 37 | @@ -545,7 +554,7 @@ int stream_alloc(struct stream **sp, struct list *streaml, 38 | s->mnat = mnat; 39 | err = mnat->mediah(&s->mns, mnat_sess, 40 | rtp_sock(s->rtp), 41 | - cfg->rtcp_mux ? NULL : rtcp_sock(s->rtp), 42 | + rtcp_mux ? NULL : rtcp_sock(s->rtp), 43 | s->sdp, mnat_connected_handler, s); 44 | if (err) 45 | goto out; 46 | @@ -630,11 +639,19 @@ int stream_send(struct stream *s, bool ext, bool marker, int pt, uint32_t ts, 47 | 48 | static void stream_remote_set(struct stream *s) 49 | { 50 | + bool rtcp_mux; 51 | + 52 | if (!s) 53 | return; 54 | 55 | + if (s->mnat && str_cmp(s->mnat->id, "ice") == 0) { 56 | + rtcp_mux = true; 57 | + } 58 | + else 59 | + rtcp_mux = s->cfg.rtcp_mux; 60 | + 61 | /* RFC 5761 */ 62 | - if (s->cfg.rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) { 63 | + if (rtcp_mux && sdp_media_rattr(s->sdp, "rtcp-mux")) { 64 | 65 | if (!s->rtcp_mux) 66 | info("%s: RTP/RTCP multiplexing enabled\n", 67 | -------------------------------------------------------------------------------- /dist/tools/build_debug.sh: -------------------------------------------------------------------------------- 1 | sl_extra_lflags="-L ../opus -L ../my_include " 2 | sl_extra_modules="alsa jack slaudio slogging dtls_srtp menu" 3 | make_opt="-j4 SYSROOT_ALT=\"../3rdparty\"" 4 | 5 | if [ "$1" == "" ]; then 6 | export CC=gcc 7 | make $make_opt RELEASE=1 LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 8 | MODULES="opus stdio ice g711 g722 turn stun uuid auloop webapp menu $sl_extra_modules" \ 9 | EXTRA_CFLAGS="-I ../my_include" \ 10 | EXTRA_LFLAGS="$sl_extra_lflags -L ../openssl" 11 | fi 12 | 13 | if [ "$1" == "assembler" ]; then 14 | export CC=gcc 15 | make $make_opt RELEASE=1 LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 16 | MODULES="opus stdio ice g711 g722 turn stun uuid auloop webapp menu $sl_extra_modules" \ 17 | EXTRA_CFLAGS="-I ../my_include -save-temps=obj" \ 18 | EXTRA_LFLAGS="$sl_extra_lflags -L ../openssl" 19 | fi 20 | 21 | if [ "$1" == "stack" ]; then 22 | export CC=gcc 23 | make $make_opt RELEASE=1 LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 24 | MODULES="opus stdio ice g711 g722 turn stun uuid auloop webapp menu $sl_extra_modules" \ 25 | EXTRA_CFLAGS="-I ../my_include -Wstack-usage=4096 -fstack-usage" \ 26 | EXTRA_LFLAGS="$sl_extra_lflags -L ../openssl" 27 | fi 28 | 29 | if [ "$1" == "lib" ]; then 30 | make $make_opt USE_OPENSSL="yes" LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 31 | MODULES="opus stdio ice g711 turn stun uuid auloop webapp effect" \ 32 | EXTRA_CFLAGS="-I ../my_include -DSLPLUGIN" \ 33 | EXTRA_LFLAGS="$sl_extra_lflags" libbaresip.a 34 | fi 35 | 36 | if [ "$1" == "scan" ]; then 37 | export CC=clang 38 | scan-build make $make_opt RELEASE=1 LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 39 | MODULES="opus stdio ice g711 g722 turn stun uuid auloop webapp menu $sl_extra_modules" \ 40 | EXTRA_CFLAGS="-I ../my_include" \ 41 | EXTRA_LFLAGS="$sl_extra_lflags -L ../openssl" 42 | fi 43 | 44 | if [ "$1" == "llvm" ]; then 45 | export CC=clang 46 | make $make_opt RELEASE=1 LIBRE_SO=../re LIBREM_PATH=../rem STATIC=1 \ 47 | MODULES="opus stdio ice g711 g722 turn stun uuid auloop webapp menu $sl_extra_modules" \ 48 | EXTRA_CFLAGS="-I ../my_include" \ 49 | EXTRA_LFLAGS="$sl_extra_lflags -L ../openssl -fsanitize=address -fno-omit-frame-pointer" 50 | 51 | # ASAN_OPTIONS=fast_unwind_on_malloc=0 ./baresip 52 | fi 53 | -------------------------------------------------------------------------------- /dist/tools/ccheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | dist/tools/ccheck.py src/modules/slaudio 4 | dist/tools/ccheck.py src/modules/slogging 5 | dist/tools/ccheck.py src/modules/webapp 6 | dist/tools/ccheck.py src/modules/effect 7 | -------------------------------------------------------------------------------- /dist/tools/downloads: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dist/tools/s3urls | grep $(dist/version.sh) 4 | -------------------------------------------------------------------------------- /dist/tools/jitter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dev="wlp4s0" 4 | 5 | function setup() { 6 | sudo ip link add ifb1 type ifb || : 7 | sudo ip link set ifb1 up 8 | sudo tc qdisc add dev $dev handle ffff: ingress 9 | sudo tc filter add dev $dev parent ffff: u32 match u32 0 0 action mirred egress redirect dev ifb1 10 | } 11 | 12 | function delay() { 13 | if [[ -z $1 ]]; then 14 | jitter=100 15 | else 16 | jitter=$1 17 | fi 18 | sudo tc qdisc add dev ifb1 root netem delay 10ms 19 | for i in {1..100} 20 | do 21 | sudo tc qdisc change dev ifb1 root netem delay ${jitter}ms 22 | echo "delay active" 23 | sleep 1 24 | sudo tc qdisc change dev ifb1 root netem delay 10ms 25 | # sudo tc qdisc del dev ifb1 root 26 | echo "delay inactive" 27 | sleep 5 28 | done 29 | } 30 | 31 | function reorder() { 32 | if [[ -z $1 ]]; then 33 | jitter=100 34 | else 35 | jitter=$1 36 | fi 37 | sudo tc qdisc add dev ifb1 root netem delay ${jitter}ms 10ms 38 | echo "reorder active" 39 | sleep 10 40 | sudo tc qdisc del dev ifb1 root 41 | echo "reorder inactive" 42 | sleep 5 43 | } 44 | 45 | if [[ "$1" == "setup" ]]; then 46 | setup 47 | exit 0 48 | fi 49 | 50 | if [[ "$1" == "delay" ]]; then 51 | delay $2 52 | exit 0 53 | fi 54 | 55 | if [[ "$1" == "reorder" ]]; then 56 | reorder $2 57 | exit 0 58 | fi 59 | -------------------------------------------------------------------------------- /dist/tools/s3urls: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import urllib.request 4 | import xml.etree.ElementTree as ET 5 | 6 | response = urllib.request.urlopen('https://s3.eu-central-1.amazonaws.com/studio-link-artifacts/') 7 | xml = response.read() 8 | root = ET.fromstring(xml) 9 | 10 | for content in root.findall('{http://s3.amazonaws.com/doc/2006-03-01/}Contents'): 11 | for key in content.findall('{http://s3.amazonaws.com/doc/2006-03-01/}Key'): 12 | print('https://s3.eu-central-1.amazonaws.com/studio-link-artifacts/' + key.text) 13 | -------------------------------------------------------------------------------- /dist/tools/sl_trace/sl_trace.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 199309L 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "sl_trace.h" 11 | 12 | static uint64_t profile_start, profile_diff; 13 | static uint64_t profile_max = 0, profile_sum = 0, profile_min = 0; 14 | static int counter = 0; 15 | static struct tmr tmr; 16 | static int trace_arg; 17 | 18 | 19 | static uint64_t microseconds(void) 20 | { 21 | uint64_t usecs; 22 | struct timespec now; 23 | 24 | clock_gettime(CLOCK_MONOTONIC_RAW, &now); 25 | 26 | usecs = (long)now.tv_sec * (uint64_t)1000000; 27 | usecs += (long)now.tv_nsec/1000; 28 | 29 | return usecs; 30 | } 31 | 32 | 33 | static void sl_trace_reset(void) 34 | { 35 | profile_sum = 0; 36 | profile_max = 0; 37 | profile_min = 0; 38 | counter = 0; 39 | } 40 | 41 | 42 | void sl_trace_start(void) 43 | { 44 | profile_start = microseconds(); 45 | } 46 | 47 | 48 | void sl_trace_end(int arg) 49 | { 50 | uint64_t jfs; 51 | 52 | jfs = microseconds(); 53 | ++counter; 54 | 55 | profile_diff = jfs - profile_start; 56 | 57 | if (profile_diff > profile_max) { 58 | profile_max = profile_diff; 59 | trace_arg = arg; 60 | } 61 | 62 | if (profile_diff < profile_min || !profile_min) { 63 | profile_min = profile_diff; 64 | } 65 | 66 | profile_sum += profile_diff; 67 | } 68 | 69 | 70 | void sl_trace_debug(void) 71 | { 72 | double profile_avg = 0.0; 73 | if (counter) 74 | profile_avg = profile_sum/counter; 75 | 76 | printf("SL_TRACE_MAX: (min)%4ld (max)%4ld (avg)%5.2f (arg)%d (count)%3d\n", 77 | profile_min, profile_max, profile_avg, trace_arg, counter); 78 | sl_trace_reset(); 79 | } 80 | 81 | 82 | void sl_trace_debug_tmr(void *arg) 83 | { 84 | sl_trace_debug(); 85 | tmr_init(&tmr); 86 | tmr_start(&tmr, 250, sl_trace_debug_tmr, NULL); 87 | } 88 | -------------------------------------------------------------------------------- /dist/tools/sl_trace/sl_trace.h: -------------------------------------------------------------------------------- 1 | void sl_trace_start(void); 2 | void sl_trace_end(int arg); 3 | void sl_trace_debug(void); 4 | void sl_trace_debug_tmr(void *arg); 5 | -------------------------------------------------------------------------------- /dist/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source dist/lib/versions.sh 4 | source dist/lib/functions.sh 5 | 6 | sl_prepare_version 7 | 8 | echo $version_t 9 | -------------------------------------------------------------------------------- /dist/windows/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile 3 | # 4 | # Copyright (C) 2010 - 2016 Creytiv.com 5 | # 6 | 7 | ifeq ($(BUILD_TARGET),windows64) 8 | TUPLE := x86_64-w64-mingw32 9 | SSLARCH := mingw64 10 | endif 11 | ifeq ($(BUILD_TARGET),windows32) 12 | TUPLE := i686-w64-mingw32 13 | SSLARCH := mingw 14 | endif 15 | 16 | 17 | # Tools 18 | SYSROOT := /usr/$(TUPLE) 19 | 20 | CC := $(TUPLE)-gcc 21 | CXX := $(TUPLE)-g++ 22 | RANLIB := $(TUPLE)-ranlib 23 | AR := $(TUPLE)-ar 24 | PWD := $(shell pwd) 25 | 26 | 27 | # Compiler and Linker Flags 28 | # 29 | 30 | CFLAGS := -DFD_SETSIZE=8192 \ 31 | -isystem $(PWD)/3rdparty/include -I$(PWD) -I $(PWD)/my_include -g -gdwarf-2 32 | 33 | LFLAGS := -L$(SYSROOT)/lib/ -L$(PWD)/3rdparty/lib/ -L$(PWD)/my_include -L$(PWD)/rem -fstack-protector 34 | 35 | # workaround for linker order (note, the order is important) 36 | LIBS := -lrem -lssl -lcrypto -lwsock32 -lws2_32 -liphlpapi -lcrypt32 -lopus -lFLAC -lsamplerate -lsoundio -lstdc++ -lole32 -lwinmm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid -ldsound 37 | 38 | 39 | COMMON_FLAGS := CC=$(CC) \ 40 | CXX=$(CXX) \ 41 | RANLIB=$(RANLIB) \ 42 | EXTRA_CFLAGS="$(CFLAGS)" \ 43 | EXTRA_LFLAGS="$(LFLAGS) -static" \ 44 | LIBS="$(LIBS)" \ 45 | SYSROOT=$(SYSROOT) \ 46 | SYSROOT_ALT="" \ 47 | HAVE_GETOPT=1 \ 48 | HAVE_LIBRESOLV= \ 49 | HAVE_PTHREAD= \ 50 | HAVE_PTHREAD_RWLOCK= \ 51 | HAVE_LIBPTHREAD= \ 52 | HAVE_INET_PTON=1 \ 53 | HAVE_INET6=1 \ 54 | PEDANTIC= \ 55 | OPT_SIZE=1 \ 56 | OS=win32 \ 57 | USE_OPENSSL=yes \ 58 | USE_OPENSSL_DTLS=yes \ 59 | USE_OPENSSL_SRTP=yes \ 60 | USE_ZLIB= \ 61 | -j4 \ 62 | RELEASE=1 63 | 64 | #default: retest baresip 65 | default: baresip 66 | 67 | libre.a: Makefile 68 | @rm -f re/libre.* 69 | make $@ -C re $(COMMON_FLAGS) 70 | 71 | librem.a: Makefile libre.a 72 | @rm -f rem/librem.* 73 | @make $@ -C rem $(COMMON_FLAGS) 74 | 75 | .PHONY: retest 76 | test: retest 77 | retest: Makefile librem.a libre.a 78 | @rm -f retest/retest 79 | make -C retest $(COMMON_FLAGS) LIBRE_SO=$(PWD)/re \ 80 | LIBREM_PATH=$(PWD)/rem 81 | cd retest && $(WINE) retest -r 82 | 83 | .PHONY: baresip 84 | baresip: Makefile librem.a libre.a 85 | @rm -f baresip/baresip.exe baresip/src/static.c 86 | PKG_CONFIG_LIBDIR="$(SYSROOT)/lib/pkgconfig" \ 87 | make baresip.exe -C baresip $(COMMON_FLAGS) STATIC=1 \ 88 | LIBRE_SO=$(PWD)/re LIBREM_PATH=$(PWD)/rem \ 89 | MODULES="opus wincons ice g711 turn stun uuid auloop webapp winwave slaudio slogging g722 dtls_srtp" 90 | cp -a baresip/baresip.exe studio-link-standalone.exe 91 | make clean -C baresip $(COMMON_FLAGS) 92 | make libbaresip.a -C baresip $(COMMON_FLAGS) STATIC=1 \ 93 | LIBRE_SO=$(PWD)/re LIBREM_PATH=$(PWD)/rem \ 94 | MODULES="opus ice g711 turn stun uuid apponair effectonair slogging g722 dtls_srtp" \ 95 | EXTRA_CFLAGS="$(CFLAGS) -DSLIVE" 96 | cp -a baresip/libbaresip.a libbaresip_onair.a 97 | make clean -C baresip $(COMMON_FLAGS) 98 | make libbaresip.a -C baresip $(COMMON_FLAGS) STATIC=1 \ 99 | LIBRE_SO=$(PWD)/re LIBREM_PATH=$(PWD)/rem \ 100 | MODULES="opus ice g711 turn stun uuid webapp effect slogging g722 dtls_srtp" \ 101 | EXTRA_CFLAGS="$(CFLAGS) -DSLPLUGIN" 102 | 103 | .PHONY: openssl 104 | openssl: 105 | cd openssl && \ 106 | CC=$(CC) RANLIB=$(RANLIB) AR=$(AR) \ 107 | ./Configure $(SSLARCH) no-shared no-threads && \ 108 | make build_libs -j4 109 | 110 | clean: 111 | make distclean -C baresip 112 | make distclean -C retest 113 | make distclean -C rem 114 | make distclean -C re 115 | 116 | info: 117 | make $@ -C re $(COMMON_FLAGS) 118 | 119 | dump: 120 | @echo "TUPLE = $(TUPLE)" 121 | @echo "WINE = $(WINE)" 122 | -------------------------------------------------------------------------------- /docs/dev/timing.drawio: -------------------------------------------------------------------------------- 1 | 7Vxdd5s4EP01fkwOSHw+Nk6atpuc5KzTk+6+9MggbJ1i5MVyPvrrF2ywQSPHTgwyTpqXmAGEdK/mzjCS3cP9ydNlSqbjax7SuIeM8KmHz3sImaZhZf9yy/PS4ltoaRilLCwuWhsG7DctjEZhnbOQzmoXCs5jwaZ1Y8CThAaiZiNpyh/rl0U8rj91SkYUGAYBiaH1noVivLR6yF3bv1A2GpdPNh1/eWZCyouLkczGJOSPFRO+6OF+yrlYfpo89Wmcg1fisrzv84azq46lNBG73HDiuD/6J//cD6l9GVupJR7i85OilQcSz4sBF50VzyUCNAk/5UBmRwlPMuNZSGZjmrdqZgdjMYmLj7n9lghB02RhQQbOrDOR8l8r8HIL7HnJLElHVLzQXVzATsMaa8WALymfUJE+Zxc8rrmyC/zHFZpKW0pjIthDnWtSTJnRqrnVE245y7qMjGJ6I6No51k6LpuY8Xka0OKuKjdbGvKkdpbAgHayD5VRr00L5l8xC2wwC5JyZlQmQsrnSbji/XHMBB1MSZCffcycvz4XNnL8QFNBn14krziLUR0U0y2OK+SahoJdbGwmsgbda3FyjgMnrMAJ6cTJ7SZObh0nXwGToxMm78jE1+uU+LqoIfGVG9IsvqbVqWnQEXbliIhd79S1m4muyNLMcDfjq+VLEFtriCskuzol0exm6JChQo4SKk8rVN0KHx3RDVnM99ENEGE06waCDN8TlnfwivPpfk4RsTju85ini3txSKgXBSvGK2ecwKPDqBk3WpH8shutcNbiR8jfjHLwq1GUoyhCgRLl0Bk6ttMQyt5Our4qsWhBGcMYeOQoW3WUFW8TWsMBhpFzkOmTANBm4xN1/Oo4FYGiCmphIjEb5dEhyFCjmf0sR4sFJP5UnJiwMMwfoyRsTanRDAOmrCY7lgdQaxRAvT6nJIxZht67ZcEFNTDAgkrO2yMByvlgP3lpQizQVrHQWqApi/CdBklVx9KLEnwzPzxKWPI3VVVUL0pQ9Q6P0mq4WzJNnUCV4apbQDm7JYtagYJ19ruU5FmiAUsB7yaQmqYUIyALqup0a4HUhgllyQIsNLxbFkx8aBqgvJY04A9Eg0KTtNLg4D+alMHrYlV00MsEzMo+oC5hswNMwJrOB5QmbB+eCRdmTICA3dcCNoK0dYnY7dj+HKlG9/b9OVJDnnsqrSm0vBTgwmRMWTeFXra796R0xn6T4aKp3Fem+WAWw7PPevZ53tZc8Nlyi97h1hCkKoFtniLofVpLTi4sOR3E+8p6c1e8T6rP7rWED1aOdHtgCe4fDwS1FUWdTqv/eY3uoNnD/+xO+Z/lG6dG5c9szBvhdgjt3qhIPdmE8vku6136HDLyAqpeihx6tmU3lJFaznaH1JqOejBbyWYyzSzZ8JExYbMZS0aAqVfVPmvu2wCItrQ7FBvKQqjeDcd+o/vzN6LUEcGSNzK/OV2XG3KkrrQsTj4CrP1NSfjcKWkKbeqFlkqaPDTETlO7JMoq9ovZumpOtSZOPswWjnxXlfRyqlo3bWsrivXX08393fj2Lvr35mvA+t+vHyLFl4oKgC+S8AjxtXbA19eJL5SXdxwUbPlt461BQW6ovaCgJA2uH3xmCZuNOxUUqJmFBVflUL7jYtJQULCNQwYFJTvwdeKKjuiLcmVslyuZpyEXgk8qjMY0yrmOyZDGtxkvgnEl0VfSBYJPGypmS5mSpV7+V359zG+LDFjMvk3ZhCz6XSwwHF0QMSX1sWy9rxc35L8vg7uv49H8+txAzuX36c9vikA9oAFPwrag3nlnLsBVgf7uUPsdgBrG7EPnRHthLL8tb9hfpRdjGGIBsq/Ni6QkaGtdUNkvSw2lnvzJkesalSX816ZQclvIQe3V/TZD+X7Kfnv5oCOvbrZX9ssO1z9UseRy/XMf+OJ/ -------------------------------------------------------------------------------- /docs/dev/timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Studio-Link/app/9a6f85baeb1942d5ea5e2a015e5fd2e00a5cadf0/docs/dev/timing.png -------------------------------------------------------------------------------- /src/modules/apponair/apponair.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file applive.c Init Live Session 3 | * 4 | * Copyright (C) 2013-2018 studio-link.de Sebastian Reimers 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "webapp.h" 11 | 12 | static struct tmr tmr_startup, tmr_call; 13 | static char command[255] = {0}; 14 | 15 | 16 | static bool no_active_call(void) 17 | { 18 | struct le *le; 19 | 20 | for (le = list_head(uag_list()); le; le = le->next) { 21 | 22 | struct ua *ua = le->data; 23 | 24 | if (ua_call(ua)) 25 | return false; 26 | } 27 | 28 | return true; 29 | } 30 | 31 | 32 | static void startup_call(void *arg) 33 | { 34 | struct call *call = NULL; 35 | if (no_active_call()) { 36 | ua_connect(uag_current(), &call, NULL, 37 | "stream", VIDMODE_OFF); 38 | } 39 | tmr_start(&tmr_call, 6000, startup_call, NULL); 40 | } 41 | 42 | 43 | static void startup(void *arg) 44 | { 45 | startup_call(NULL); 46 | system(command); 47 | } 48 | 49 | 50 | static int module_init(void) 51 | { 52 | int err = 0; 53 | struct config *cfg = conf_config(); 54 | 55 | webapp_accounts_init(); 56 | 57 | #if defined (DARWIN) 58 | re_snprintf(command, sizeof(command), "open https://stream.studio-link.de/stream/login/%s?version=2", 59 | cfg->sip.uuid); 60 | #elif defined (WIN32) 61 | re_snprintf(command, sizeof(command), "start https://stream.studio-link.de/stream/login/%s?version=2", 62 | cfg->sip.uuid); 63 | #else 64 | re_snprintf(command, sizeof(command), "xdg-open https://stream.studio-link.de/stream/login/%s?version=2", 65 | cfg->sip.uuid); 66 | #endif 67 | 68 | 69 | tmr_init(&tmr_startup); 70 | tmr_init(&tmr_call); 71 | tmr_start(&tmr_startup, 1500, startup, NULL); 72 | 73 | return err; 74 | } 75 | 76 | 77 | static int module_close(void) 78 | { 79 | tmr_cancel(&tmr_startup); 80 | tmr_cancel(&tmr_call); 81 | webapp_accounts_close(); 82 | return 0; 83 | } 84 | 85 | 86 | EXPORT_SYM const struct mod_export DECL_EXPORTS(apponair) = { 87 | "apponair", 88 | "application", 89 | module_init, 90 | module_close, 91 | }; 92 | -------------------------------------------------------------------------------- /src/modules/apponair/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2013-2018 Studio Link 5 | # 6 | 7 | MOD := apponair 8 | $(MOD)_SRCS += apponair.c account.c utils.c 9 | $(MOD)_LFLAGS += -lm 10 | 11 | include mk/mod.mk 12 | -------------------------------------------------------------------------------- /src/modules/apponair/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "webapp.h" 6 | 7 | void webapp_odict_add(struct odict *og, const struct odict_entry *eg) 8 | { 9 | struct le *le; 10 | struct odict *o; 11 | int err = 0; 12 | char index[64]; 13 | size_t index_cnt=0; 14 | 15 | err = odict_alloc(&o, DICT_BSIZE); 16 | if (err) 17 | return; 18 | 19 | le = (void *)&eg->le; 20 | if (!le) 21 | return; 22 | 23 | for (le=le->list->head; le; le=le->next) { 24 | const struct odict_entry *e = le->data; 25 | if (!str_cmp(e->key, "command")) 26 | continue; 27 | odict_entry_add(o, e->key, e->type, e->u.str); 28 | } 29 | 30 | /* Limited Loop 31 | * No one will need more than 100 accounts for a personal computer 32 | */ 33 | for (int i=0; i<100; i++) { 34 | re_snprintf(index, sizeof(index), "%u", index_cnt); 35 | if (!odict_lookup(og, index)) { 36 | break; 37 | } 38 | index_cnt = index_cnt + 1; 39 | } 40 | 41 | odict_entry_add(og, index, ODICT_OBJECT, o); 42 | 43 | mem_deref(o); 44 | } 45 | 46 | 47 | int webapp_write_file(char *string, char *filename) 48 | { 49 | FILE *f = NULL; 50 | int err = 0; 51 | 52 | f = fopen(filename, "w"); 53 | if (!f) 54 | return errno; 55 | 56 | re_fprintf(f, "%s", string); 57 | 58 | if (f) 59 | (void)fclose(f); 60 | 61 | return err; 62 | } 63 | 64 | 65 | int webapp_write_file_json(struct odict *json, char *filename) 66 | { 67 | FILE *f = NULL; 68 | int err = 0; 69 | 70 | f = fopen(filename, "w"); 71 | if (!f) 72 | return errno; 73 | 74 | re_fprintf(f, "%H", json_encode_odict, json); 75 | 76 | if (f) 77 | (void)fclose(f); 78 | 79 | return err; 80 | } 81 | 82 | 83 | int webapp_load_file(struct mbuf *mb, char *filename) 84 | { 85 | int err = 0, fd = open(filename, O_RDONLY); 86 | 87 | if (fd < 0) 88 | return errno; 89 | 90 | for (;;) { 91 | uint8_t buf[1024]; 92 | 93 | const ssize_t n = read(fd, (void *)buf, sizeof(buf)); 94 | if (n < 0) { 95 | err = errno; 96 | break; 97 | } 98 | else if (n == 0) 99 | break; 100 | 101 | err |= mbuf_write_mem(mb, buf, n); 102 | } 103 | 104 | (void)close(fd); 105 | 106 | return err; 107 | } 108 | -------------------------------------------------------------------------------- /src/modules/apponair/webapp.h: -------------------------------------------------------------------------------- 1 | #ifdef SLPLUGIN 2 | /* 3 | * effect/effect.c 4 | */ 5 | void effect_set_bypass(bool value); 6 | #endif 7 | 8 | enum ws_type { 9 | WS_BARESIP, 10 | WS_CALLS, 11 | WS_CONTACT, 12 | WS_CHAT, 13 | WS_METER, 14 | WS_CPU, 15 | WS_OPTIONS, 16 | }; 17 | 18 | enum webapp_call_state { 19 | WS_CALL_OFF, 20 | WS_CALL_RINGING, 21 | WS_CALL_ON 22 | }; 23 | 24 | enum { 25 | DICT_BSIZE = 32, 26 | MAX_LEVELS = 8, 27 | }; 28 | 29 | struct webapp { 30 | struct websock_conn *wc_srv; 31 | struct le le; 32 | enum ws_type ws_type; 33 | }; 34 | 35 | extern enum webapp_call_state webapp_call_status; 36 | 37 | extern struct odict *webapp_calls; 38 | 39 | int webapp_call_delete(struct call *call); 40 | int webapp_call_update(struct call *call, char *state); 41 | 42 | /* 43 | * vumeter.c 44 | */ 45 | int webapp_vu_encode_update(struct aufilt_enc_st **stp, void **ctx, 46 | const struct aufilt *af, struct aufilt_prm *prm); 47 | int webapp_vu_decode_update(struct aufilt_dec_st **stp, void **ctx, 48 | const struct aufilt *af, struct aufilt_prm *prm); 49 | int webapp_vu_encode(struct aufilt_enc_st *st, int16_t *sampv, size_t *sampc); 50 | int webapp_vu_decode(struct aufilt_dec_st *st, int16_t *sampv, size_t *sampc); 51 | 52 | /* 53 | * account.c 54 | */ 55 | int webapp_accounts_init(void); 56 | void webapp_accounts_close(void); 57 | const struct odict* webapp_accounts_get(void); 58 | void webapp_account_add(const struct odict_entry *acc); 59 | void webapp_account_delete(char *user, char *domain); 60 | void webapp_account_current(void); 61 | void webapp_account_status(const char *aor, bool status); 62 | 63 | /* 64 | * contact.c 65 | */ 66 | int webapp_contacts_init(void); 67 | void webapp_contacts_close(void); 68 | void webapp_contact_add(const struct odict_entry *contact); 69 | void webapp_contact_delete(const char *sip); 70 | const struct odict* webapp_contacts_get(void); 71 | 72 | /* 73 | * option.c 74 | */ 75 | int webapp_options_init(void); 76 | void webapp_options_close(void); 77 | const struct odict* webapp_options_get(void); 78 | void webapp_options_set(char *key, char *value); 79 | char* webapp_options_getv(char *key); 80 | 81 | /* 82 | * chat.c 83 | */ 84 | int webapp_chat_init(void); 85 | void webapp_chat_close(void); 86 | int webapp_chat_add(const char *peer, const char *message, bool self); 87 | const struct odict* webapp_messages_get(void); 88 | 89 | /* 90 | * ws_*.c 91 | */ 92 | void webapp_ws_baresip(const struct websock_hdr *hdr, 93 | struct mbuf *mb, void *arg); 94 | void webapp_ws_calls(const struct websock_hdr *hdr, 95 | struct mbuf *mb, void *arg); 96 | void webapp_ws_call_mute_send(void); 97 | void webapp_ws_contacts(const struct websock_hdr *hdr, 98 | struct mbuf *mb, void *arg); 99 | void webapp_ws_chat(const struct websock_hdr *hdr, 100 | struct mbuf *mb, void *arg); 101 | void webapp_ws_meter(const struct websock_hdr *hdr, 102 | struct mbuf *mb, void *arg); 103 | void webapp_ws_options(const struct websock_hdr *hdr, 104 | struct mbuf *mb, void *arg); 105 | void ws_meter_process(unsigned int ch, float *in, unsigned long nframes); 106 | void webapp_ws_meter_init(void); 107 | void webapp_ws_meter_close(void); 108 | 109 | /* 110 | * websocket.c 111 | */ 112 | void ws_send_all(enum ws_type ws_type, char *str); 113 | void ws_send_all_b(enum ws_type ws_type, struct mbuf *mb); 114 | void ws_send_json(enum ws_type type, const struct odict *o); 115 | void srv_websock_close_handler(int err, void *arg); 116 | int webapp_ws_handler(struct http_conn *conn, enum ws_type type, 117 | const struct http_msg *msg, 118 | websock_recv_h *recvh); 119 | void webapp_ws_init(void); 120 | void webapp_ws_close(void); 121 | 122 | /* 123 | * utils.c 124 | */ 125 | void webapp_odict_add(struct odict *og, const struct odict_entry *e); 126 | int webapp_write_file(char *string, char *filename); 127 | int webapp_write_file_json(struct odict *json, char *filename); 128 | int webapp_load_file(struct mbuf *mb, char *filename); 129 | -------------------------------------------------------------------------------- /src/modules/auicecast/auicecast.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file auice.c 3 | * 4 | * Copyright (C) 2016 IT-Service Sebastian Reimers 5 | */ 6 | #define _DEFAULT_SOURCE 1 7 | #define _BSD_SOURCE 1 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | /** 17 | * @defgroup auice auice 18 | * 19 | * Audio module for ICECAST 20 | */ 21 | 22 | struct auplay_st { 23 | const struct auplay *ap; /* pointer to base-class (inheritance) */ 24 | pthread_t thread; 25 | bool run; 26 | void *write; 27 | int16_t *sampv; 28 | size_t sampc; 29 | auplay_write_h *wh; 30 | void *arg; 31 | struct auplay_prm prm; 32 | char *device; 33 | }; 34 | 35 | static struct auplay *auplay; 36 | 37 | 38 | static void destructor(void *arg) 39 | { 40 | struct auplay_st *st = arg; 41 | 42 | if (st->run) { 43 | st->run = false; 44 | pthread_join(st->thread, NULL); 45 | } 46 | mem_deref(st->sampv); 47 | } 48 | 49 | 50 | static void *play_thread(void *arg) 51 | { 52 | struct auplay_st *st = arg; 53 | shout_t *shout; 54 | int write; 55 | int num_frames; 56 | const int MP3_SIZE = 4096; 57 | long ret; 58 | unsigned char mp3_buffer[MP3_SIZE]; 59 | 60 | num_frames = st->prm.srate * st->prm.ptime / 1000; 61 | 62 | shout_init(); 63 | 64 | if (!(shout = shout_new())) { 65 | printf("Could not allocate shout_t\n"); 66 | return NULL; 67 | } 68 | 69 | if (shout_set_host(shout, "127.0.0.1") != SHOUTERR_SUCCESS) { 70 | printf("Error setting hostname: %s\n", shout_get_error(shout)); 71 | return NULL; 72 | } 73 | 74 | if (shout_set_protocol(shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { 75 | printf("Error setting protocol: %s\n", shout_get_error(shout)); 76 | return NULL; 77 | } 78 | 79 | if (shout_set_port(shout, 8000) != SHOUTERR_SUCCESS) { 80 | printf("Error setting port: %s\n", shout_get_error(shout)); 81 | return NULL; 82 | } 83 | 84 | if (shout_set_password(shout, "hackme") != SHOUTERR_SUCCESS) { 85 | printf("Error setting password: %s\n", shout_get_error(shout)); 86 | return NULL; 87 | } 88 | if (shout_set_mount(shout, "/example.mp3") != SHOUTERR_SUCCESS) { 89 | printf("Error setting mount: %s\n", shout_get_error(shout)); 90 | return NULL; 91 | } 92 | 93 | if (shout_set_user(shout, "source") != SHOUTERR_SUCCESS) { 94 | printf("Error setting user: %s\n", shout_get_error(shout)); 95 | return NULL; 96 | } 97 | 98 | if (shout_set_format(shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) { 99 | printf("Error setting format: %s\n", shout_get_error(shout)); 100 | return NULL; 101 | } 102 | 103 | if (shout_set_name(shout, "testcast") != SHOUTERR_SUCCESS) { 104 | printf("Error setting name: %s\n", shout_get_error(shout)); 105 | return NULL; 106 | } 107 | 108 | if (shout_set_description(shout, "description") != SHOUTERR_SUCCESS) { 109 | printf("Error setting genre: %s\n", shout_get_error(shout)); 110 | return NULL; 111 | } 112 | 113 | if (shout_open(shout) != SHOUTERR_SUCCESS) { 114 | return NULL; 115 | } 116 | 117 | lame_t lame = lame_init(); 118 | lame_set_in_samplerate(lame, 48000); 119 | lame_set_num_channels(lame, 2); 120 | lame_set_brate(lame, 128); 121 | lame_set_mode(lame, 1); 122 | lame_set_quality(lame, 2); 123 | lame_set_VBR(lame, vbr_default); 124 | lame_init_params(lame); 125 | 126 | warning("start stream\n"); 127 | while (st->run) { 128 | st->wh(st->sampv, st->sampc, st->arg); 129 | 130 | write = lame_encode_buffer_interleaved(lame, st->sampv, num_frames, mp3_buffer, MP3_SIZE); 131 | 132 | ret = shout_send(shout, mp3_buffer, write); 133 | if (ret != SHOUTERR_SUCCESS) { 134 | printf("DEBUG: Send error: %s\n", shout_get_error(shout)); 135 | break; 136 | } 137 | shout_sync(shout); 138 | } 139 | 140 | warning("stop stream\n"); 141 | lame_close(lame); 142 | shout_close(shout); 143 | shout_shutdown(); 144 | 145 | return NULL; 146 | } 147 | 148 | 149 | static int alloc_handler(struct auplay_st **stp, const struct auplay *ap, 150 | struct auplay_prm *prm, const char *device, 151 | auplay_write_h *wh, void *arg) 152 | { 153 | struct auplay_st *st; 154 | int err; 155 | 156 | if (!stp || !ap || !prm || !wh) 157 | return EINVAL; 158 | 159 | st = mem_zalloc(sizeof(*st), destructor); 160 | if (!st) 161 | return ENOMEM; 162 | 163 | st->ap = ap; 164 | st->prm = *prm; 165 | st->wh = wh; 166 | st->arg = arg; 167 | 168 | st->sampc = prm->srate * prm->ch * prm->ptime / 1000; 169 | st->sampv = mem_alloc(st->sampc * sizeof(int16_t), NULL); 170 | if (!st->sampv) { 171 | err = ENOMEM; 172 | goto out; 173 | } 174 | 175 | st->run = true; 176 | err = pthread_create(&st->thread, NULL, play_thread, st); 177 | if (err) { 178 | st->run = false; 179 | goto out; 180 | } 181 | 182 | out: 183 | if (err) 184 | mem_deref(st); 185 | else 186 | *stp = st; 187 | 188 | return err; 189 | } 190 | 191 | 192 | static int module_init(void) 193 | { 194 | return auplay_register(&auplay, "auice", alloc_handler); 195 | } 196 | 197 | 198 | static int module_close(void) 199 | { 200 | auplay = mem_deref(auplay); 201 | 202 | return 0; 203 | } 204 | 205 | 206 | EXPORT_SYM const struct mod_export DECL_EXPORTS(auicecast) = { 207 | "auicecast", 208 | "auplay", 209 | module_init, 210 | module_close 211 | }; 212 | -------------------------------------------------------------------------------- /src/modules/auicecast/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2010 Creytiv.com 5 | # 6 | 7 | MOD := auicecast 8 | $(MOD)_SRCS += auicecast.c 9 | $(MOD)_LFLAGS += -lshout -lmp3lame 10 | 11 | include mk/mod.mk 12 | -------------------------------------------------------------------------------- /src/modules/effect/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2010 Creytiv.com 5 | # 6 | 7 | MOD := effect 8 | $(MOD)_SRCS += effect.c 9 | $(MOD)_LFLAGS += -lm 10 | 11 | include mk/mod.mk 12 | -------------------------------------------------------------------------------- /src/modules/effectonair/effectonair.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file effectlive.c DAW EffectLive Overlay Plugin 3 | * 4 | * Copyright (C) 2013-2018 studio-link.de 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #define SAMPLE_16BIT_SCALING 32767.0f 16 | #define SAMPLE_16BIT_MAX 32767 17 | #define SAMPLE_16BIT_MIN -32767 18 | #define SAMPLE_16BIT_MAX_F 32767.0f 19 | #define SAMPLE_16BIT_MIN_F -32767.0f 20 | 21 | #define NORMALIZED_FLOAT_MIN -1.0f 22 | #define NORMALIZED_FLOAT_MAX 1.0f 23 | 24 | #define f_round(f) lrintf(f) 25 | 26 | #define float_16(s, d)\ 27 | if ((s) <= NORMALIZED_FLOAT_MIN) {\ 28 | (d) = SAMPLE_16BIT_MIN;\ 29 | }\ 30 | else if ((s) >= NORMALIZED_FLOAT_MAX) {\ 31 | (d) = SAMPLE_16BIT_MAX;\ 32 | }\ 33 | else {\ 34 | (d) = f_round ((s) * SAMPLE_16BIT_SCALING);\ 35 | } 36 | 37 | 38 | struct auplay_st { 39 | const struct auplay *ap; /* pointer to base-class (inheritance) */ 40 | pthread_t thread; 41 | bool run; 42 | void *write; 43 | int16_t *sampv; 44 | size_t sampc; 45 | auplay_write_h *wh; 46 | void *arg; 47 | struct auplay_prm prm; 48 | char *device; 49 | }; 50 | 51 | struct ausrc_st { 52 | const struct ausrc *as; /* pointer to base-class (inheritance) */ 53 | pthread_t thread; 54 | bool run; 55 | void *read; 56 | int16_t *sampv; 57 | size_t sampc; 58 | ausrc_read_h *rh; 59 | void *arg; 60 | struct ausrc_prm prm; 61 | char *device; 62 | }; 63 | 64 | static struct ausrc *ausrc; 65 | static struct auplay *auplay; 66 | 67 | struct ausrc_st *st_src; 68 | struct auplay_st *st_play; 69 | 70 | 71 | static void sample_move_d16_sS(char *dst, float *src, unsigned long nsamples, 72 | unsigned long dst_skip) 73 | { 74 | while (nsamples--) { 75 | float_16 (*src, *((int16_t*) dst)); 76 | dst += dst_skip; 77 | ++src; 78 | } 79 | } 80 | 81 | 82 | static void sample_move_dS_s16(float *dst, char *src, unsigned long nsamples, 83 | unsigned long src_skip) 84 | { 85 | /* ALERT: signed sign-extension portability !!! */ 86 | const float scaling = 1.0/SAMPLE_16BIT_SCALING; 87 | while (nsamples--) { 88 | *dst = (*((short *) src)) * scaling; 89 | ++dst; 90 | src += src_skip; 91 | } 92 | } 93 | 94 | 95 | void effectlive_src(const float* const input0, const float* const input1, 96 | unsigned long nframes); 97 | 98 | void effectlive_src(const float* const input0, const float* const input1, 99 | unsigned long nframes) 100 | { 101 | struct auframe af; 102 | static uint64_t frames = 0; 103 | 104 | if (st_src) { 105 | sample_move_d16_sS((char*)st_src->sampv, (float*)input0, 106 | nframes, 4); 107 | sample_move_d16_sS((char*)st_src->sampv+2, (float*)input1, 108 | nframes, 4); 109 | 110 | af.fmt = st_src->prm.fmt; 111 | af.sampv = st_src->sampv; 112 | af.sampc = nframes * 2; 113 | af.timestamp = frames * AUDIO_TIMEBASE / 48000; 114 | 115 | frames += nframes; 116 | 117 | st_src->rh(&af, st_src->arg); 118 | } 119 | } 120 | 121 | 122 | static void ausrc_destructor(void *arg) 123 | { 124 | mem_deref(st_src->sampv); 125 | st_src = NULL; 126 | } 127 | 128 | 129 | static void auplay_destructor(void *arg) 130 | { 131 | mem_deref(st_play->sampv); 132 | st_play = NULL; 133 | } 134 | 135 | 136 | static int src_alloc(struct ausrc_st **stp, const struct ausrc *as, 137 | struct media_ctx **ctx, 138 | struct ausrc_prm *prm, const char *device, 139 | ausrc_read_h *rh, ausrc_error_h *errh, void *arg) 140 | { 141 | (void)ctx; 142 | (void)errh; 143 | (void)device; 144 | int err = 0; 145 | 146 | if (!stp || !as || !prm) 147 | return EINVAL; 148 | 149 | if ((st_src = mem_zalloc(sizeof(*st_src), ausrc_destructor)) == NULL) 150 | return ENOMEM; 151 | 152 | 153 | st_src->run = false; 154 | st_src->as = as; 155 | st_src->rh = rh; 156 | st_src->arg = arg; 157 | 158 | st_src->sampc = prm->srate * prm->ch * prm->ptime / 1000; 159 | st_src->sampv = mem_zalloc(10 * st_src->sampc, NULL); 160 | if (!st_src->sampv) { 161 | err = ENOMEM; 162 | goto out; 163 | } 164 | 165 | out: 166 | if (err) 167 | mem_deref(st_src); 168 | else 169 | st_src->run = true; 170 | *stp = st_src; 171 | 172 | return err; 173 | } 174 | 175 | 176 | static int play_alloc(struct auplay_st **stp, const struct auplay *ap, 177 | struct auplay_prm *prm, const char *device, 178 | auplay_write_h *wh, void *arg) 179 | { 180 | int err = 0; 181 | if (!stp || !ap || !prm) 182 | return EINVAL; 183 | 184 | if ((st_play = mem_zalloc(sizeof(*st_play), 185 | auplay_destructor)) == NULL) 186 | return ENOMEM; 187 | st_play->run = false; 188 | st_play->ap = ap; 189 | st_play->wh = wh; 190 | st_play->arg = arg; 191 | st_play->sampc = prm->srate * prm->ch * prm->ptime / 1000; 192 | st_play->sampv = mem_zalloc(10 * st_play->sampc, NULL); 193 | if (!st_play->sampv) { 194 | err = ENOMEM; 195 | goto out; 196 | } 197 | 198 | out: 199 | if (err) 200 | mem_deref(st_play); 201 | else 202 | st_play->run = true; 203 | *stp = st_play; 204 | 205 | return err; 206 | 207 | } 208 | 209 | 210 | static int effect_init(void) 211 | { 212 | int err; 213 | err = ausrc_register(&ausrc, baresip_ausrcl(), "effectonair", src_alloc); 214 | err |= auplay_register(&auplay, baresip_auplayl(), "effectonair", play_alloc); 215 | 216 | return err; 217 | } 218 | 219 | 220 | static int effect_close(void) 221 | { 222 | ausrc = mem_deref(ausrc); 223 | auplay = mem_deref(auplay); 224 | 225 | return 0; 226 | } 227 | 228 | 229 | const struct mod_export DECL_EXPORTS(effectonair) = { 230 | "effectonair", 231 | "sound", 232 | effect_init, 233 | effect_close 234 | }; 235 | -------------------------------------------------------------------------------- /src/modules/effectonair/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2016 Studio Link 5 | # 6 | 7 | MOD := effectonair 8 | $(MOD)_SRCS += effectonair.c 9 | $(MOD)_LFLAGS += -lm 10 | 11 | include mk/mod.mk 12 | -------------------------------------------------------------------------------- /src/modules/g722/g722.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file g722.c G.722 audio codec 3 | * 4 | * Copyright (C) 2010 Creytiv.com 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "g722.h" 13 | 14 | 15 | /** 16 | * @defgroup g722 g722 17 | * 18 | * The G.722 audio codec 19 | * 20 | * ## From RFC 3551: 21 | 22 | 4.5.2 G722 23 | 24 | G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding 25 | within 64 kbit/s". The G.722 encoder produces a stream of octets, 26 | each of which SHALL be octet-aligned in an RTP packet. The first bit 27 | transmitted in the G.722 octet, which is the most significant bit of 28 | the higher sub-band sample, SHALL correspond to the most significant 29 | bit of the octet in the RTP packet. 30 | 31 | Even though the actual sampling rate for G.722 audio is 16,000 Hz, 32 | the RTP clock rate for the G722 payload format is 8,000 Hz because 33 | that value was erroneously assigned in RFC 1890 and must remain 34 | unchanged for backward compatibility. The octet rate or sample-pair 35 | rate is 8,000 Hz. 36 | 37 | ## Reference: 38 | 39 | http://www.soft-switch.org/spandsp-modules.html 40 | 41 | */ 42 | 43 | 44 | enum { 45 | G722_SAMPLE_RATE = 16000, 46 | G722_BITRATE_48k = 48000, 47 | G722_BITRATE_56k = 56000, 48 | G722_BITRATE_64k = 64000 49 | }; 50 | 51 | 52 | struct auenc_state { 53 | g722_encode_state_t enc; 54 | }; 55 | 56 | struct audec_state { 57 | g722_decode_state_t dec; 58 | }; 59 | 60 | 61 | static int encode_update(struct auenc_state **aesp, 62 | const struct aucodec *ac, 63 | struct auenc_param *prm, const char *fmtp) 64 | { 65 | struct auenc_state *st; 66 | int err = 0; 67 | (void)prm; 68 | (void)fmtp; 69 | 70 | if (!aesp || !ac) 71 | return EINVAL; 72 | 73 | if (*aesp) 74 | return 0; 75 | 76 | st = mem_alloc(sizeof(*st), NULL); 77 | if (!st) 78 | return ENOMEM; 79 | 80 | if (!g722_encode_init(&st->enc, G722_BITRATE_64k, 0)) { 81 | err = EPROTO; 82 | goto out; 83 | } 84 | 85 | out: 86 | if (err) 87 | mem_deref(st); 88 | else 89 | *aesp = st; 90 | 91 | return err; 92 | } 93 | 94 | 95 | static int decode_update(struct audec_state **adsp, 96 | const struct aucodec *ac, const char *fmtp) 97 | { 98 | struct audec_state *st; 99 | int err = 0; 100 | (void)fmtp; 101 | 102 | if (!adsp || !ac) 103 | return EINVAL; 104 | 105 | if (*adsp) 106 | return 0; 107 | 108 | st = mem_alloc(sizeof(*st), NULL); 109 | if (!st) 110 | return ENOMEM; 111 | 112 | if (!g722_decode_init(&st->dec, G722_BITRATE_64k, 0)) { 113 | err = EPROTO; 114 | goto out; 115 | } 116 | 117 | out: 118 | if (err) 119 | mem_deref(st); 120 | else 121 | *adsp = st; 122 | 123 | return err; 124 | } 125 | 126 | 127 | static int encode(struct auenc_state *st, 128 | bool *marker, uint8_t *buf, size_t *len, 129 | int fmt, const void *sampv, size_t sampc) 130 | { 131 | int n; 132 | (void)marker; 133 | 134 | if (fmt != AUFMT_S16LE) 135 | return ENOTSUP; 136 | 137 | n = g722_encode(&st->enc, buf, sampv, (int)sampc); 138 | if (n <= 0) { 139 | return EPROTO; 140 | } 141 | else if (n > (int)*len) { 142 | return EOVERFLOW; 143 | } 144 | 145 | *len = n; 146 | 147 | return 0; 148 | } 149 | 150 | 151 | static int decode(struct audec_state *st, int fmt, void *sampv, size_t *sampc, 152 | bool marker, const uint8_t *buf, size_t len) 153 | { 154 | int n; 155 | (void)marker; 156 | 157 | if (!st || !sampv || !buf) 158 | return EINVAL; 159 | 160 | if (fmt != AUFMT_S16LE) 161 | return ENOTSUP; 162 | 163 | n = g722_decode(&st->dec, sampv, buf, (int)len); 164 | if (n < 0) 165 | return EPROTO; 166 | 167 | *sampc = n; 168 | 169 | return 0; 170 | } 171 | 172 | 173 | static struct aucodec g722 = { 174 | .pt = "9", 175 | .name = "G722", 176 | .srate = 16000, 177 | .crate = 8000, 178 | .ch = 1, 179 | .pch = 1, 180 | .encupdh = encode_update, 181 | .ench = encode, 182 | .decupdh = decode_update, 183 | .dech = decode, 184 | }; 185 | 186 | 187 | static int module_init(void) 188 | { 189 | aucodec_register(baresip_aucodecl(), &g722); 190 | return 0; 191 | } 192 | 193 | 194 | static int module_close(void) 195 | { 196 | aucodec_unregister(&g722); 197 | return 0; 198 | } 199 | 200 | 201 | EXPORT_SYM const struct mod_export DECL_EXPORTS(g722) = { 202 | "g722", 203 | "codec", 204 | module_init, 205 | module_close 206 | }; 207 | -------------------------------------------------------------------------------- /src/modules/g722/g722.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SpanDSP - a series of DSP components for telephony 3 | * 4 | * g722.h - The ITU G.722 codec. 5 | * 6 | * Written by Steve Underwood 7 | * 8 | * Copyright (C) 2005 Steve Underwood 9 | * 10 | * Despite my general liking of the GPL, I place my own contributions 11 | * to this code in the public domain for the benefit of all mankind - 12 | * even the slimy ones who might try to proprietize my work and use it 13 | * to my detriment. 14 | * 15 | * Based on a single channel G.722 codec which is: 16 | * 17 | ***** Copyright (c) CMU 1993 ***** 18 | * Computer Science, Speech Group 19 | * Chengxiang Lu and Alex Hauptmann 20 | * 21 | * $Id$ 22 | */ 23 | 24 | 25 | /*! \file */ 26 | 27 | #if !defined(_G722_H_) 28 | #define _G722_H_ 29 | 30 | /*! \page g722_page G.722 encoding and decoding 31 | \section g722_page_sec_1 What does it do? 32 | The G.722 module is a bit exact implementation of the ITU G.722 specification for all three 33 | specified bit rates - 64000bps, 56000bps and 48000bps. It passes the ITU tests. 34 | 35 | To allow fast and flexible interworking with narrow band telephony, the encoder and decoder 36 | support an option for the linear audio to be an 8k samples/second stream. In this mode the 37 | codec is considerably faster, and still fully compatible with wideband terminals using G.722. 38 | 39 | \section g722_page_sec_2 How does it work? 40 | ???. 41 | */ 42 | 43 | enum 44 | { 45 | G722_SAMPLE_RATE_8000 = 0x0001, 46 | G722_PACKED = 0x0002 47 | }; 48 | 49 | #ifndef INT16_MAX 50 | #define INT16_MAX 32767 51 | #endif 52 | #ifndef INT16_MIN 53 | #define INT16_MIN (-32768) 54 | #endif 55 | 56 | typedef struct 57 | { 58 | /*! TRUE if the operating in the special ITU test mode, with the band split filters 59 | disabled. */ 60 | int itu_test_mode; 61 | /*! TRUE if the G.722 data is packed */ 62 | int packed; 63 | /*! TRUE if encode from 8k samples/second */ 64 | int eight_k; 65 | /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ 66 | int bits_per_sample; 67 | 68 | /*! Signal history for the QMF */ 69 | int x[24]; 70 | 71 | struct 72 | { 73 | int s; 74 | int sp; 75 | int sz; 76 | int r[3]; 77 | int a[3]; 78 | int ap[3]; 79 | int p[3]; 80 | int d[7]; 81 | int b[7]; 82 | int bp[7]; 83 | int sg[7]; 84 | int nb; 85 | int det; 86 | } band[2]; 87 | 88 | unsigned int in_buffer; 89 | int in_bits; 90 | unsigned int out_buffer; 91 | int out_bits; 92 | } g722_encode_state_t; 93 | 94 | typedef struct 95 | { 96 | /*! TRUE if the operating in the special ITU test mode, with the band split filters 97 | disabled. */ 98 | int itu_test_mode; 99 | /*! TRUE if the G.722 data is packed */ 100 | int packed; 101 | /*! TRUE if decode to 8k samples/second */ 102 | int eight_k; 103 | /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ 104 | int bits_per_sample; 105 | 106 | /*! Signal history for the QMF */ 107 | int x[24]; 108 | 109 | struct 110 | { 111 | int s; 112 | int sp; 113 | int sz; 114 | int r[3]; 115 | int a[3]; 116 | int ap[3]; 117 | int p[3]; 118 | int d[7]; 119 | int b[7]; 120 | int bp[7]; 121 | int sg[7]; 122 | int nb; 123 | int det; 124 | } band[2]; 125 | 126 | unsigned int in_buffer; 127 | int in_bits; 128 | unsigned int out_buffer; 129 | int out_bits; 130 | } g722_decode_state_t; 131 | 132 | #ifdef __cplusplus 133 | extern "C" { 134 | #endif 135 | 136 | g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, int rate, int options); 137 | int g722_encode_release(g722_encode_state_t *s); 138 | int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len); 139 | 140 | g722_decode_state_t *g722_decode_init(g722_decode_state_t *s, int rate, int options); 141 | int g722_decode_release(g722_decode_state_t *s); 142 | int g722_decode(g722_decode_state_t *s, int16_t amp[], const uint8_t g722_data[], int len); 143 | 144 | #ifdef __cplusplus 145 | } 146 | #endif 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /src/modules/g722/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2010 Creytiv.com 5 | # 6 | 7 | MOD := g722 8 | $(MOD)_SRCS += g722.c g722.h g722_decode.c g722_encode.c 9 | 10 | include mk/mod.mk 11 | -------------------------------------------------------------------------------- /src/modules/gpio/event_gpio.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013 Adafruit 3 | 4 | Original RPi.GPIO Author Ben Croston 5 | Modified for BBIO Author Justin Cooper 6 | 7 | This file incorporates work covered by the following copyright and 8 | permission notice, all modified code adopts the original license: 9 | 10 | Copyright (c) 2013 Ben Croston 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy of 13 | this software and associated documentation files (the "Software"), to deal in 14 | the Software without restriction, including without limitation the rights to 15 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 16 | of the Software, and to permit persons to whom the Software is furnished to do 17 | so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | */ 30 | 31 | #define NO_EDGE 0 32 | #define RISING_EDGE 1 33 | #define FALLING_EDGE 2 34 | #define BOTH_EDGE 3 35 | 36 | #define INPUT 0 37 | #define OUTPUT 1 38 | #define ALT0 4 39 | 40 | #define HIGH 1 41 | #define LOW 0 42 | 43 | #define PUD_OFF 0 44 | #define PUD_DOWN 1 45 | #define PUD_UP 2 46 | 47 | int gpio_export(unsigned int gpio); 48 | int gpio_unexport(unsigned int gpio); 49 | void exports_cleanup(void); 50 | int gpio_set_direction(unsigned int gpio, unsigned int in_flag); 51 | int gpio_get_direction(unsigned int gpio, unsigned int *value); 52 | int gpio_set_value(unsigned int gpio, unsigned int value); 53 | int gpio_get_value(unsigned int gpio, unsigned int *value); 54 | 55 | int add_edge_detect(unsigned int gpio, unsigned int edge); 56 | void remove_edge_detect(unsigned int gpio); 57 | int add_edge_callback(unsigned int gpio, void (*func)(unsigned int gpio)); 58 | int event_detected(unsigned int gpio); 59 | int gpio_event_add(unsigned int gpio); 60 | int gpio_event_remove(unsigned int gpio); 61 | int gpio_is_evented(unsigned int gpio); 62 | int event_initialise(void); 63 | void event_cleanup(void); 64 | int blocking_wait_for_edge(unsigned int gpio, unsigned int edge); 65 | -------------------------------------------------------------------------------- /src/modules/gpio/gpio.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file gpio.c GPIO BeagleBone Black Application module 3 | * 4 | * Copyright (C) 2015 Studio-Link.de 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include "event_gpio.h" 10 | 11 | #define GPIO_LED 61 12 | #define GPIO_BUTTON 66 13 | 14 | static struct tmr tmr_alert; /**< Incoming call alert timer */ 15 | static int led_status = 0; 16 | static struct ua *current_agent; 17 | unsigned long long last_bounce = 0; 18 | 19 | 20 | static void led_on(void) 21 | { 22 | led_status = 1; 23 | gpio_set_value(GPIO_LED, 1); 24 | } 25 | 26 | 27 | static void led_off(void) 28 | { 29 | led_status = 0; 30 | gpio_set_value(GPIO_LED, 0); 31 | } 32 | 33 | static void button_action(unsigned int gpio) 34 | { 35 | info("BUTTON %d pressed \n", gpio); 36 | ua_answer(current_agent, NULL); 37 | } 38 | 39 | 40 | static void button_callback(unsigned int gpio) 41 | { 42 | unsigned int bouncetime = 200; //milliseconds 43 | struct timeval tv_timenow; 44 | unsigned long long timenow = 0; 45 | 46 | gettimeofday(&tv_timenow, NULL); 47 | timenow = tv_timenow.tv_sec*1E6 + tv_timenow.tv_usec; 48 | 49 | if (timenow - last_bounce > bouncetime*1000) 50 | { 51 | button_action(gpio); 52 | } 53 | last_bounce = timenow; 54 | } 55 | 56 | 57 | static void button_init(void) 58 | { 59 | gpio_export(GPIO_BUTTON); 60 | sys_msleep(100); //Workaround: wait until udev sets permissions 61 | gpio_set_direction(GPIO_BUTTON, INPUT); 62 | add_edge_detect(GPIO_BUTTON, FALLING_EDGE); 63 | add_edge_callback(GPIO_BUTTON, &button_callback); 64 | } 65 | 66 | static void button_close(void) 67 | { 68 | remove_edge_detect(GPIO_BUTTON); 69 | gpio_unexport(GPIO_BUTTON); 70 | } 71 | 72 | 73 | static void led_init(void) 74 | { 75 | gpio_export(GPIO_LED); 76 | sys_msleep(100); //Workaround: wait until udev sets permissions 77 | gpio_set_direction(GPIO_LED, OUTPUT); 78 | led_off(); 79 | } 80 | 81 | 82 | static void led_close(void) 83 | { 84 | led_off(); 85 | gpio_unexport(GPIO_LED); 86 | } 87 | 88 | 89 | static void alert_start(void *arg) 90 | { 91 | (void)arg; 92 | 93 | if (led_status) { 94 | led_off(); 95 | } else { 96 | led_on(); 97 | } 98 | 99 | tmr_start(&tmr_alert, 500, alert_start, NULL); 100 | } 101 | 102 | 103 | static void alert_stop(void) 104 | { 105 | if (led_status) { 106 | led_off(); 107 | } 108 | tmr_cancel(&tmr_alert); 109 | } 110 | 111 | 112 | static void ua_event_handler(struct ua *ua, enum ua_event ev, 113 | struct call *call, const char *prm, void *arg) 114 | { 115 | (void)call; 116 | (void)prm; 117 | (void)arg; 118 | 119 | switch (ev) { 120 | case UA_EVENT_CALL_INCOMING: 121 | current_agent = ua; 122 | alert_start(0); 123 | break; 124 | 125 | case UA_EVENT_CALL_ESTABLISHED: 126 | alert_stop(); 127 | break; 128 | 129 | case UA_EVENT_CALL_CLOSED: 130 | alert_stop(); 131 | break; 132 | 133 | default: 134 | break; 135 | } 136 | } 137 | 138 | 139 | static int module_init(void) 140 | { 141 | int err; 142 | 143 | tmr_init(&tmr_alert); 144 | led_init(); 145 | button_init(); 146 | err = uag_event_register(ua_event_handler, NULL); 147 | 148 | return err; 149 | } 150 | 151 | 152 | static int module_close(void) 153 | { 154 | tmr_cancel(&tmr_alert); 155 | led_close(); 156 | button_close(); 157 | uag_event_unregister(ua_event_handler); 158 | 159 | return 0; 160 | } 161 | 162 | 163 | const struct mod_export DECL_EXPORTS(gpio) = { 164 | "gpio", 165 | "application", 166 | module_init, 167 | module_close 168 | }; 169 | -------------------------------------------------------------------------------- /src/modules/gpio/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2015 Studio-Link.de 5 | # 6 | 7 | MOD := gpio 8 | $(MOD)_SRCS += event_gpio.c 9 | $(MOD)_SRCS += gpio.c 10 | $(MOD)_LFLAGS += -lpthread 11 | 12 | include mk/mod.mk 13 | -------------------------------------------------------------------------------- /src/modules/jack/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2010 Creytiv.com 5 | # 6 | 7 | MOD := jack 8 | $(MOD)_SRCS += jack.c 9 | $(MOD)_LFLAGS += -ljack -lm 10 | 11 | include mk/mod.mk 12 | -------------------------------------------------------------------------------- /src/modules/slaudio/convert.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SAMPLE_32BIT_SCALING 2147483647.0f 4 | #define SAMPLE_32BIT_MAX 2147483647 5 | #define SAMPLE_32BIT_MIN -2147483647 6 | 7 | #define SAMPLE_24BIT_SCALING 8388607.0f 8 | #define SAMPLE_24BIT_MAX 8388607 9 | #define SAMPLE_24BIT_MIN -8388607 10 | 11 | #define SAMPLE_16BIT_SCALING 32767.0f 12 | #define SAMPLE_16BIT_MAX 32767 13 | #define SAMPLE_16BIT_MIN -32767 14 | 15 | #define NORMALIZED_FLOAT_MIN -1.0f 16 | #define NORMALIZED_FLOAT_MAX 1.0f 17 | 18 | #define f_round(f) lrintf (f) 19 | 20 | #define float_32(s, d)\ 21 | if ((s) <= NORMALIZED_FLOAT_MIN) {\ 22 | (d) = SAMPLE_32BIT_MIN;\ 23 | }\ 24 | else if ((s) >= NORMALIZED_FLOAT_MAX) {\ 25 | (d) = SAMPLE_32BIT_MAX;\ 26 | }\ 27 | else {\ 28 | (d) = f_round ((s) * SAMPLE_32BIT_SCALING);\ 29 | } 30 | 31 | #define float_24(s, d)\ 32 | if ((s) <= NORMALIZED_FLOAT_MIN) {\ 33 | (d) = SAMPLE_24BIT_MIN;\ 34 | }\ 35 | else if ((s) >= NORMALIZED_FLOAT_MAX) {\ 36 | (d) = SAMPLE_24BIT_MAX;\ 37 | }\ 38 | else {\ 39 | (d) = f_round ((s) * SAMPLE_24BIT_SCALING);\ 40 | } 41 | 42 | #define float_16(s, d)\ 43 | if ((s) <= NORMALIZED_FLOAT_MIN) {\ 44 | (d) = SAMPLE_16BIT_MIN;\ 45 | }\ 46 | else if ((s) >= NORMALIZED_FLOAT_MAX) {\ 47 | (d) = SAMPLE_16BIT_MAX;\ 48 | }\ 49 | else {\ 50 | (d) = f_round ((s) * SAMPLE_16BIT_SCALING);\ 51 | } 52 | -------------------------------------------------------------------------------- /src/modules/slaudio/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2020 Studio Link 5 | # 6 | 7 | MOD := slaudio 8 | $(MOD)_SRCS += slaudio.c record.c 9 | $(MOD)_LFLAGS += -lsoundio -lsamplerate 10 | ifeq ($(OS),linux) 11 | $(MOD)_LFLAGS += -lpulse-simple -lpulse 12 | endif 13 | $(MOD)_CFLAGS += -DSOUNDIO_STATIC_LIBRARY=1 -Wno-cast-align 14 | 15 | include mk/mod.mk 16 | -------------------------------------------------------------------------------- /src/modules/slaudio/slaudio.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern struct list sessionl; 5 | void slaudio_record_init(void); 6 | void slaudio_record_open_folder(void); 7 | 8 | void slaudio_record_set(bool status); 9 | void slaudio_monorecord_set(bool status); 10 | void slaudio_monostream_set(bool status); 11 | void slaudio_mute_set(bool status); 12 | void slaudio_monitor_set(bool status); 13 | 14 | int slaudio_record_get_timer(void); 15 | const struct odict* slaudio_get_interfaces(void); 16 | void slaudio_set_driver(int value); 17 | void slaudio_set_input(int value); 18 | void slaudio_set_first_input_channel(int value); 19 | void slaudio_set_output(int value); 20 | int slaudio_reset(void); 21 | -------------------------------------------------------------------------------- /src/modules/slogging/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2020 studio-link.de 5 | # 6 | 7 | MOD := slogging 8 | $(MOD)_SRCS += slogging.c 9 | 10 | include mk/mod.mk 11 | -------------------------------------------------------------------------------- /src/modules/slogging/slogging.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define LOG_ERR 3 /* error conditions */ 7 | #define LOG_WARNING 4 /* warning conditions */ 8 | #define LOG_INFO 6 /* informational */ 9 | #define LOG_DEBUG 7 /* debug-level messages */ 10 | 11 | static struct http_cli *cli = NULL; 12 | static const struct network *net; 13 | static char url[255] = {0}; 14 | static struct http_req *req = NULL; 15 | enum { UUID_LEN = 36 }; 16 | static char myid[9] = {0}; 17 | static struct mqueue *mq; 18 | static char *fmt; 19 | 20 | 21 | static void mqueue_handler(int id, void *data, void *arg) 22 | { 23 | char *msg = data; 24 | http_request(&req, cli, "POST", url, NULL, 25 | NULL, NULL, msg); 26 | 27 | mem_deref(msg); 28 | } 29 | 30 | 31 | static const int lmap[] = { LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR }; 32 | 33 | static void log_handler(uint32_t level, const char *msg) 34 | { 35 | char *gelf; 36 | int time_ms = 0, timestamp = 0; 37 | 38 | timestamp = time(NULL); 39 | 40 | #ifdef WIN32 41 | SYSTEMTIME st; 42 | GetSystemTime(&st); 43 | time_ms = st.wMilliseconds; 44 | #else 45 | time_ms = (int)(tmr_jiffies() - (uint64_t)timestamp * (uint64_t)1000); 46 | #endif 47 | 48 | fmt = mem_zalloc(8192 * sizeof(char), NULL); 49 | gelf = mem_zalloc(8192 * sizeof(char), NULL); 50 | 51 | re_snprintf(gelf, 8191, 52 | "{\"version\": \"1.1\",\ 53 | \"timestamp\":\"%d.%03d\",\ 54 | \"short_message\":\"%s\",\ 55 | \"host\": \"%s\",\ 56 | \"level\": \"%d\"}\r\n", 57 | timestamp, time_ms, 58 | msg, 59 | myid, 60 | lmap[MIN(level, ARRAY_SIZE(lmap)-1)] 61 | ); 62 | 63 | re_snprintf(fmt, 8191, 64 | "Content-Length: %d\r\n" 65 | "Content-Type: application/x-www-form-urlencoded\r\n" 66 | "\r\n" 67 | "%s", 68 | str_len(gelf), gelf); 69 | 70 | mem_deref(gelf); 71 | 72 | mqueue_push(mq, 0, fmt); 73 | } 74 | 75 | 76 | static struct log lg = { 77 | .h = log_handler, 78 | }; 79 | 80 | 81 | static int uuid_load(const char *file, char *uuid, size_t sz) 82 | { 83 | FILE *f = NULL; 84 | int err = 0; 85 | 86 | f = fopen(file, "r"); 87 | if (!f) 88 | return errno; 89 | 90 | if (!fgets(uuid, (int)sz, f)) 91 | err = errno; 92 | 93 | (void)fclose(f); 94 | 95 | debug("uuid: loaded UUID %s from file %s\n", uuid, file); 96 | 97 | return err; 98 | } 99 | 100 | 101 | static int generate_random_uuid(FILE *f) 102 | { 103 | if (re_fprintf(f, "%08x-%04x-%04x-%04x-%08x%04x", 104 | rand_u32(), rand_u16(), rand_u16(), rand_u16(), 105 | rand_u32(), rand_u16()) != UUID_LEN) 106 | return ENOMEM; 107 | 108 | return 0; 109 | } 110 | 111 | 112 | static int uuid_init(const char *file) 113 | { 114 | FILE *f = NULL; 115 | int err = 0; 116 | 117 | f = fopen(file, "r"); 118 | if (f) { 119 | err = 0; 120 | goto out; 121 | } 122 | 123 | f = fopen(file, "w"); 124 | if (!f) { 125 | err = errno; 126 | warning("uuid: fopen() %s (%m)\n", file, err); 127 | goto out; 128 | } 129 | 130 | err = generate_random_uuid(f); 131 | if (err) { 132 | warning("uuid: generate random UUID failed (%m)\n", err); 133 | goto out; 134 | } 135 | 136 | info("uuid: generated new UUID in %s\n", file); 137 | 138 | out: 139 | if (f) 140 | fclose(f); 141 | 142 | return err; 143 | } 144 | 145 | 146 | #ifdef WIN32 147 | static void determineWinOsVersion(void) 148 | { 149 | #define pGetModuleHandle GetModuleHandleW 150 | typedef LONG NTSTATUS; 151 | typedef NTSTATUS(NTAPI *RtlGetVersionFunction)(LPOSVERSIONINFO); 152 | 153 | OSVERSIONINFOEX result = { sizeof(OSVERSIONINFOEX), 154 | 0, 0, 0, 0, {'\0'}, 0, 0, 0, 0, 0}; 155 | HMODULE ntdll = pGetModuleHandle(L"ntdll.dll"); 156 | if (!ntdll) 157 | return; 158 | 159 | RtlGetVersionFunction pRtlGetVersion = 160 | (RtlGetVersionFunction)GetProcAddress(ntdll, "RtlGetVersion"); 161 | if (!pRtlGetVersion) 162 | return; 163 | 164 | pRtlGetVersion((LPOSVERSIONINFO)&result); 165 | 166 | info("slogging: Windows Version %i.%i.%i \n", result.dwMajorVersion, 167 | result.dwMinorVersion, result.dwBuildNumber); 168 | } 169 | #endif 170 | 171 | static int module_init(void) 172 | { 173 | int err = 0; 174 | char path[256]; 175 | char uuidtmp[40]; 176 | 177 | err = conf_path_get(path, sizeof(path)); 178 | if (err) 179 | return err; 180 | 181 | strncat(path, "/uuid", sizeof(path) - strlen(path) - 1); 182 | 183 | err = uuid_init(path); 184 | if (err) 185 | return err; 186 | 187 | err = uuid_load(path, uuidtmp, sizeof(uuidtmp)); 188 | if (err) 189 | return err; 190 | 191 | str_ncpy(myid, uuidtmp, sizeof(myid)); 192 | 193 | err = mqueue_alloc(&mq, mqueue_handler, NULL); 194 | if (err) 195 | return err; 196 | 197 | net = baresip_network(); 198 | re_snprintf(url, sizeof(url), "https://log.studio.link/gelf"); 199 | http_client_alloc(&cli, net_dnsc(net)); 200 | 201 | 202 | log_register_handler(&lg); 203 | 204 | info("slogging: started\n"); 205 | info("slogging: uuid: %s\n", myid); 206 | info("slogging: Version %s\n", BARESIP_VERSION); 207 | info("slogging: Machine %s/%s\n", sys_arch_get(), sys_os_get()); 208 | info("slogging: Kernel %H\n", sys_kernel_get, NULL); 209 | info("slogging: Build %H\n", sys_build_get, NULL); 210 | info("slogging: Network %H\n", net_debug, net); 211 | 212 | #ifdef WIN32 213 | determineWinOsVersion(); 214 | #endif 215 | 216 | return 0; 217 | } 218 | 219 | static int module_close(void) 220 | { 221 | log_unregister_handler(&lg); 222 | req = mem_deref(req); 223 | cli = mem_deref(cli); 224 | mq = mem_deref(mq); 225 | fmt = mem_deref(fmt); 226 | 227 | return 0; 228 | } 229 | 230 | const struct mod_export DECL_EXPORTS(slogging) = { 231 | "slogging", 232 | "application", 233 | module_init, 234 | module_close 235 | }; 236 | -------------------------------------------------------------------------------- /src/modules/slrtaudio/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2018 Studio Link 5 | # 6 | 7 | MOD := slrtaudio 8 | $(MOD)_SRCS += slrtaudio.c record.c 9 | $(MOD)_LFLAGS += -lrtaudio -lstdc++ -lsamplerate 10 | ifeq ($(OS),linux) 11 | $(MOD)_LFLAGS += -lpulse-simple -lpulse 12 | endif 13 | $(MOD)_CFLAGS += -Wno-aggregate-return 14 | 15 | include mk/mod.mk 16 | -------------------------------------------------------------------------------- /src/modules/slrtaudio/record.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "share/compat.h" 6 | #include "FLAC/metadata.h" 7 | #include "FLAC/stream_encoder.h" 8 | #include 9 | 10 | #if defined (WIN32) 11 | #include 12 | #include 13 | #include 14 | #include 15 | #define DIR_SEP "\\" 16 | #else 17 | #define DIR_SEP "/" 18 | #endif 19 | 20 | #define PTIME 20 21 | #define SAMPC 1920 /* Max samples, 48000Hz 2ch at 20ms */ 22 | #include "slrtaudio.h" 23 | 24 | static bool record = false; 25 | static char command[256]; 26 | static int record_timer = 0; 27 | 28 | 29 | void slrtaudio_record_set(bool active) 30 | { 31 | record = active; 32 | } 33 | 34 | 35 | int slrtaudio_record_get_timer() 36 | { 37 | return record_timer; 38 | } 39 | 40 | 41 | static int timestamp_print(struct re_printf *pf, const struct tm *tm) 42 | { 43 | if (!tm) 44 | return 0; 45 | 46 | return re_hprintf(pf, "%d-%02d-%02d-%02d-%02d-%02d", 47 | 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, 48 | tm->tm_hour, tm->tm_min, tm->tm_sec); 49 | } 50 | 51 | 52 | static int openfile(struct session *sess) 53 | { 54 | char filename[256]; 55 | char buf[256]; 56 | time_t tnow = time(0); 57 | struct tm *tm = localtime(&tnow); 58 | FLAC__bool ok = true; 59 | FLAC__StreamMetadata *meta[2]; 60 | FLAC__StreamEncoderInitStatus init_status; 61 | FLAC__StreamMetadata_VorbisComment_Entry entry; 62 | int err; 63 | int ret; 64 | #ifdef WIN32 65 | char win32_path[MAX_PATH]; 66 | 67 | if (S_OK != SHGetFolderPath(NULL, 68 | CSIDL_DESKTOPDIRECTORY, 69 | NULL, 70 | 0, 71 | win32_path)) { 72 | return ENOENT; 73 | } 74 | str_ncpy(buf, win32_path, sizeof(buf)); 75 | #else 76 | err = fs_gethome(buf, sizeof(buf)); 77 | if (err) 78 | return err; 79 | #endif 80 | 81 | 82 | (void)re_snprintf(filename, sizeof(filename), "%s" 83 | DIR_SEP "studio-link", buf, timestamp_print, tm); 84 | 85 | fs_mkdir(filename, 0700); 86 | 87 | (void)re_snprintf(filename, sizeof(filename), "%s" 88 | DIR_SEP "studio-link" 89 | DIR_SEP "%H", buf, timestamp_print, tm); 90 | 91 | fs_mkdir(filename, 0700); 92 | 93 | #if defined (DARWIN) 94 | re_snprintf(command, sizeof(command), "open %s", 95 | filename); 96 | #elif defined (WIN32) 97 | re_snprintf(command, sizeof(command), "start %s", 98 | filename); 99 | #else 100 | re_snprintf(command, sizeof(command), "xdg-open %s", 101 | filename); 102 | #endif 103 | if (sess->local) { 104 | (void)re_snprintf(filename, sizeof(filename), "%s" 105 | DIR_SEP "local.flac", filename); 106 | ret = system(command); 107 | } 108 | else { 109 | (void)re_snprintf(filename, sizeof(filename), "%s" 110 | DIR_SEP "remote-track-%d.flac", filename, sess->track); 111 | } 112 | 113 | 114 | /* Basic Encoder */ 115 | if((sess->flac = FLAC__stream_encoder_new()) == NULL) { 116 | warning("slrtaudio/record: allocating FLAC encoder\n"); 117 | return ENOMEM; 118 | } 119 | 120 | ok &= FLAC__stream_encoder_set_verify(sess->flac, true); 121 | ok &= FLAC__stream_encoder_set_compression_level(sess->flac, 5); 122 | ok &= FLAC__stream_encoder_set_channels(sess->flac, 2); 123 | ok &= FLAC__stream_encoder_set_bits_per_sample(sess->flac, 16); 124 | ok &= FLAC__stream_encoder_set_sample_rate(sess->flac, 48000); 125 | ok &= FLAC__stream_encoder_set_total_samples_estimate(sess->flac, 0); 126 | 127 | if (!ok) { 128 | warning("slrtaudio/record: FLAC__stream_encoder_set\n"); 129 | return EINVAL; 130 | } 131 | 132 | /* METADATA */ 133 | meta[0] = FLAC__metadata_object_new( 134 | FLAC__METADATA_TYPE_VORBIS_COMMENT); 135 | meta[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); 136 | 137 | ok = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( 138 | &entry, "ENCODED_BY", "STUDIO LINK"); 139 | 140 | ok &= FLAC__metadata_object_vorbiscomment_append_comment( 141 | meta[0], entry, /*copy=*/false); 142 | 143 | if (!ok) { 144 | warning("slrtaudio/record: \ 145 | FLAC METADATA ERROR: out of memory or tag error\n"); 146 | return ENOMEM; 147 | } 148 | 149 | meta[1]->length = 1234; /* padding length */ 150 | 151 | ok = FLAC__stream_encoder_set_metadata(sess->flac, meta, 2); 152 | 153 | if (!ok) { 154 | warning("slrtaudio/record: \ 155 | FLAC__stream_encoder_set_metadata\n"); 156 | return ENOMEM; 157 | } 158 | 159 | init_status = FLAC__stream_encoder_init_file(sess->flac, filename, 160 | NULL, NULL); 161 | 162 | if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { 163 | warning("slrtaudio/record: \ 164 | FLAC ERROR: initializing encoder: %s\n", 165 | FLAC__StreamEncoderInitStatusString[init_status]); 166 | } 167 | 168 | return 0; 169 | } 170 | 171 | 172 | void webapp_options_set(char *key, char *value); 173 | static void *record_thread(void *arg) 174 | { 175 | struct session *sess = arg; 176 | FLAC__bool ok = true; 177 | unsigned i; 178 | int ret; 179 | FLAC__StreamEncoderState encstate; 180 | 181 | while (sess->run_record) { 182 | ret = aubuf_get_samp(sess->aubuf, PTIME, sess->sampv, SAMPC); 183 | if (ret) { 184 | goto sleep; 185 | } 186 | 187 | if (record) { 188 | if (!sess->flac) { 189 | if (sess->local) 190 | info("slrtaudio/record: open \ 191 | session record file\n"); 192 | ret = openfile(sess); 193 | if (ret) { 194 | warning("slrtaudio/record: \ 195 | FLAC open file error\n"); 196 | webapp_options_set("record", "false"); 197 | continue; 198 | } 199 | } 200 | 201 | for (i = 0; i < SAMPC; i++) { 202 | sess->pcm[i] = (FLAC__int32)sess->sampv[i]; 203 | } 204 | 205 | ok = FLAC__stream_encoder_process_interleaved( 206 | sess->flac, sess->pcm, SAMPC/2); 207 | 208 | if (sess->local) 209 | record_timer += PTIME; 210 | 211 | } 212 | else { 213 | if (sess->flac) { 214 | if (sess->local) 215 | info("slrtaudio/record: \ 216 | close session record file\n"); 217 | FLAC__stream_encoder_finish(sess->flac); 218 | FLAC__stream_encoder_delete(sess->flac); 219 | 220 | sess->flac = NULL; 221 | 222 | /* open folder on stop record 223 | * if record_time > 5min 224 | */ 225 | if (sess->local && record_timer > 300000) 226 | ret = system(command); 227 | } 228 | record_timer = 0; 229 | } 230 | 231 | if (!ok) { 232 | encstate = FLAC__stream_encoder_get_state(sess->flac); 233 | warning("slrtaudio/record: FLAC ENCODE ERROR: %s\n", 234 | FLAC__StreamEncoderStateString[encstate]); 235 | ok = true; 236 | } 237 | sleep: 238 | sys_msleep(5); 239 | } 240 | return NULL; 241 | } 242 | 243 | 244 | void slrtaudio_record_init(void) { 245 | 246 | struct session *sess; 247 | struct le *le; 248 | 249 | for (le = sessionl.head; le; le = le->next) { 250 | sess = le->data; 251 | 252 | if (sess->stream) 253 | continue; 254 | 255 | sess->pcm = mem_zalloc(10 * 1920, NULL); 256 | sess->sampv = mem_zalloc(10 * 1920, NULL); 257 | aubuf_alloc(&sess->aubuf, 1920 * 10, 1920 * 100); 258 | sess->run_record = true; 259 | pthread_create(&sess->record_thread, NULL, 260 | record_thread, sess); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/modules/slrtaudio/slrtaudio.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "FLAC/stream_encoder.h" 3 | #include 4 | 5 | struct session { 6 | struct le le; 7 | struct ausrc_st *st_src; 8 | struct auplay_st *st_play; 9 | bool local; 10 | bool stream; 11 | bool run_src; 12 | bool run_play; 13 | bool run_record; 14 | int32_t *dstmix; 15 | struct aubuf *aubuf; 16 | pthread_t record_thread; 17 | FLAC__StreamEncoder *flac; 18 | int16_t *sampv; 19 | FLAC__int32 *pcm; 20 | int8_t ch; 21 | float *vumeter; 22 | struct call *call; 23 | int8_t track; 24 | bool talk; 25 | int16_t bufsz; 26 | }; 27 | 28 | extern struct list sessionl; 29 | void slrtaudio_record_init(void); 30 | void slrtaudio_record_set(bool active); 31 | int slrtaudio_record_get_timer(void); 32 | void slrtaudio_mono_set(bool active); 33 | void slrtaudio_mute_set(bool active); 34 | const struct odict* slrtaudio_get_interfaces(void); 35 | void slrtaudio_set_driver(int value); 36 | void slrtaudio_set_input(int value); 37 | void slrtaudio_set_first_input_channel(int value); 38 | void slrtaudio_set_output(int value); 39 | int slrtaudio_callback_in(void *out, void *in, unsigned int nframes, 40 | double stream_time, rtaudio_stream_status_t status, 41 | void *userdata); 42 | int slrtaudio_callback_out(void *out, void *in, unsigned int nframes, 43 | double stream_time, rtaudio_stream_status_t status, 44 | void *userdata); 45 | 46 | /* extern */ 47 | void ws_meter_process(unsigned int ch, float *in, unsigned long nframes); 48 | -------------------------------------------------------------------------------- /src/modules/webapp/contact.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "webapp.h" 5 | 6 | static struct odict *contacts = NULL; 7 | 8 | static char filename[256] = ""; 9 | 10 | 11 | const struct odict* webapp_contacts_get(void) { 12 | return (const struct odict *)contacts; 13 | } 14 | 15 | 16 | static int contact_register(const struct odict_entry *o) 17 | { 18 | struct le *le; 19 | struct pl pl; 20 | char buf[512] = {0}; 21 | char sip[100] = {0}; 22 | char name[50] = {0}; 23 | struct contacts *mycontacts = baresip_contacts(); 24 | 25 | int err = 0; 26 | 27 | if (o->type == ODICT_OBJECT) { 28 | le = o->u.odict->lst.head; 29 | 30 | } 31 | else { 32 | le = (void *)&o->le; 33 | } 34 | 35 | for (le=le; le; le=le->next) { 36 | const struct odict_entry *e = le->data; 37 | 38 | if (e->type != ODICT_STRING) { 39 | continue; 40 | } 41 | 42 | if (!str_cmp(e->key, "name")) { 43 | str_ncpy(name, e->u.str, sizeof(name)); 44 | } 45 | else if (!str_cmp(e->key, "sip")) { 46 | str_ncpy(sip, e->u.str, sizeof(sip)); 47 | } 48 | else if (!str_cmp(e->key, "command")) { 49 | continue; 50 | } 51 | else if (!str_cmp(e->key, "status")) { 52 | continue; 53 | } 54 | } 55 | 56 | re_snprintf(buf, sizeof(buf), "\"%s\"", 57 | name, sip); 58 | 59 | pl.p = buf; 60 | pl.l = strlen(buf); 61 | contact_add(mycontacts, NULL, &pl); 62 | 63 | return err; 64 | } 65 | 66 | 67 | void webapp_contact_add(const struct odict_entry *contact) 68 | { 69 | contact_register(contact); 70 | webapp_odict_add(contacts, contact); 71 | webapp_write_file_json(contacts, filename); 72 | } 73 | 74 | 75 | void webapp_contact_delete(const char *sip) 76 | { 77 | struct le *le; 78 | for (le = contacts->lst.head; le; le = le->next) { 79 | char o_sip[100]; 80 | const struct odict_entry *o = le->data; 81 | const struct odict_entry *e; 82 | 83 | e = odict_lookup(o->u.odict, "sip"); 84 | if (!e) 85 | continue; 86 | str_ncpy(o_sip, e->u.str, sizeof(o_sip)); 87 | 88 | if (!str_cmp(o_sip, sip)) { 89 | odict_entry_del(contacts, o->key); 90 | /* 91 | snprintf(aor, sizeof(aor), "sip:%s@%s", user, domain); 92 | mem_deref(uag_find_aor(aor)); 93 | */ 94 | webapp_write_file_json(contacts, filename); 95 | break; 96 | } 97 | } 98 | } 99 | 100 | 101 | int webapp_contacts_init(void) 102 | { 103 | char path[256] = ""; 104 | struct mbuf *mb; 105 | struct le *le; 106 | int err = 0; 107 | 108 | mb = mbuf_alloc(8192); 109 | if (!mb) 110 | return ENOMEM; 111 | 112 | err = conf_path_get(path, sizeof(path)); 113 | if (err) 114 | goto out; 115 | 116 | if (re_snprintf(filename, sizeof(filename), 117 | "%s/contacts.json", path) < 0) 118 | return ENOMEM; 119 | 120 | err = webapp_load_file(mb, filename); 121 | if (err) { 122 | err = odict_alloc(&contacts, DICT_BSIZE); 123 | } 124 | else { 125 | err = json_decode_odict(&contacts, DICT_BSIZE, 126 | (char *)mb->buf, mb->end, MAX_LEVELS); 127 | } 128 | if (err) 129 | goto out; 130 | 131 | for (le = contacts->lst.head; le; le = le->next) { 132 | contact_register(le->data); 133 | } 134 | 135 | out: 136 | mem_deref(mb); 137 | return err; 138 | } 139 | 140 | 141 | void webapp_contacts_close(void) 142 | { 143 | webapp_write_file_json(contacts, filename); 144 | mem_deref(contacts); 145 | } 146 | -------------------------------------------------------------------------------- /src/modules/webapp/jitter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "webapp.h" 6 | 7 | 8 | #define STIME 192 /* 192 = 48 Samples * 2ch * 2 Bytes = 1ms */ 9 | #define STARTUP_COUNT 200 10 | 11 | static int16_t *dummy[1920]; 12 | 13 | /* src/audio.c */ 14 | struct timestamp_recv { 15 | uint32_t first; 16 | uint32_t last; 17 | bool is_set; 18 | unsigned num_wraps; 19 | }; 20 | 21 | struct aurx { 22 | struct auplay_st *auplay; /**< Audio Player */ 23 | struct auplay_prm auplay_prm; /**< Audio Player parameters */ 24 | const struct aucodec *ac; /**< Current audio decoder */ 25 | struct audec_state *dec; /**< Audio decoder state (optional) */ 26 | struct aubuf *aubuf; /**< Incoming audio buffer */ 27 | size_t aubuf_maxsz; /**< Maximum aubuf size in [bytes] */ 28 | volatile bool aubuf_started; /**< Aubuf was started flag */ 29 | struct auresamp resamp; /**< Optional resampler for DSP */ 30 | struct list filtl; /**< Audio filters in decoding order */ 31 | char *module; /**< Audio player module name */ 32 | char *device; /**< Audio player device name */ 33 | void *sampv; /**< Sample buffer */ 34 | int16_t *sampv_rs; /**< Sample buffer for resampler */ 35 | uint32_t ptime; /**< Packet time for receiving */ 36 | int pt; /**< Payload type for incoming RTP */ 37 | double level_last; /**< Last audio level value [dBov] */ 38 | bool level_set; /**< True if level_last is set */ 39 | enum aufmt play_fmt; /**< Sample format for audio playback*/ 40 | enum aufmt dec_fmt; /**< Sample format for decoder */ 41 | bool need_conv; /**< Sample format conversion needed */ 42 | struct timestamp_recv ts_recv;/**< Receive timestamp state */ 43 | size_t last_sampc; 44 | 45 | struct { 46 | uint64_t aubuf_overrun; 47 | uint64_t aubuf_underrun; 48 | uint64_t n_discard; 49 | } stats; 50 | }; 51 | 52 | 53 | static void stereo_mono_ch_select(int16_t *buf, size_t sampc, enum sess_chmix chmix) 54 | { 55 | while (sampc--) { 56 | if (chmix == CH_MONO_L) { 57 | buf[1] = buf[0]; 58 | } 59 | if (chmix == CH_MONO_R) { 60 | buf[0] = buf[1]; 61 | } 62 | buf += 2; 63 | } 64 | } 65 | 66 | 67 | void webapp_jitter_reset(struct session *sess) 68 | { 69 | sess->jitter.startup = 0; 70 | sess->jitter.max = 0; 71 | sess->jitter.max_l = 0; 72 | sess->jitter.max_r = 0; 73 | sess->chmix = CH_STEREO; 74 | } 75 | 76 | 77 | void webapp_jitter(struct session *sess, int16_t *sampv, 78 | auplay_write_h *wh, unsigned int sampc, void *arg) 79 | { 80 | struct aurx *rx = arg; 81 | size_t frames = sampc / 2; 82 | 83 | int16_t max_l = 0, max_r = 0, max = 0; 84 | size_t bufsz = 0, pos = 0; 85 | 86 | /* set threshold to 500ms */ 87 | int16_t silence_threshold = 48000/sampc; 88 | 89 | bufsz = aubuf_cur_size(rx->aubuf); 90 | 91 | if (bufsz <= (sess->buffer * STIME) && 92 | sess->jitter.startup < STARTUP_COUNT) { 93 | memset(sampv, 0, sampc * sizeof(int16_t)); 94 | return; 95 | } 96 | 97 | if (bufsz <= (size_t)(sess->buffer * 0.8) * STIME && !sess->jitter.talk) { 98 | if (sess->jitter.delay_count >= 960) { 99 | /* only slightly increase buffer */ 100 | sess->jitter.talk = true; 101 | sess->jitter.silence_count = 0; 102 | sess->jitter.delay_count = 0; 103 | } 104 | memset(sampv, 0, sampc * sizeof(int16_t)); 105 | sess->jitter.delay_count += frames; 106 | return; 107 | } 108 | 109 | sess->jitter.bufsz = bufsz/STIME; 110 | wh(sampv, sampc, arg); 111 | 112 | /* GAIN */ 113 | pos = 0; 114 | for (uint16_t frame = 0; frame < sampc; frame++) 115 | { 116 | sampv[pos] = (int16_t)(sampv[pos] * sess->volume); 117 | ++pos; 118 | } 119 | 120 | if (sess->jitter.startup == STARTUP_COUNT) { 121 | if (sess->jitter.max_l > 400 && sess->jitter.max_r < 400) { 122 | sess->chmix = CH_MONO_L; 123 | sess->changed = true; 124 | } 125 | if (sess->jitter.max_r > 400 && sess->jitter.max_l < 400) { 126 | sess->chmix = CH_MONO_R; 127 | sess->changed = true; 128 | } 129 | sess->jitter.startup = STARTUP_COUNT+1; 130 | } 131 | 132 | /* Mono chmix */ 133 | if (sess->chmix != CH_STEREO) { 134 | stereo_mono_ch_select(sampv, sampc, sess->chmix); 135 | } 136 | 137 | /* Detect Talk spurt and channel usage */ 138 | pos = 0; 139 | for (uint16_t frame = 0; frame < frames; frame++) 140 | { 141 | if (sampv[pos] > max_l) 142 | max_l = sampv[pos]; 143 | if (sampv[pos+1] > max_r) 144 | max_r = sampv[pos+1]; 145 | pos += 2; 146 | } 147 | 148 | if (max_l > max_r) 149 | max = max_l; 150 | else 151 | max = max_r; 152 | 153 | sess->jitter.max = (sess->jitter.max + max) / 2; 154 | if (max_l > sess->jitter.max_l) 155 | sess->jitter.max_l = max_l; 156 | if (max_r > sess->jitter.max_r) 157 | sess->jitter.max_r = max_r; 158 | 159 | if (sess->jitter.startup <= STARTUP_COUNT) 160 | sess->jitter.startup++; 161 | 162 | if (sess->jitter.max < 400) { 163 | if (sess->jitter.silence_count < silence_threshold) { 164 | sess->jitter.silence_count++; 165 | } 166 | else { 167 | sess->jitter.talk = false; 168 | } 169 | } 170 | else { 171 | sess->jitter.talk = true; 172 | sess->jitter.silence_count = 0; 173 | } 174 | 175 | if (sess->jitter.talk) 176 | return; 177 | 178 | bufsz = aubuf_cur_size(rx->aubuf); 179 | /* Reduce latency */ 180 | if (bufsz >= (sess->buffer + 100) * STIME) { 181 | /* only slightly reduce buffer */ 182 | sess->jitter.talk = true; 183 | sess->jitter.silence_count = 0; 184 | wh(dummy, 1920, arg); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/modules/webapp/module.mk: -------------------------------------------------------------------------------- 1 | # 2 | # module.mk 3 | # 4 | # Copyright (C) 2013-2020 studio-link.de 5 | # 6 | 7 | MOD := webapp 8 | $(MOD)_SRCS += webapp.c account.c contact.c option.c 9 | $(MOD)_SRCS += ws_baresip.c ws_contacts.c ws_meter.c ws_calls.c 10 | $(MOD)_SRCS += ws_options.c ws_rtaudio.c 11 | $(MOD)_SRCS += websocket.c utils.c sessions.c jitter.c 12 | $(MOD)_LFLAGS += -lm -lFLAC 13 | 14 | include mk/mod.mk 15 | -------------------------------------------------------------------------------- /src/modules/webapp/option.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "webapp.h" 6 | 7 | static struct odict *options = NULL; 8 | static char filename[256] = ""; 9 | static char command[255] = {0}; 10 | 11 | 12 | const struct odict* webapp_options_get(void) 13 | { 14 | return (const struct odict *)options; 15 | } 16 | 17 | 18 | void webapp_options_set(char *key, char *value) 19 | { 20 | int err = 0; 21 | info("webapp/option: %s: %s\n", key, value); 22 | #ifdef SLPLUGIN 23 | if (!str_cmp(key, "bypass")) { 24 | if (!str_cmp(value, "false")) { 25 | effect_set_bypass(false); 26 | } 27 | else { 28 | effect_set_bypass(true); 29 | } 30 | } 31 | #else 32 | if (!str_cmp(key, "monitoring")) { 33 | if (!str_cmp(value, "true")) { 34 | slaudio_monitor_set(true); 35 | } 36 | else { 37 | slaudio_monitor_set(false); 38 | } 39 | } 40 | if (!str_cmp(key, "monostream")) { 41 | if (!str_cmp(value, "true")) { 42 | slaudio_monostream_set(true); 43 | } 44 | else { 45 | slaudio_monostream_set(false); 46 | webapp_options_set("monorecord", ""); 47 | } 48 | } 49 | if (!str_cmp(key, "monorecord")) { 50 | if (!str_cmp(value, "true")) { 51 | slaudio_monorecord_set(true); 52 | webapp_options_set("monostream", "true"); 53 | } 54 | else { 55 | slaudio_monorecord_set(false); 56 | } 57 | } 58 | if (!str_cmp(key, "record")) { 59 | if (!str_cmp(value, "false")) { 60 | slaudio_record_set(false); 61 | } 62 | else { 63 | slaudio_record_set(true); 64 | } 65 | } 66 | if (!str_cmp(key, "mute")) { 67 | if (!str_cmp(value, "false")) { 68 | slaudio_mute_set(false); 69 | } 70 | else { 71 | slaudio_mute_set(true); 72 | } 73 | } 74 | if (!str_cmp(key, "onair")) { 75 | static struct call *call = NULL; 76 | if (!str_cmp(value, "false")) { 77 | webapp_session_stop_stream(); 78 | } 79 | else { 80 | struct config *cfg = conf_config(); 81 | #if defined (DARWIN) 82 | re_snprintf(command, sizeof(command), 83 | "open https://stream.studio-link.de/stream/login/%s?version=2", 84 | cfg->sip.uuid); 85 | #elif defined (WIN32) 86 | re_snprintf(command, sizeof(command), 87 | "start https://stream.studio-link.de/stream/login/%s?version=2", 88 | cfg->sip.uuid); 89 | #else 90 | re_snprintf(command, sizeof(command), 91 | "xdg-open https://stream.studio-link.de/stream/login/%s?version=2", 92 | cfg->sip.uuid); 93 | #endif 94 | err = system(command); 95 | 96 | if (err) {}; 97 | 98 | ua_connect(webapp_get_main_ua(), &call, NULL, 99 | "stream@studio.link", VIDMODE_OFF); 100 | 101 | webapp_call_update(call, "Outgoing"); 102 | } 103 | } 104 | if (!str_cmp(key, "record-folder")) { 105 | slaudio_record_open_folder(); 106 | } 107 | #endif 108 | 109 | odict_entry_del(options, key); 110 | odict_entry_add(options, key, ODICT_STRING, value); 111 | ws_send_json(WS_OPTIONS, options); 112 | webapp_write_file_json(options, filename); 113 | } 114 | 115 | 116 | char* webapp_options_getv(char *key, char *def) 117 | { 118 | const struct odict_entry *e = NULL; 119 | 120 | e = odict_lookup(options, key); 121 | 122 | if (!e) 123 | return def; 124 | 125 | return e->u.str; 126 | } 127 | 128 | 129 | int webapp_options_init(void) 130 | { 131 | char path[256] = {0}; 132 | char *key; 133 | struct mbuf *mb; 134 | int err = 0; 135 | 136 | mb = mbuf_alloc(8192); 137 | if (!mb) 138 | return ENOMEM; 139 | 140 | err = conf_path_get(path, sizeof(path)); 141 | if (err) 142 | goto out; 143 | 144 | if (re_snprintf(filename, sizeof(filename), 145 | "%s/options.json", path) < 0) 146 | return ENOMEM; 147 | 148 | err = webapp_load_file(mb, filename); 149 | if (err) { 150 | err = odict_alloc(&options, DICT_BSIZE); 151 | } 152 | else { 153 | err = json_decode_odict(&options, DICT_BSIZE, 154 | (char *)mb->buf, mb->end, MAX_LEVELS); 155 | } 156 | if (err) 157 | goto out; 158 | odict_entry_del(options, "bypass"); 159 | odict_entry_del(options, "record"); 160 | odict_entry_del(options, "auto-mix-n-1"); 161 | odict_entry_del(options, "onair"); 162 | odict_entry_del(options, "raisehand"); 163 | odict_entry_del(options, "afk"); 164 | odict_entry_del(options, "mute"); 165 | 166 | #ifndef SLPLUGIN 167 | str_dup(&key, webapp_options_getv("monorecord", "true")); 168 | webapp_options_set("monorecord", key); 169 | mem_deref(key); 170 | 171 | str_dup(&key, webapp_options_getv("monostream", "true")); 172 | webapp_options_set("monostream", key); 173 | mem_deref(key); 174 | 175 | str_dup(&key, webapp_options_getv("monitoring", "")); 176 | webapp_options_set("monitoring", key); 177 | mem_deref(key); 178 | #endif 179 | 180 | out: 181 | mem_deref(mb); 182 | return err; 183 | } 184 | 185 | 186 | void webapp_options_close(void) 187 | { 188 | webapp_write_file_json(options, filename); 189 | mem_deref(options); 190 | } 191 | -------------------------------------------------------------------------------- /src/modules/webapp/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "webapp.h" 6 | 7 | void webapp_odict_add(struct odict *og, const struct odict_entry *eg) 8 | { 9 | struct le *le; 10 | struct odict *o; 11 | int err = 0; 12 | char index[64]; 13 | size_t index_cnt=0; 14 | 15 | err = odict_alloc(&o, DICT_BSIZE); 16 | if (err) 17 | return; 18 | 19 | le = (void *)&eg->le; 20 | if (!le) 21 | return; 22 | 23 | for (le=le->list->head; le; le=le->next) { 24 | const struct odict_entry *e = le->data; 25 | if (!str_cmp(e->key, "command")) 26 | continue; 27 | odict_entry_add(o, e->key, e->type, e->u.str); 28 | } 29 | 30 | /* Limited Loop 31 | * No one will need more than 100 accounts for a personal computer 32 | */ 33 | for (int i=0; i<100; i++) { 34 | re_snprintf(index, sizeof(index), "%u", index_cnt); 35 | if (!odict_lookup(og, index)) { 36 | break; 37 | } 38 | index_cnt = index_cnt + 1; 39 | } 40 | 41 | odict_entry_add(og, index, ODICT_OBJECT, o); 42 | 43 | mem_deref(o); 44 | } 45 | 46 | 47 | int webapp_write_file(char *string, char *filename) 48 | { 49 | FILE *f = NULL; 50 | int err = 0; 51 | 52 | f = fopen(filename, "w"); 53 | if (!f) 54 | return errno; 55 | 56 | re_fprintf(f, "%s", string); 57 | 58 | if (f) 59 | (void)fclose(f); 60 | 61 | return err; 62 | } 63 | 64 | 65 | int webapp_write_file_json(struct odict *json, char *filename) 66 | { 67 | FILE *f = NULL; 68 | int err = 0; 69 | 70 | f = fopen(filename, "w"); 71 | if (!f) 72 | return errno; 73 | 74 | re_fprintf(f, "%H", json_encode_odict, json); 75 | 76 | if (f) 77 | (void)fclose(f); 78 | 79 | return err; 80 | } 81 | 82 | 83 | int webapp_load_file(struct mbuf *mb, char *filename) 84 | { 85 | int err = 0, fd = open(filename, O_RDONLY); 86 | 87 | if (fd < 0) 88 | return errno; 89 | 90 | for (;;) { 91 | uint8_t buf[1024]; 92 | 93 | const ssize_t n = read(fd, (void *)buf, sizeof(buf)); 94 | if (n < 0) { 95 | err = errno; 96 | break; 97 | } 98 | else if (n == 0) 99 | break; 100 | 101 | err |= mbuf_write_mem(mb, buf, n); 102 | } 103 | 104 | (void)close(fd); 105 | 106 | return err; 107 | } 108 | 109 | 110 | bool webapp_active_calls(void) 111 | { 112 | struct le *le; 113 | 114 | for (le = list_head(uag_list()); le; le = le->next) { 115 | 116 | struct ua *ua = le->data; 117 | 118 | if (ua_call(ua)) 119 | return true; 120 | } 121 | 122 | return false; 123 | } 124 | -------------------------------------------------------------------------------- /src/modules/webapp/webapp.h: -------------------------------------------------------------------------------- 1 | #define SLVERSION "SLVERSION_T" 2 | #ifndef SLPLUGIN 3 | #include 4 | #include "FLAC/stream_encoder.h" 5 | #endif 6 | 7 | #ifdef SLPLUGIN 8 | /* 9 | * effect/effect.c 10 | */ 11 | void effect_set_bypass(bool value); 12 | #endif 13 | 14 | enum ws_type { 15 | WS_BARESIP, 16 | WS_CALLS, 17 | WS_CONTACT, 18 | WS_CHAT, 19 | WS_METER, 20 | WS_CPU, 21 | WS_OPTIONS, 22 | WS_RTAUDIO 23 | }; 24 | 25 | enum webapp_call_state { 26 | WS_CALL_OFF, 27 | WS_CALL_RINGING, 28 | WS_CALL_ON 29 | }; 30 | 31 | enum { 32 | DICT_BSIZE = 32, 33 | MAX_LEVELS = 8, 34 | }; 35 | 36 | struct webapp { 37 | struct websock_conn *wc_srv; 38 | struct le le; 39 | enum ws_type ws_type; 40 | }; 41 | 42 | extern enum webapp_call_state webapp_call_status; 43 | extern struct odict *webapp_calls; 44 | 45 | 46 | char *webapp_provisioning_host(void); 47 | 48 | enum sess_chmix { 49 | CH_STEREO, 50 | CH_MONO_L, 51 | CH_MONO_R 52 | }; 53 | 54 | struct sess_jitter { 55 | bool talk; 56 | int16_t bufsz; 57 | int16_t max; 58 | int16_t max_l; 59 | int16_t max_r; 60 | int16_t startup; 61 | int16_t silence_count; 62 | int16_t delay_count; 63 | }; 64 | 65 | struct session { 66 | struct le le; 67 | struct ausrc_st *st_src; 68 | struct auplay_st *st_play; 69 | int32_t *dstmix; 70 | int8_t ch; 71 | bool run_src; 72 | bool run_play; 73 | struct call *call; 74 | bool stream; /* only used for standalone */ 75 | bool local; /* only used for standalone */ 76 | int8_t track; 77 | struct sess_jitter jitter; 78 | enum sess_chmix chmix; 79 | char *state; 80 | bool changed; 81 | size_t buffer; 82 | double volume; 83 | #ifdef SLPLUGIN 84 | bool primary; 85 | bool run_auto_mix; 86 | bool bypass; 87 | bool effect_ready; 88 | #else 89 | bool run_record; 90 | struct aubuf *aubuf; 91 | pthread_t record_thread; 92 | FLAC__StreamEncoder *flac; 93 | int16_t *sampv; 94 | FLAC__int32 *pcm; 95 | float *vumeter; 96 | FLAC__StreamMetadata *meta[2]; 97 | #endif 98 | }; 99 | 100 | /* 101 | * sessions.c 102 | */ 103 | void webapp_sessions_init(void); 104 | void webapp_sessions_close(void); 105 | int webapp_session_delete(char * const sess_id, struct call *call); 106 | int8_t webapp_call_update(struct call *call, char *state); 107 | int webapp_session_stop_stream(void); 108 | struct call* webapp_session_get_call(char * const sess_id); 109 | bool webapp_session_available(void); 110 | void webapp_session_chmix(char * const sess_id); 111 | void webapp_session_bufferinc(char * const sess_id); 112 | void webapp_session_bufferdec(char * const sess_id); 113 | void webapp_session_volume(char * const sess_id, char * const volume); 114 | 115 | /* 116 | * account.c 117 | */ 118 | struct ua* webapp_get_main_ua(void); 119 | struct ua* webapp_get_quick_ua(void); 120 | int webapp_accounts_init(void); 121 | void webapp_accounts_close(void); 122 | const struct odict* webapp_accounts_get(void); 123 | void webapp_account_add(const struct odict_entry *acc); 124 | void webapp_account_delete(char *user, char *domain); 125 | void webapp_account_current(void); 126 | void webapp_account_status(const char *aor, bool status); 127 | 128 | /* 129 | * contact.c 130 | */ 131 | int webapp_contacts_init(void); 132 | void webapp_contacts_close(void); 133 | void webapp_contact_add(const struct odict_entry *contact); 134 | void webapp_contact_delete(const char *sip); 135 | const struct odict* webapp_contacts_get(void); 136 | 137 | /* 138 | * option.c 139 | */ 140 | int webapp_options_init(void); 141 | void webapp_options_close(void); 142 | const struct odict* webapp_options_get(void); 143 | void webapp_options_set(char *key, char *value); 144 | char* webapp_options_getv(char *key, char *def); 145 | 146 | /* 147 | * ws_*.c 148 | */ 149 | void webapp_ws_baresip(const struct websock_hdr *hdr, 150 | struct mbuf *mb, void *arg); 151 | void webapp_ws_calls(const struct websock_hdr *hdr, 152 | struct mbuf *mb, void *arg); 153 | void webapp_ws_contacts(const struct websock_hdr *hdr, 154 | struct mbuf *mb, void *arg); 155 | void webapp_ws_chat(const struct websock_hdr *hdr, 156 | struct mbuf *mb, void *arg); 157 | void webapp_ws_meter(const struct websock_hdr *hdr, 158 | struct mbuf *mb, void *arg); 159 | void webapp_ws_options(const struct websock_hdr *hdr, 160 | struct mbuf *mb, void *arg); 161 | void ws_meter_process(unsigned int ch, float *in, unsigned long nframes); 162 | void webapp_ws_meter_init(void); 163 | void webapp_ws_meter_close(void); 164 | int webapp_ws_rtaudio_init(void); 165 | void webapp_ws_rtaudio_close(void); 166 | void webapp_ws_rtaudio(const struct websock_hdr *hdr, 167 | struct mbuf *mb, void *arg); 168 | void webapp_ws_rtaudio_sync(void); 169 | 170 | /* 171 | * websocket.c 172 | */ 173 | void ws_send_all(enum ws_type ws_type, char *str); 174 | void ws_send_all_b(enum ws_type ws_type, struct mbuf *mb); 175 | void ws_send_json(enum ws_type type, const struct odict *o); 176 | void srv_websock_close_handler(int err, void *arg); 177 | int webapp_ws_handler(struct http_conn *conn, enum ws_type type, 178 | const struct http_msg *msg, 179 | websock_recv_h *recvh); 180 | void webapp_ws_init(void); 181 | void webapp_ws_close(void); 182 | 183 | /* 184 | * utils.c 185 | */ 186 | void webapp_odict_add(struct odict *og, const struct odict_entry *e); 187 | int webapp_write_file(char *string, char *filename); 188 | int webapp_write_file_json(struct odict *json, char *filename); 189 | int webapp_load_file(struct mbuf *mb, char *filename); 190 | struct call* webapp_get_call(char *sid); 191 | bool webapp_active_calls(void); 192 | 193 | /* 194 | * slaudio module 195 | */ 196 | const struct odict* slaudio_get_interfaces(void); 197 | void slaudio_record_set(bool status); 198 | void slaudio_record_open_folder(void); 199 | void slaudio_monorecord_set(bool status); 200 | void slaudio_monostream_set(bool status); 201 | void slaudio_mute_set(bool status); 202 | void slaudio_monitor_set(bool status); 203 | int slaudio_reset(void); 204 | void slaudio_set_driver(int value); 205 | void slaudio_set_input(int value); 206 | void slaudio_set_first_input_channel(int value); 207 | void slaudio_set_output(int value); 208 | 209 | /* 210 | * jitter.c 211 | */ 212 | void webapp_jitter_reset(struct session *sess); 213 | void webapp_jitter(struct session *sess, int16_t *sampv, 214 | auplay_write_h *wh, unsigned int sampc, void *arg); 215 | -------------------------------------------------------------------------------- /src/modules/webapp/websocket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "webapp.h" 4 | 5 | static struct list ws_srv_conns; 6 | static struct websock *ws; 7 | static struct tmr tmr_exit; 8 | 9 | static int ws_count = 0; 10 | 11 | 12 | int webapp_ws_handler(struct http_conn *conn, enum ws_type type, 13 | const struct http_msg *msg, websock_recv_h *recvh) 14 | { 15 | struct webapp *webapp; 16 | webapp = mem_zalloc(sizeof(*webapp), NULL); 17 | websock_accept(&webapp->wc_srv, ws, conn, msg, 18 | 0, recvh, 19 | srv_websock_close_handler, webapp); 20 | list_append(&ws_srv_conns, &webapp->le, webapp); 21 | debug("websocket created: %p\n", webapp->wc_srv); 22 | webapp->ws_type = type; 23 | 24 | ++ws_count; 25 | tmr_cancel(&tmr_exit); 26 | 27 | return 0; 28 | } 29 | 30 | 31 | void ws_send_all(enum ws_type ws_type, char *str) { 32 | struct le *le; 33 | for (le = list_head(&ws_srv_conns); le; le = le->next) { 34 | struct webapp *webapp = le->data; 35 | if (webapp->ws_type == ws_type) { 36 | websock_send(webapp->wc_srv, WEBSOCK_TEXT, 37 | "%s", str); 38 | } 39 | } 40 | } 41 | 42 | 43 | void ws_send_all_b(enum ws_type ws_type, struct mbuf *mb) { 44 | struct le *le; 45 | for (le = list_head(&ws_srv_conns); le; le = le->next) { 46 | struct webapp *webapp = le->data; 47 | if (webapp->ws_type == ws_type) { 48 | websock_send(webapp->wc_srv, WEBSOCK_TEXT, 49 | "%b", mb->buf, mb->end); 50 | } 51 | } 52 | } 53 | 54 | 55 | static int print_handler(const char *p, size_t size, void *arg) 56 | { 57 | return mbuf_write_mem(arg, (uint8_t *)p, size); 58 | } 59 | 60 | 61 | void ws_send_json(enum ws_type type, const struct odict *o) 62 | { 63 | struct re_printf pf; 64 | struct mbuf *mbr = mbuf_alloc(4096); 65 | pf.vph = print_handler; 66 | pf.arg = mbr; 67 | 68 | json_encode_odict(&pf, o); 69 | ws_send_all_b(type, mbr); 70 | mem_deref(mbr); 71 | } 72 | 73 | 74 | static void exit_baresip(void *arg) { 75 | ua_stop_all(false); 76 | } 77 | 78 | 79 | void srv_websock_close_handler(int err, void *arg) 80 | { 81 | (void)err; 82 | struct webapp *webapp_p = arg; 83 | debug("websocket closed: %p\n", webapp_p->wc_srv); 84 | mem_deref(webapp_p->wc_srv); 85 | list_unlink(&webapp_p->le); 86 | mem_deref(webapp_p); 87 | 88 | --ws_count; 89 | #ifndef SLPLUGIN 90 | /* close studio link if browser is closed */ 91 | if (!webapp_active_calls() && !ws_count) { 92 | tmr_start(&tmr_exit, 800, exit_baresip, NULL); 93 | } 94 | #endif 95 | } 96 | 97 | 98 | static void websock_shutdown_handler(void *arg) 99 | { 100 | info("websocket shutdown\n"); 101 | } 102 | 103 | 104 | void webapp_ws_init(void) 105 | { 106 | int err; 107 | tmr_init(&tmr_exit); 108 | 109 | list_init(&ws_srv_conns); 110 | err = websock_alloc(&ws, websock_shutdown_handler, NULL); 111 | if (err) { 112 | mem_deref(ws); 113 | return; 114 | } 115 | } 116 | 117 | 118 | void webapp_ws_close(void) 119 | { 120 | info("webapp_ws_close\n"); 121 | tmr_cancel(&tmr_exit); 122 | struct le *le; 123 | if (!list_isempty(&ws_srv_conns)) { 124 | for (le = list_head(&ws_srv_conns); le; le = le->next) { 125 | if (le->data) { 126 | struct webapp *webapp = le->data; 127 | mem_deref(webapp->wc_srv); 128 | } 129 | } 130 | } 131 | 132 | mem_deref(ws); 133 | list_flush(&ws_srv_conns); 134 | } 135 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_baresip.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "webapp.h" 4 | 5 | #define SIP_EMPTY "{ \"callback\": \"CLOSED\",\ 6 | \"message\": \"SIP Number Empty\" }" 7 | 8 | #define SIP_MAX_CALLS "{ \"callback\": \"CLOSED\",\ 9 | \"message\": \"Max Calls reached...\" }" 10 | 11 | static void sip_delete(struct odict *cmd, const struct odict_entry *e) 12 | { 13 | char user[50]; 14 | char domain[70]; 15 | 16 | e = odict_lookup(cmd, "user"); 17 | if (e) 18 | str_ncpy(user, e->u.str, sizeof(user)); 19 | e = odict_lookup(cmd, "domain"); 20 | if (e) 21 | str_ncpy(domain, e->u.str, sizeof(domain)); 22 | webapp_account_delete(user, domain); 23 | warning("delete executed\n"); 24 | ws_send_json(WS_BARESIP, webapp_accounts_get()); 25 | } 26 | 27 | 28 | void webapp_ws_baresip(const struct websock_hdr *hdr, 29 | struct mbuf *mb, void *arg) 30 | { 31 | struct odict *cmd = NULL; 32 | const struct odict_entry *e = NULL; 33 | struct call *call = NULL; 34 | int err = 0; 35 | 36 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 37 | mbuf_get_left(mb), MAX_LEVELS); 38 | if (err) 39 | goto out; 40 | 41 | e = odict_lookup(cmd, "command"); 42 | if (!e) 43 | goto out; 44 | 45 | if (!str_cmp(e->u.str, "call")) { 46 | e = odict_lookup(cmd, "dial"); 47 | if (!str_cmp(e->u.str, "")) { 48 | ws_send_all(WS_CALLS, SIP_EMPTY); 49 | } 50 | else { 51 | if (!webapp_session_available()) { 52 | ws_send_all(WS_CALLS, SIP_MAX_CALLS); 53 | goto out; 54 | } 55 | 56 | ua_connect(uag_current(), &call, NULL, 57 | e->u.str, VIDMODE_OFF); 58 | webapp_call_update(call, "Outgoing"); 59 | } 60 | } 61 | else if (!str_cmp(e->u.str, "addsip")) { 62 | webapp_account_add(e); 63 | ws_send_json(WS_BARESIP, webapp_accounts_get()); 64 | } 65 | else if (!str_cmp(e->u.str, "deletesip")) { 66 | sip_delete(cmd, e); 67 | } 68 | else if (!str_cmp(e->u.str, "uagcurrent")) { 69 | char aorfind[100] = {0}; 70 | 71 | e = odict_lookup(cmd, "aor"); 72 | if (!e) 73 | goto out; 74 | snprintf(aorfind, sizeof(aorfind), "sip:%s", e->u.str); 75 | uag_current_set(uag_find_aor(aorfind)); 76 | webapp_account_current(); 77 | ws_send_json(WS_BARESIP, webapp_accounts_get()); 78 | } 79 | 80 | out: 81 | mem_deref(cmd); 82 | } 83 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_calls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "webapp.h" 4 | 5 | #define SIP_CLOSED "{ \"callback\": \"CLOSED\"}" 6 | 7 | 8 | void webapp_ws_calls(const struct websock_hdr *hdr, 9 | struct mbuf *mb, void *arg) 10 | { 11 | struct odict *cmd = NULL; 12 | struct call *call = NULL; 13 | const struct odict_entry *e = NULL; 14 | const struct odict_entry *key = NULL; 15 | int err = 0; 16 | 17 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 18 | mbuf_get_left(mb), MAX_LEVELS); 19 | if (err) 20 | goto out; 21 | 22 | e = odict_lookup(cmd, "command"); 23 | if (!e) 24 | goto out; 25 | 26 | key = odict_lookup(cmd, "key"); 27 | if (!key) 28 | goto out; 29 | 30 | call = webapp_session_get_call(key->u.str); 31 | if (!call) 32 | goto out; 33 | 34 | if (!str_cmp(e->u.str, "accept")) { 35 | ua_answer(call_get_ua(call), call, VIDMODE_OFF); 36 | } 37 | else if (!str_cmp(e->u.str, "hangup")) { 38 | webapp_session_delete(key->u.str, NULL); 39 | if (!webapp_active_calls()) { 40 | ws_send_all(WS_CALLS, SIP_CLOSED); 41 | } 42 | } 43 | else if (!str_cmp(e->u.str, "chmix")) { 44 | webapp_session_chmix(key->u.str); 45 | } 46 | else if (!str_cmp(e->u.str, "bufferinc")) { 47 | webapp_session_bufferinc(key->u.str); 48 | } 49 | else if (!str_cmp(e->u.str, "bufferdec")) { 50 | webapp_session_bufferdec(key->u.str); 51 | } 52 | else if (!str_cmp(e->u.str, "volume")) { 53 | e = odict_lookup(cmd, "value"); 54 | if (!e) 55 | goto out; 56 | webapp_session_volume(key->u.str, e->u.str); 57 | } 58 | else if (!str_cmp(e->u.str, "dtmf")) { 59 | e = odict_lookup(cmd, "tone"); 60 | if (!e) 61 | goto out; 62 | 63 | call_send_digit(call, e->u.str[0]); 64 | } 65 | 66 | out: 67 | mem_deref(cmd); 68 | } 69 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_contacts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "webapp.h" 4 | 5 | void webapp_ws_contacts(const struct websock_hdr *hdr, 6 | struct mbuf *mb, void *arg) 7 | { 8 | struct odict *cmd = NULL; 9 | const struct odict_entry *e = NULL; 10 | int err = 0; 11 | 12 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 13 | mbuf_get_left(mb), MAX_LEVELS); 14 | if (err) 15 | goto out; 16 | 17 | e = odict_lookup(cmd, "command"); 18 | if (!e) 19 | goto out; 20 | 21 | if (!str_cmp(e->u.str, "addcontact")) { 22 | webapp_contact_add(e); 23 | ws_send_json(WS_CONTACT, webapp_contacts_get()); 24 | ws_send_json(WS_CALLS, webapp_calls); 25 | } 26 | else if (!str_cmp(e->u.str, "deletecontact")) { 27 | char sip[100] = {0}; 28 | 29 | e = odict_lookup(cmd, "sip"); 30 | if (!e) 31 | goto out; 32 | 33 | str_ncpy(sip, e->u.str, sizeof(sip)); 34 | webapp_contact_delete(sip); 35 | ws_send_json(WS_CONTACT, webapp_contacts_get()); 36 | } 37 | 38 | out: 39 | 40 | mem_deref(cmd); 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_meter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "webapp.h" 7 | 8 | 9 | #define MAX_METERS 32 10 | 11 | static float bias = 1.0f; 12 | static float peaks[MAX_METERS] = {0}; 13 | static float sent_peaks[MAX_METERS] = {0}; 14 | 15 | static struct tmr tmr; 16 | 17 | 18 | /* Read and reset the recent peak sample */ 19 | static void webapp_read_peaks(void) 20 | { 21 | memcpy(sent_peaks, peaks, sizeof(peaks)); 22 | memset(peaks, 0, sizeof(peaks)); 23 | } 24 | 25 | 26 | void webapp_ws_meter(const struct websock_hdr *hdr, 27 | struct mbuf *mb, void *arg) 28 | { 29 | struct odict *cmd = NULL; 30 | int err = 0; 31 | 32 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 33 | mbuf_get_left(mb), MAX_LEVELS); 34 | if (err) 35 | goto out; 36 | 37 | out: 38 | mem_deref(cmd); 39 | } 40 | 41 | 42 | #ifndef SLPLUGIN 43 | int slaudio_record_get_timer(void); 44 | #endif 45 | static void write_ws(void) 46 | { 47 | int n; 48 | int i; 49 | float db; 50 | char one_peak[100]; 51 | char p[2048]; 52 | 53 | p[0] = '\0'; 54 | /* Record time */ 55 | #ifndef SLPLUGIN 56 | int hours; 57 | int min; 58 | int sec; 59 | int msec; 60 | int record_time; 61 | 62 | record_time = slaudio_record_get_timer(); 63 | hours = record_time / 1000 / 3600; 64 | min = (record_time / 1000 / 60) - (hours * 60); 65 | sec = (record_time / 1000) - (hours * 3600) - (min * 60); 66 | msec = record_time - (hours * 3600 * 1000) - (min * 60 * 1000) - (sec * 1000); 67 | 68 | re_snprintf(one_peak, 100, "%d:%02d:%02d:%03d 0 ", hours, min, sec, msec); 69 | #else 70 | re_snprintf(one_peak, 100, "0 0 "); 71 | #endif 72 | strcat((char*)p, one_peak); 73 | 74 | for (i=0; i peaks[ch]) { 101 | peaks[ch] = s; 102 | } 103 | } 104 | } 105 | 106 | 107 | void webapp_ws_meter_init(void) 108 | { 109 | tmr_init(&tmr); 110 | tmr_start(&tmr, 150, tmr_handler, NULL); 111 | } 112 | 113 | 114 | void webapp_ws_meter_close(void) 115 | { 116 | tmr_cancel(&tmr); 117 | } 118 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_options.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "webapp.h" 4 | 5 | 6 | void webapp_ws_options(const struct websock_hdr *hdr, 7 | struct mbuf *mb, void *arg) 8 | { 9 | struct odict *cmd = NULL; 10 | const struct odict_entry *key = NULL; 11 | const struct odict_entry *value = NULL; 12 | int err = 0; 13 | 14 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 15 | mbuf_get_left(mb), MAX_LEVELS); 16 | if (err) 17 | goto out; 18 | 19 | key = odict_lookup(cmd, "key"); 20 | if (!key) 21 | goto out; 22 | 23 | value = odict_lookup(cmd, "value"); 24 | if (!value) 25 | goto out; 26 | 27 | webapp_options_set(key->u.str, value->u.str); 28 | 29 | out: 30 | mem_deref(cmd); 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/webapp/ws_rtaudio.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ws_rtaudio.c RTAUDIO 3 | * 4 | * Copyright (C) 2018-2019 studio-link.de 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include "webapp.h" 10 | 11 | 12 | void webapp_ws_rtaudio_sync(void) 13 | { 14 | #ifndef SLPLUGIN 15 | ws_send_json(WS_RTAUDIO, slaudio_get_interfaces()); 16 | #endif 17 | } 18 | 19 | 20 | void webapp_ws_rtaudio(const struct websock_hdr *hdr, 21 | struct mbuf *mb, void *arg) 22 | { 23 | 24 | #ifndef SLPLUGIN 25 | struct odict *cmd = NULL; 26 | const struct odict_entry *e = NULL; 27 | int err; 28 | 29 | err = json_decode_odict(&cmd, DICT_BSIZE, (const char *)mbuf_buf(mb), 30 | mbuf_get_left(mb), MAX_LEVELS); 31 | if (err) 32 | goto out; 33 | 34 | e = odict_lookup(cmd, "command"); 35 | if (!e) 36 | goto out; 37 | 38 | if (!str_cmp(e->u.str, "driver")) { 39 | e = odict_lookup(cmd, "id"); 40 | slaudio_set_driver(e->u.integer); 41 | goto out; 42 | } 43 | 44 | if (!str_cmp(e->u.str, "input")) { 45 | e = odict_lookup(cmd, "id"); 46 | slaudio_set_input(e->u.integer); 47 | goto out; 48 | } 49 | 50 | if (!str_cmp(e->u.str, "first_input_channel")) { 51 | e = odict_lookup(cmd, "id"); 52 | slaudio_set_first_input_channel(e->u.integer); 53 | goto out; 54 | } 55 | 56 | if (!str_cmp(e->u.str, "output")) { 57 | e = odict_lookup(cmd, "id"); 58 | slaudio_set_output(e->u.integer); 59 | goto out; 60 | } 61 | 62 | if (!str_cmp(e->u.str, "reload")) { 63 | slaudio_reset(); 64 | goto out; 65 | } 66 | 67 | out: 68 | mem_deref(cmd); 69 | #endif 70 | } 71 | 72 | 73 | int webapp_ws_rtaudio_init(void) 74 | { 75 | int err = 0; 76 | 77 | return err; 78 | } 79 | 80 | 81 | void webapp_ws_rtaudio_close(void) 82 | { 83 | } 84 | -------------------------------------------------------------------------------- /src/webui/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | #npm run dev 4 | npm run prod 5 | mkdir -p headers 6 | xxd -i dist/index.html > headers/index_html.h 7 | xxd -i dist/app.css > headers/css.h 8 | xxd -i dist/app.js > headers/js.h 9 | find dist/images -type f | xargs -I{} xxd -i {} > headers/images.h 10 | mkdir -p ../modules/webapp/assets/ 11 | cp -a headers/*.h ../modules/webapp/assets/ 12 | -------------------------------------------------------------------------------- /src/webui/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dist/app.js": "/dist/app.js", 3 | "/dist/app.css": "/dist/app.css", 4 | "/dist/index.html": "/dist/index.html", 5 | "/dist/images/logo.svg": "/dist/images/logo.svg", 6 | "/dist/images/logo_plugin.svg": "/dist/images/logo_plugin.svg", 7 | "/dist/images/logo_standalone.svg": "/dist/images/logo_standalone.svg" 8 | } 9 | -------------------------------------------------------------------------------- /src/webui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webui", 3 | "version": "1.0.0", 4 | "description": "webui", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "npm run development", 8 | "development": "mix", 9 | "watch": "mix watch", 10 | "watch-poll": "mix watch -- --watch-options-poll=1000", 11 | "hot": "mix watch --hot", 12 | "prod": "npm run production", 13 | "production": "mix --production" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@fortawesome/fontawesome-svg-core": "^1.2.36", 20 | "@fortawesome/free-regular-svg-icons": "^5.15.4", 21 | "@fortawesome/free-solid-svg-icons": "^5.15.4", 22 | "bootbox": "^5.5.2", 23 | "bootstrap": "4.6.1", 24 | "cross-env": "^7.0.3", 25 | "handlebars": "^4.7.7", 26 | "handlebars-loader": "^1.7.1", 27 | "jquery": "^3.6.0", 28 | "laravel-mix": "^6.0.31", 29 | "laravel-mix-copy-watched": "^2.3.1", 30 | "laravel-mix-purgecss": "^6.0.0", 31 | "parsleyjs": "^2.9.2", 32 | "popper.js": "^1.16.1", 33 | "resolve-url-loader": "^5.0.0", 34 | "sass": "^1.49.7", 35 | "sass-loader": "^12.4.0", 36 | "vue": "^2.6.12", 37 | "vue-loader": "^15.9.8", 38 | "vue-template-compiler": "^2.6.12", 39 | "vue-tour": "^2.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/webui/src/app.js: -------------------------------------------------------------------------------- 1 | var Handlebars = require("handlebars/runtime"); 2 | 3 | Handlebars.registerHelper("inc", function(value, options) { 4 | return parseInt(value) + 1; 5 | }); 6 | 7 | Handlebars.registerHelper("mysip", function(value, options) { 8 | if (value.match("sip:(.+@studio.link)")) 9 | return value.match("sip:(.+@studio.link)")[1]; 10 | return value; 11 | }); 12 | 13 | Handlebars.registerHelper("inc", function(value, options) { 14 | return parseInt(value) + 1; 15 | }); 16 | 17 | Handlebars.registerHelper("myselect", function(channel, index, options) { 18 | if (channel == index) return "selected"; 19 | }); 20 | 21 | Handlebars.registerHelper("capitalize", function(str) { 22 | if (typeof str !== "string") return ""; 23 | return str.charAt(0).toUpperCase() + str.slice(1); 24 | }); 25 | 26 | Handlebars.registerHelper("ifeq", function(a, b, options) { 27 | if (a == b) { 28 | return options.fn(this); 29 | } 30 | return options.inverse(this); 31 | }); 32 | 33 | Handlebars.registerHelper("times", function(n, block) { 34 | var accum = ""; 35 | for (var i = 0; i < n; ++i) { 36 | block.data.index = i; 37 | block.data.first = i === 0; 38 | block.data.last = i === n - 1; 39 | accum += block.fn(this); 40 | } 41 | return accum; 42 | }); 43 | 44 | $.fn.serializeObject = function() { 45 | var obj = {}; 46 | var arr = this.serializeArray(); 47 | arr.forEach(function(item, index) { 48 | if (obj[item.name] === undefined) { 49 | // New 50 | obj[item.name] = item.value || ""; 51 | } else { 52 | // Existing 53 | if (!obj[item.name].push) { 54 | obj[item.name] = [obj[item.name]]; 55 | } 56 | obj[item.name].push(item.value || ""); 57 | } 58 | }); 59 | return obj; 60 | }; 61 | 62 | window.addEventListener("beforeunload", function(e) { 63 | if (window.callactive) { 64 | e.preventDefault(); 65 | e.returnValue = ""; 66 | } 67 | }); 68 | 69 | $(function() { 70 | var changelog = require("./templates/changelog.handlebars"); 71 | window.callactive = false; 72 | 73 | $.get("/swvariant", function(data) { 74 | var swvariant = data; 75 | window.swvariant = swvariant; 76 | 77 | if (swvariant == "standalone") { 78 | $("#btn-interface").removeClass("d-none"); 79 | $("#btn-record").removeClass("d-none"); 80 | $("#btn-record-folder").removeClass("d-none"); 81 | $("#btn-onair").removeClass("d-none"); 82 | $("#btn-mute").removeClass("d-none"); 83 | ws_rtaudio_init(); 84 | 85 | if (localStorage.getItem("slonboarding") != "completed") { 86 | window.Vue.prototype.$tours["slTour"].start(); 87 | } 88 | } 89 | if (swvariant == "plugin") { 90 | $("#btn-interface").addClass("d-none"); 91 | } 92 | 93 | ws_baresip_init(); 94 | ws_calls_init(); 95 | ws_meter_init(); 96 | ws_contacts_init(); 97 | ws_options_init(); 98 | }); 99 | 100 | $.get("/version", function(data) { 101 | version = data; 102 | version_int = parseInt(version.replace(/[^0-9]+/g, "")); 103 | 104 | $("#version").html('' + version + ""); 105 | $("#changelog").on("click", function() { 106 | bootbox.alert({ 107 | message: changelog(), 108 | size: "large", 109 | }); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /src/webui/src/app.scss: -------------------------------------------------------------------------------- 1 | $gray-900: #1e1e1e; 2 | $link-color: #c8c8c8; 3 | $secondary: #333333; 4 | $progress-height: 0.6rem; 5 | $container-max-widths: ( 6 | sm: 550px, 7 | md: 970px, 8 | lg: 980px, 9 | xl: 1140px 10 | ); 11 | 12 | @import 'theme/variables'; 13 | @import '~bootstrap/scss/bootstrap'; 14 | @import 'theme/bootswatch'; 15 | 16 | label { 17 | margin-bottom: 0; 18 | } 19 | 20 | .card-body { 21 | padding: 1rem; 22 | } 23 | 24 | .card-header { 25 | padding: 0.5rem 1rem; 26 | } 27 | 28 | .card-header-sm { 29 | padding: 0.25rem 1rem; 30 | } 31 | 32 | .progress { 33 | margin-top: 5px; 34 | } 35 | 36 | .main-volume { 37 | height: 20px; 38 | } 39 | 40 | .container { 41 | padding-right: 10px; 42 | padding-left: 10px; 43 | } 44 | 45 | .form-control { 46 | background-color: #303030; 47 | color: #fff; 48 | } 49 | 50 | .form-control:focus { 51 | border-color: #f67e05; 52 | background-color: #303030; 53 | color: #fff; 54 | } 55 | 56 | .form-control:disabled, .form-control[readonly] { 57 | background-color: #444444; 58 | border-color: #444444; 59 | color: #fff; 60 | } 61 | 62 | a:hover { 63 | text-decoration: none; 64 | } 65 | 66 | .modal-content { 67 | background-color: #171717; 68 | border: 1px solid #000; 69 | } 70 | 71 | #variant { 72 | font-family: 'Exo 2', sans-serif; 73 | font-size: 35px; 74 | vertical-align: middle; 75 | color: #c7c7c9; 76 | } 77 | 78 | @font-face { 79 | font-family: 'Exo 2'; 80 | font-style: normal; 81 | font-weight: 500; 82 | src: 83 | local('Exo 2 Medium'), 84 | local('Exo2-Medium'), 85 | /* from https://fonts.gstatic.com/s/exo2/v4/7cHrv4okm5zmbt7bCPs7wHs.eot */ 86 | url('/fonts/Exo_2_500.eot') format('embedded-opentype'), 87 | /* from https://fonts.gstatic.com/s/exo2/v4/7cHrv4okm5zmbt7bCPs7wHk.woff */ 88 | url('/fonts/Exo_2_500.woff') format('woff'), 89 | /* from https://fonts.gstatic.com/s/exo2/v4/7cHrv4okm5zmbt7bCPs7wH8.woff2 */ 90 | url('/fonts/Exo_2_500.woff2') format('woff2'), 91 | /* from https://fonts.gstatic.com/l/font?kit=7cHrv4okm5zmbt7bCPs7wHg&skey=adb63786cb87541c&v=v4#Exo2 */ 92 | url('/fonts/Exo_2_500.svg') format('svg'), 93 | /* from https://fonts.gstatic.com/s/exo2/v4/7cHrv4okm5zmbt7bCPs7wHo.ttf */ 94 | url('/fonts/Exo_2_500.ttf') format('truetype'); 95 | } 96 | 97 | #changelogen h1 { 98 | font-size: 2rem; 99 | } 100 | 101 | #changelogen h2 { 102 | font-size: 1.5rem; 103 | } 104 | 105 | #changelogen h3 { 106 | font-size: 1rem; 107 | } 108 | -------------------------------------------------------------------------------- /src/webui/src/components/Onboarding.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/webui/src/icons.js: -------------------------------------------------------------------------------- 1 | import { library, dom } from '@fortawesome/fontawesome-svg-core' 2 | import { faCog } from '@fortawesome/free-solid-svg-icons/faCog' 3 | import { faRandom } from '@fortawesome/free-solid-svg-icons/faRandom' 4 | import { faLongArrowAltRight } from '@fortawesome/free-solid-svg-icons/faLongArrowAltRight' 5 | import { faRss } from '@fortawesome/free-solid-svg-icons/faRss' 6 | import { faDotCircle } from '@fortawesome/free-regular-svg-icons/faDotCircle' 7 | import { faPhone } from '@fortawesome/free-solid-svg-icons/faPhone' 8 | import { faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons/faMicrophoneSlash' 9 | import { faPhoneSlash } from '@fortawesome/free-solid-svg-icons/faPhoneSlash' 10 | import { faUserPlus } from '@fortawesome/free-solid-svg-icons/faUserPlus' 11 | import { faAddressBook } from '@fortawesome/free-solid-svg-icons/faAddressBook' 12 | import { faPhoneSquare } from '@fortawesome/free-solid-svg-icons/faPhoneSquare' 13 | import { faPlusCircle } from '@fortawesome/free-solid-svg-icons/faPlusCircle' 14 | import { faEdit } from '@fortawesome/free-solid-svg-icons/faEdit' 15 | import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt' 16 | import { faTh } from '@fortawesome/free-solid-svg-icons/faTh' 17 | import { faFolderOpen } from '@fortawesome/free-regular-svg-icons/faFolderOpen' 18 | 19 | library.add(faCog) 20 | library.add(faRandom) 21 | library.add(faLongArrowAltRight) 22 | library.add(faRss) 23 | library.add(faDotCircle) 24 | library.add(faPhone) 25 | library.add(faMicrophoneSlash) 26 | library.add(faPhoneSlash) 27 | library.add(faUserPlus) 28 | library.add(faAddressBook) 29 | library.add(faPhoneSquare) 30 | library.add(faPlusCircle) 31 | library.add(faEdit) 32 | library.add(faTrashAlt) 33 | library.add(faTh) 34 | library.add(faFolderOpen) 35 | 36 | // Replace any existing tags with and set up a MutationObserver to 37 | // continue doing this as the DOM changes. 38 | dom.watch() 39 | -------------------------------------------------------------------------------- /src/webui/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Studio Link 12 | 13 | 14 |
15 |
16 |
17 |

18 | 21 |

22 |
23 | Default | Help 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
Audio
32 | 35 | 38 | 41 | 44 |
45 |
46 |
47 | 48 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | More... 68 |
69 |
70 | 87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 |
98 |
99 | Status 100 |
101 |
102 |
    103 |
  • Your ID:
  • 104 |
  • 105 |
106 |
107 |
108 | 109 | 110 |
111 |
112 | 115 |
116 | 117 |
118 | 119 |
120 | 121 |
122 |
123 | 124 |
125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/webui/src/init.js: -------------------------------------------------------------------------------- 1 | require('bootstrap'); 2 | require('./icons'); 3 | 4 | import Vue from 'vue'; 5 | import VueTour from 'vue-tour' 6 | 7 | require('vue-tour/dist/vue-tour.css') 8 | 9 | var $ = require('jquery'); 10 | window.jQuery = $; 11 | window.$ = $; 12 | window.Vue = Vue; 13 | 14 | import Parsley from 'parsleyjs' 15 | 16 | var bootbox = require('bootbox'); 17 | window.bootbox = bootbox; 18 | 19 | window.ws_host = location.host; 20 | // window.ws_host = "127.0.0.1:34081"; 21 | 22 | Vue.use(VueTour); 23 | 24 | Vue.component("onboarding", require("./components/Onboarding.vue").default); 25 | 26 | const app = new Vue({ 27 | el: "#app" 28 | }); 29 | -------------------------------------------------------------------------------- /src/webui/src/templates/activecalls.handlebars: -------------------------------------------------------------------------------- 1 | {{#each this}} 2 |
3 |
Remote Track {{track}} 4 | 8 | 9 | 40 | 41 |
42 | {{state}} 43 | 47 |
48 |
49 |
50 | ID: {{mysip peer}} 51 | (Buffer: ms) 52 | Talk: 53 |
54 |
55 |
56 | {{/each}} 57 | -------------------------------------------------------------------------------- /src/webui/src/templates/addcontact.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/webui/src/templates/addsip.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/webui/src/templates/audiointerface.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 | 8 | 13 |
14 |
15 | 16 | 21 |
22 |
23 | 24 | 34 |
35 |
36 | 37 | 42 |
43 |
{{this.error_msg}}
44 |
45 |
46 |
-------------------------------------------------------------------------------- /src/webui/src/templates/calls.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | Type 4 | SIP 5 | 6 | 7 | 8 | {{#each this}} 9 | 10 | {{type}} 11 | {{sip}} 12 | 13 | {{/each}} 14 | 15 | -------------------------------------------------------------------------------- /src/webui/src/templates/changelog.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 12 | 13 | 14 |
15 | 21 |
22 |

Credits/Licences

23 |
24 | MIT LICENSE AGREEMENT FOR STUDIOLINK SOFTWARE:
25 | Copyright (c) 2013-2021 Sebastian Reimers 
26 | 
27 | Permission is hereby granted, free of charge, to any person obtaining
28 | a copy of this software and associated documentation files (the "Software"),
29 | to deal in the Software without restriction, including without limitation the
30 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
31 | sell copies of the Software, and to permit persons to whom the Software is
32 | furnished to do so, subject to the following conditions:
33 | 
34 | The above copyright notice and this permission notice shall be included in
35 | all copies or substantial portions of the Software.
36 | 
37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
38 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
39 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
40 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
41 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
42 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
43 | 			
44 |

Third-Party

45 | 54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /src/webui/src/templates/chatlist.handlebars: -------------------------------------------------------------------------------- 1 | 2 | {{#each this}} 3 | {{name}} 4 | {{/each}} 5 | -------------------------------------------------------------------------------- /src/webui/src/templates/chatmessages.handlebars: -------------------------------------------------------------------------------- 1 |
    2 | {{#each this}} 3 | 4 |
  • 5 |
    6 |
    7 | {{#if self}} 8 | Me 9 | {{else}} 10 | {{peer}} 11 | {{/if}} 12 | 13 | {{@key}} 14 | 15 |
    16 |

    17 | {{message}} 18 |

    19 |
    20 |
  • 21 | 22 | {{/each}} 23 |
24 | 25 | 48 | -------------------------------------------------------------------------------- /src/webui/src/templates/currentlist.handlebars: -------------------------------------------------------------------------------- 1 | {{#each this}} 2 | {{user}}@{{domain}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /src/webui/src/templates/editsip.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/webui/src/templates/index.handlebars: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Studio-Link/app/9a6f85baeb1942d5ea5e2a015e5fd2e00a5cadf0/src/webui/src/templates/index.handlebars -------------------------------------------------------------------------------- /src/webui/src/templates/keyboard.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /src/webui/src/templates/listcontacts.handlebars: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 45 | 46 | 47 | {{#each this}} 48 | 49 | 50 | 51 | 61 | 62 | {{/each}} 63 | 64 |
NameSIPActions
echoecho@studio.link 17 |

18 | 21 |

22 |
musicmusic@studio.link 28 |

29 | 32 |

33 |
music1music1@studio.link 39 |

40 | 43 |

44 |
{{name}}{{sip}} 52 |

53 | 56 | 59 |

60 |
65 | -------------------------------------------------------------------------------- /src/webui/src/templates/listsip.handlebars: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{#each this}} 14 | 15 | 16 | {{#if status}} 17 | 18 | {{else}} 19 | 20 | {{/if}} 21 | 29 | 30 | {{/each}} 31 | 32 |
AccountStatusActions
{{user}}@{{domain}}OnlineOffline 22 |

23 | 24 | 27 |

28 |
33 | -------------------------------------------------------------------------------- /src/webui/src/templates/options.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{#each this}} 8 | 9 | {{#ifeq @key "monitoring"}}{{/ifeq}} 10 | {{#ifeq @key "monorecord"}}{{/ifeq}} 11 | {{#ifeq @key "monostream"}}{{/ifeq}} 12 | 25 | 26 | {{/each}} 27 | 28 |
NameState
MonitoringMono Local RecordingMono Remote Streaming 13 |

14 | {{#if this}} 15 | 18 | {{else}} 19 | 22 | {{/if}} 23 |

24 |
29 | -------------------------------------------------------------------------------- /src/webui/src/templates/status.handlebars: -------------------------------------------------------------------------------- 1 | CPU: 0% 2 | {{#each this}} 3 | {{name}}: {{value}} 4 | {{/each}} 5 | -------------------------------------------------------------------------------- /src/webui/src/theme/_bootswatch.scss: -------------------------------------------------------------------------------- 1 | // Darkly 4.6.0 2 | // Bootswatch 3 | 4 | 5 | // Variables =================================================================== 6 | 7 | $web-font-path: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400&display=swap" !default; 8 | @import url($web-font-path); 9 | 10 | // Typography ================================================================== 11 | 12 | .blockquote { 13 | &-footer { 14 | color: $gray-600; 15 | } 16 | } 17 | 18 | // Tables ====================================================================== 19 | 20 | .table { 21 | &-primary { 22 | &, 23 | > th, 24 | > td { 25 | background-color: $primary; 26 | } 27 | } 28 | 29 | &-secondary { 30 | &, 31 | > th, 32 | > td { 33 | background-color: $secondary; 34 | } 35 | } 36 | 37 | &-light { 38 | &, 39 | > th, 40 | > td { 41 | background-color: $light; 42 | } 43 | } 44 | 45 | &-dark { 46 | &, 47 | > th, 48 | > td { 49 | background-color: $dark; 50 | } 51 | } 52 | 53 | &-success { 54 | &, 55 | > th, 56 | > td { 57 | background-color: $success; 58 | } 59 | } 60 | 61 | &-info { 62 | &, 63 | > th, 64 | > td { 65 | background-color: $info; 66 | } 67 | } 68 | 69 | &-danger { 70 | &, 71 | > th, 72 | > td { 73 | background-color: $danger; 74 | } 75 | } 76 | 77 | &-warning { 78 | &, 79 | > th, 80 | > td { 81 | background-color: $warning; 82 | } 83 | } 84 | 85 | &-active { 86 | &, 87 | > th, 88 | > td { 89 | background-color: $table-active-bg; 90 | } 91 | } 92 | 93 | &-hover { 94 | .table-primary:hover { 95 | &, 96 | > th, 97 | > td { 98 | background-color: darken($primary, 5%); 99 | } 100 | } 101 | 102 | .table-secondary:hover { 103 | &, 104 | > th, 105 | > td { 106 | background-color: darken($secondary, 5%); 107 | } 108 | } 109 | 110 | .table-light:hover { 111 | &, 112 | > th, 113 | > td { 114 | background-color: darken($light, 5%); 115 | } 116 | } 117 | 118 | .table-dark:hover { 119 | &, 120 | > th, 121 | > td { 122 | background-color: darken($dark, 5%); 123 | } 124 | } 125 | 126 | .table-success:hover { 127 | &, 128 | > th, 129 | > td { 130 | background-color: darken($success, 5%); 131 | } 132 | } 133 | 134 | .table-info:hover { 135 | &, 136 | > th, 137 | > td { 138 | background-color: darken($info, 5%); 139 | } 140 | } 141 | 142 | .table-danger:hover { 143 | &, 144 | > th, 145 | > td { 146 | background-color: darken($danger, 5%); 147 | } 148 | } 149 | 150 | .table-warning:hover { 151 | &, 152 | > th, 153 | > td { 154 | background-color: darken($warning, 5%); 155 | } 156 | } 157 | 158 | .table-active:hover { 159 | &, 160 | > th, 161 | > td { 162 | background-color: $table-active-bg; 163 | } 164 | } 165 | 166 | } 167 | } 168 | 169 | // Forms ======================================================================= 170 | 171 | .input-group-addon { 172 | color: $white; 173 | } 174 | 175 | // Navs ======================================================================== 176 | 177 | .nav-tabs, 178 | .nav-pills { 179 | .nav-link, 180 | .nav-link.active, 181 | .nav-link.active:focus, 182 | .nav-link.active:hover, 183 | .nav-item.open .nav-link, 184 | .nav-item.open .nav-link:focus, 185 | .nav-item.open .nav-link:hover { 186 | color: $white; 187 | } 188 | } 189 | 190 | .breadcrumb a { 191 | color: $white; 192 | } 193 | 194 | .pagination { 195 | a:hover { 196 | text-decoration: none; 197 | } 198 | } 199 | 200 | // Indicators ================================================================== 201 | 202 | .close { 203 | opacity: .4; 204 | 205 | &:hover, 206 | &:focus { 207 | opacity: 1; 208 | } 209 | } 210 | 211 | .alert { 212 | border: none; 213 | color: $white; 214 | 215 | a, 216 | .alert-link { 217 | color: $white; 218 | text-decoration: underline; 219 | } 220 | 221 | @each $color, $value in $theme-colors { 222 | &-#{$color} { 223 | @if $enable-gradients { 224 | background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x; 225 | } @else { 226 | background-color: $value; 227 | } 228 | } 229 | } 230 | } 231 | 232 | // Containers ================================================================== 233 | 234 | .list-group-item-action { 235 | color: $white; 236 | 237 | &:hover, 238 | &:focus { 239 | background-color: $gray-700; 240 | color: $white; 241 | } 242 | 243 | .list-group-item-heading { 244 | color: $white; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/webui/src/theme/_variables.scss: -------------------------------------------------------------------------------- 1 | // Darkly 4.6.0 2 | // Bootswatch 3 | 4 | // 5 | // Color system 6 | // 7 | 8 | $white: #fff !default; 9 | $gray-100: #f8f9fa !default; 10 | $gray-200: #ebebeb !default; 11 | $gray-300: #dee2e6 !default; 12 | $gray-400: #ced4da !default; 13 | $gray-500: #adb5bd !default; 14 | $gray-600: #888 !default; 15 | $gray-700: #444 !default; 16 | $gray-800: #303030 !default; 17 | $gray-900: #222 !default; 18 | $black: #000 !default; 19 | 20 | $blue: #375a7f !default; 21 | $indigo: #6610f2 !default; 22 | $purple: #6f42c1 !default; 23 | $pink: #e83e8c !default; 24 | $red: #e74c3c !default; 25 | $orange: #fd7e14 !default; 26 | $yellow: #f39c12 !default; 27 | $green: #00bc8c !default; 28 | $teal: #20c997 !default; 29 | $cyan: #3498db !default; 30 | 31 | $primary: $blue !default; 32 | $secondary: $gray-700 !default; 33 | $success: $green !default; 34 | $info: $cyan !default; 35 | $warning: $yellow !default; 36 | $danger: $red !default; 37 | $light: $gray-500 !default; 38 | $dark: $gray-800 !default; 39 | 40 | $yiq-contrasted-threshold: 175 !default; 41 | 42 | // Body 43 | 44 | $body-bg: $gray-900 !default; 45 | $body-color: $white !default; 46 | 47 | // Links 48 | 49 | $link-color: $success !default; 50 | 51 | // Fonts 52 | 53 | // stylelint-disable-next-line value-keyword-case 54 | $font-family-sans-serif: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; 55 | $font-size-base: .9375rem !default; 56 | $font-size-sm: $font-size-base * .88 !default; 57 | $h1-font-size: 3rem !default; 58 | $h2-font-size: 2.5rem !default; 59 | $h3-font-size: 2rem !default; 60 | $text-muted: $gray-600 !default; 61 | 62 | // Tables 63 | 64 | $table-accent-bg: $gray-800 !default; 65 | $table-border-color: $gray-700 !default; 66 | 67 | // Forms 68 | 69 | $input-border-color: $body-bg !default; 70 | $input-group-addon-color: $gray-500 !default; 71 | $input-group-addon-bg: $gray-700 !default; 72 | $custom-file-color: $gray-500 !default; 73 | $custom-file-border-color: $body-bg !default; 74 | 75 | // Dropdowns 76 | 77 | $dropdown-bg: $gray-900 !default; 78 | $dropdown-border-color: $gray-700 !default; 79 | $dropdown-divider-bg: $gray-700 !default; 80 | $dropdown-link-color: $white !default; 81 | $dropdown-link-hover-color: $white !default; 82 | $dropdown-link-hover-bg: $primary !default; 83 | 84 | // Navs 85 | 86 | $nav-link-padding-x: 2rem !default; 87 | $nav-link-disabled-color: $gray-500 !default; 88 | $nav-tabs-border-color: $gray-700 !default; 89 | $nav-tabs-link-hover-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent !default; 90 | $nav-tabs-link-active-color: $white !default; 91 | $nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent !default; 92 | 93 | // Navbar 94 | 95 | $navbar-padding-y: 1rem !default; 96 | $navbar-dark-color: rgba($white, .6) !default; 97 | $navbar-dark-hover-color: $white !default; 98 | $navbar-light-color: rgba($gray-900, .7) !default; 99 | $navbar-light-hover-color: $gray-900 !default; 100 | $navbar-light-active-color: $gray-900 !default; 101 | $navbar-light-toggler-border-color: rgba($gray-900, .1) !default; 102 | 103 | // Pagination 104 | 105 | $pagination-color: $white !default; 106 | $pagination-bg: $success !default; 107 | $pagination-border-width: 0 !default; 108 | $pagination-border-color: transparent !default; 109 | $pagination-hover-color: $white !default; 110 | $pagination-hover-bg: lighten($success, 10%) !default; 111 | $pagination-hover-border-color: transparent !default; 112 | $pagination-active-bg: $pagination-hover-bg !default; 113 | $pagination-active-border-color: transparent !default; 114 | $pagination-disabled-color: $white !default; 115 | $pagination-disabled-bg: darken($success, 15%) !default; 116 | $pagination-disabled-border-color: transparent !default; 117 | 118 | // Jumbotron 119 | 120 | $jumbotron-bg: $gray-800 !default; 121 | 122 | // Cards 123 | 124 | $card-cap-bg: $gray-700 !default; 125 | $card-bg: $gray-800 !default; 126 | 127 | // Popovers 128 | 129 | $popover-bg: $gray-800 !default; 130 | $popover-header-bg: $gray-700 !default; 131 | 132 | // Toasts 133 | 134 | $toast-background-color: $gray-700 !default; 135 | $toast-header-background-color: $gray-800 !default; 136 | 137 | // Modals 138 | 139 | $modal-content-bg: $gray-800 !default; 140 | $modal-content-border-color: $gray-700 !default; 141 | $modal-header-border-color: $gray-700 !default; 142 | 143 | // Progress bars 144 | 145 | $progress-bg: $gray-700 !default; 146 | 147 | // List group 148 | 149 | $list-group-bg: $gray-800 !default; 150 | $list-group-border-color: $gray-700 !default; 151 | $list-group-hover-bg: $gray-700 !default; 152 | 153 | // Breadcrumbs 154 | 155 | $breadcrumb-bg: $gray-700 !default; 156 | 157 | // Close 158 | 159 | $close-color: $white !default; 160 | $close-text-shadow: none !default; 161 | 162 | // Code 163 | 164 | $pre-color: inherit !default; 165 | -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_baresip.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.ws_baresip_init = function() { 3 | 4 | var ws_baresip_sip_accounts = {}; 5 | 6 | function ws_baresip_delete_sip(user, domain) { 7 | ws_baresip.send('{"command": "deletesip", "user": "'+user+'", "domain": "'+domain+'"}'); 8 | } 9 | 10 | function RefreshEventListener() { 11 | var addsip = require("../templates/addsip.handlebars"); 12 | var editsip = require("../templates/editsip.handlebars"); 13 | 14 | $( "#buttonaddsip" ).on( "click", function() { 15 | bootbox.dialog({ 16 | title: "Add SIP Account", 17 | message: addsip(), 18 | buttons: { 19 | close: { 20 | label: 'Cancel', 21 | callback: function() { 22 | return true; 23 | } 24 | }, 25 | success: { 26 | label: "Save", 27 | className: "btn-success", 28 | callback: function () { 29 | if ($("#formaddsip").parsley().validate()) { 30 | ws_baresip.send(JSON.stringify($('#formaddsip').serializeObject())); 31 | } else { 32 | return false; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | ); 39 | $("#formaddsip").parsley({ 40 | errorClass: 'is-invalid text-danger', 41 | successClass: 'is-valid', // Comment this option if you don't want the field to become green when valid. Recommended in Google material design to prevent too many hints for user experience. Only report when a field is wrong. 42 | errorsWrapper: '', 43 | errorTemplate: '', 44 | trigger: 'change' 45 | }); 46 | }); 47 | 48 | 49 | $( ".deletesip" ).on( "click", function() { 50 | ws_baresip_delete_sip($(this).attr('data-user'), $(this).attr('data-domain')); 51 | }); 52 | 53 | $( ".editsip" ).on( "click", function() { 54 | var sip_account_edit = {}; 55 | for(var index in ws_baresip_sip_accounts) { 56 | var user = ws_baresip_sip_accounts[index]["user"]; 57 | var domain = ws_baresip_sip_accounts[index]["domain"]; 58 | if (domain == $(this).attr('data-domain') && user == $(this).attr('data-user')) { 59 | sip_account_edit = ws_baresip_sip_accounts[index]; 60 | } 61 | } 62 | bootbox.dialog({ 63 | title: "Edit SIP Account", 64 | message: editsip(sip_account_edit), 65 | buttons: { 66 | close: { 67 | label: 'Cancel', 68 | callback: function() { 69 | return true; 70 | } 71 | }, 72 | success: { 73 | label: "Save", 74 | className: "btn-success", 75 | callback: function () { 76 | if ($("#formeditsip").parsley().validate()) { 77 | ws_baresip_delete_sip(sip_account_edit['user'], sip_account_edit['domain']); 78 | var sip_account_edit_form = $('#formeditsip').serializeObject(); 79 | if (sip_account_edit_form['password'] == "") { 80 | delete sip_account_edit_form['password']; 81 | } 82 | $.extend(sip_account_edit, sip_account_edit_form); 83 | ws_baresip.send(JSON.stringify(sip_account_edit)); 84 | } else { 85 | return false; 86 | } 87 | } 88 | } 89 | } 90 | } 91 | ); 92 | $("#formeditsip").parsley(); 93 | }); 94 | } 95 | 96 | 97 | var ws_baresip = new WebSocket('ws://'+window.ws_host+'/ws_baresip'); 98 | 99 | ws_baresip.onerror = function () { 100 | $.notify("Websocket error", "error"); 101 | }; 102 | 103 | ws_baresip.onclose = function () { 104 | $.notify("Websocket closed", "error"); 105 | }; 106 | 107 | ws_baresip.onmessage = function (message) { 108 | var msg = JSON.parse(message.data); 109 | 110 | if (msg.callback == "UPDATE") { 111 | bootbox.dialog({ 112 | title: "Update available", 113 | message: "Please update to " + msg.version, 114 | buttons: { 115 | close: { 116 | label: 'Cancel', 117 | callback: function() { 118 | return true; 119 | } 120 | }, 121 | download: { 122 | label: "Download", 123 | className: "btn-success", 124 | callback: function () { 125 | if (window.swvariant == "standalone") { 126 | window.open("https://doku.studio-link.de/standalone/installation-standalone.html"); 127 | return false; 128 | } 129 | window.open("https://doku.studio-link.de/plugin/installation-plugin-neu.html"); 130 | return false; 131 | } 132 | } 133 | } 134 | } 135 | ); 136 | return; 137 | } 138 | 139 | var listsip = require("../templates/listsip.handlebars"); 140 | var currentlist = require("../templates/currentlist.handlebars"); 141 | 142 | ws_baresip_sip_accounts = msg; 143 | $( "#accounts" ).html(listsip(ws_baresip_sip_accounts)); 144 | 145 | $( "#current_uag" ).html(currentlist(ws_baresip_sip_accounts)); 146 | 147 | if ($( "#current_uag option" ).length > 1) { 148 | $( "#current_uag" ).removeClass("d-none"); 149 | } 150 | for (var key in ws_baresip_sip_accounts) { 151 | var domain = ws_baresip_sip_accounts[key]['domain']; 152 | var user = ws_baresip_sip_accounts[key]['user']; 153 | var state = ws_baresip_sip_accounts[key]['status']; 154 | if (state) { 155 | var state_html = 'OK'; 156 | } else { 157 | var state_html = 'ERROR'; 158 | } 159 | 160 | if (domain == "studio.link") { 161 | var siphtml = user+"@"+domain; 162 | var sipstatus = "Status: "+state_html; 163 | $("#sipid").html(siphtml); 164 | $("#sipstatus").html(sipstatus); 165 | } 166 | }; 167 | RefreshEventListener(); 168 | }; 169 | 170 | 171 | $('#sipnumbercall').keypress(function(ev) { 172 | if (ev.which === 13) { 173 | $('#buttoncall').click(); 174 | $(this).blur(); 175 | } 176 | }); 177 | 178 | $( "#buttoncall" ).on( "click", function() { 179 | ws_baresip.send('{"command": "call", "dial": "'+ 180 | $( "#sipnumbercall" ).val() +'"}'); 181 | $('#sipnumbercall').val(""); 182 | }); 183 | 184 | $( "#current_uag" ).on( "change", function() { 185 | ws_baresip.send('{"command": "uagcurrent", "aor": "'+ 186 | $( "#current_uag" ).val()+'"}'); 187 | }); 188 | 189 | 190 | RefreshEventListener(); 191 | }; -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_contacts.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | window.ws_contacts_init = function() { 4 | window.ws_contacts_list = {}; 5 | var ws_contacts = new WebSocket("ws://" + window.ws_host + "/ws_contacts"); 6 | window.ws_contacts = ws_contacts; 7 | var addcontact = require("../templates/addcontact.handlebars"); 8 | var listcontacts = require("../templates/listcontacts.handlebars"); 9 | 10 | function ws_contacts_delete(sip) { 11 | ws_contacts.send('{"command": "deletecontact", "sip": "' + sip + '"}'); 12 | } 13 | 14 | function RefreshEventListener() { 15 | $("#buttonaddcontact").on("click", function() { 16 | bootbox.dialog({ 17 | title: "Add Contact", 18 | message: addcontact(), 19 | buttons: { 20 | close: { 21 | label: "Cancel", 22 | callback: function() { 23 | return true; 24 | }, 25 | }, 26 | success: { 27 | label: "Save", 28 | className: "btn-primary", 29 | callback: function() { 30 | if ( 31 | $("#formaddcontact") 32 | .parsley() 33 | .validate() 34 | ) { 35 | ws_contacts.send( 36 | JSON.stringify($("#formaddcontact").serializeObject()) 37 | ); 38 | } else { 39 | return false; 40 | } 41 | }, 42 | }, 43 | }, 44 | }); 45 | 46 | $("#formaddcontact").parsley({ 47 | errorClass: "is-invalid text-danger", 48 | successClass: "is-valid", // Comment this option if you don't want the field to become green when valid. Recommended in Google material design to prevent too many hints for user experience. Only report when a field is wrong. 49 | errorsWrapper: '', 50 | errorTemplate: "", 51 | trigger: "change", 52 | }); 53 | 54 | $("#formaddcontact-name").keypress(function(ev) { 55 | if (ev.which === 13) $(".btn-formaddcontact").click(); 56 | }); 57 | 58 | $("#formaddcontact-sip").keypress(function(ev) { 59 | if (ev.which === 13) $(".btn-formaddcontact").click(); 60 | }); 61 | }); 62 | 63 | $(".deletecontact").on("click", function() { 64 | ws_contacts_delete($(this).attr("data-sip")); 65 | }); 66 | 67 | $(".callcontact").on("click", function() { 68 | $("#sipnumbercall").val($(this).attr("data-sip")); 69 | $("#buttoncall").click(); 70 | }); 71 | } 72 | 73 | ws_contacts.onerror = function() { 74 | $.notify("Websocket error", "error"); 75 | }; 76 | 77 | ws_contacts.onclose = function() { 78 | $.notify("Websocket closed", "error"); 79 | }; 80 | 81 | ws_contacts.onmessage = function(message) { 82 | var msg = JSON.parse(message.data); 83 | ws_contacts_list = msg; 84 | $("#contacts").html(listcontacts(ws_contacts_list)); 85 | RefreshEventListener(); 86 | }; 87 | 88 | RefreshEventListener(); 89 | }; 90 | -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_meter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.ws_meter_init = function() { 3 | 4 | function iec_scale(db) { 5 | var def = 0.0; 6 | 7 | if (db < -70.0 || isNaN(db)) { 8 | def = 0.0; 9 | } else if (db < -60.0) { 10 | def = (db + 70.0) * 0.25; 11 | } else if (db < -50.0) { 12 | def = (db + 60.0) * 0.5 + 2.5; 13 | } else if (db < -40.0) { 14 | def = (db + 50.0) * 0.75 + 7.5; 15 | } else if (db < -30.0) { 16 | def = (db + 40.0) * 1.5 + 15.0; 17 | } else if (db < -20.0) { 18 | def = (db + 30.0) * 2.0 + 30.0; 19 | } else if (db < 0.0) { 20 | def = (db + 20.0) * 2.5 + 50.0; 21 | } else { 22 | def = 100.0; 23 | } 24 | 25 | return (def / 100.0); 26 | 27 | } 28 | 29 | function rainbow(db) { 30 | if (db > 90) { 31 | return "bg-danger"; 32 | } else if (db > 65) { 33 | return "bg-warning"; 34 | } 35 | return "bg-success"; 36 | } 37 | 38 | var ws_meter = new WebSocket('ws://'+window.ws_host+'/ws_meter'); 39 | 40 | ws_meter.onmessage = function (event) { 41 | 42 | var peaks = event.data.split(" "); 43 | 44 | $("#record_time").html(peaks[0]); 45 | 46 | var mic1 = iec_scale(parseFloat(peaks[2])) * 100; 47 | var mic2 = iec_scale(parseFloat(peaks[4])) * 100; 48 | var mic3 = iec_scale(parseFloat(peaks[6])) * 100; 49 | var mic4 = iec_scale(parseFloat(peaks[8])) * 100; 50 | var mic5 = iec_scale(parseFloat(peaks[10])) * 100; 51 | var mic6 = iec_scale(parseFloat(peaks[12])) * 100; 52 | var mic7 = iec_scale(parseFloat(peaks[14])) * 100; 53 | var mic8 = iec_scale(parseFloat(peaks[16])) * 100; 54 | var mic9 = iec_scale(parseFloat(peaks[18])) * 100; 55 | var mic10 = iec_scale(parseFloat(peaks[20])) * 100; 56 | var mic11 = iec_scale(parseFloat(peaks[22])) * 100; 57 | var mic12 = iec_scale(parseFloat(peaks[24])) * 100; 58 | 59 | var head1 = iec_scale(parseFloat(peaks[3])) * 100; 60 | var head2 = iec_scale(parseFloat(peaks[5])) * 100; 61 | var head3 = iec_scale(parseFloat(peaks[7])) * 100; 62 | var head4 = iec_scale(parseFloat(peaks[9])) * 100; 63 | var head5 = iec_scale(parseFloat(peaks[11])) * 100; 64 | var head6 = iec_scale(parseFloat(peaks[13])) * 100; 65 | var head7 = iec_scale(parseFloat(peaks[15])) * 100; 66 | var head8 = iec_scale(parseFloat(peaks[17])) * 100; 67 | var head9 = iec_scale(parseFloat(peaks[19])) * 100; 68 | var head10 = iec_scale(parseFloat(peaks[21])) * 100; 69 | var head11 = iec_scale(parseFloat(peaks[23])) * 100; 70 | var head12 = iec_scale(parseFloat(peaks[25])) * 100; 71 | 72 | if (peaks[4] != "inf") { 73 | $("#microphonebar2").removeClass("d-none"); 74 | } 75 | if (peaks[6] != "inf") { 76 | $("#microphonebar3").removeClass("d-none"); 77 | } 78 | if (peaks[8] != "inf") { 79 | $("#microphonebar4").removeClass("d-none"); 80 | } 81 | if (peaks[10] != "inf") { 82 | $("#microphonebar5").removeClass("d-none"); 83 | } 84 | if (peaks[12] != "inf") { 85 | $("#microphonebar6").removeClass("d-none"); 86 | } 87 | if (peaks[14] != "inf") { 88 | $("#microphonebar7").removeClass("d-none"); 89 | } 90 | if (peaks[16] != "inf") { 91 | $("#microphonebar8").removeClass("d-none"); 92 | } 93 | if (peaks[18] != "inf") { 94 | $("#microphonebar9").removeClass("d-none"); 95 | } 96 | if (peaks[20] != "inf") { 97 | $("#microphonebar10").removeClass("d-none"); 98 | } 99 | if (peaks[22] != "inf") { 100 | $("#microphonebar11").removeClass("d-none"); 101 | } 102 | if (peaks[24] != "inf") { 103 | $("#microphonebar12").removeClass("d-none"); 104 | } 105 | 106 | $("#microphonebar1").html('
'+mic1+'% Complete (success)
'); 107 | $("#microphonebar2").html('
'+mic2+'% Complete (success)
'); 108 | $("#microphonebar3").html('
'+mic3+'% Complete (success)
'); 109 | $("#microphonebar4").html('
'+mic4+'% Complete (success)
'); 110 | $("#microphonebar5").html('
'+mic5+'% Complete (success)
'); 111 | $("#microphonebar6").html('
'+mic6+'% Complete (success)
'); 112 | $("#microphonebar7").html('
'+mic7+'% Complete (success)
'); 113 | $("#microphonebar8").html('
'+mic8+'% Complete (success)
'); 114 | $("#microphonebar9").html('
'+mic9+'% Complete (success)
'); 115 | $("#microphonebar10").html('
'+mic10+'% Complete (success)
'); 116 | 117 | $("#headphonesbar1").html('
'+head1+'% Complete (success)
'); 118 | $("#headphonesbar3").html('
'+head2+'% Complete (success)
'); 119 | $("#headphonesbar5").html('
'+head3+'% Complete (success)
'); 120 | $("#headphonesbar7").html('
'+head4+'% Complete (success)
'); 121 | $("#headphonesbar9").html('
'+head5+'% Complete (success)
'); 122 | $("#headphonesbar11").html('
'+head6+'% Complete (success)
'); 123 | $("#headphonesbar13").html('
'+head7+'% Complete (success)
'); 124 | $("#headphonesbar15").html('
'+head8+'% Complete (success)
'); 125 | $("#headphonesbar17").html('
'+head9+'% Complete (success)
'); 126 | $("#headphonesbar19").html('
'+head10+'% Complete (success)
'); 127 | }; 128 | }; 129 | -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_options.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | window.ws_options_init = function() { 3 | var ws_options = new WebSocket("ws://" + window.ws_host + "/ws_options"); 4 | 5 | var bypass = false; 6 | var record = false; 7 | var onair = false; 8 | var raisehand = false; 9 | var afk = false; 10 | var mute = false; 11 | 12 | var hoptions = require("../templates/options.handlebars"); 13 | 14 | function RefreshBypass() { 15 | if (bypass) { 16 | $("#btn-bypass").removeClass("btn-secondary"); 17 | $("#btn-bypass").addClass("btn-danger"); 18 | } else { 19 | $("#btn-bypass").removeClass("btn-danger"); 20 | $("#btn-bypass").addClass("btn-secondary"); 21 | } 22 | } 23 | 24 | function RefreshRecord() { 25 | if (record) { 26 | $("#btn-record").removeClass("btn-secondary"); 27 | $("#btn-record").addClass("btn-danger"); 28 | } else { 29 | $("#btn-record").removeClass("btn-danger"); 30 | $("#btn-record").addClass("btn-secondary"); 31 | } 32 | } 33 | 34 | function RefreshOnair() { 35 | if (onair) { 36 | $("#btn-onair").removeClass("btn-secondary"); 37 | $("#btn-onair").addClass("btn-danger"); 38 | } else { 39 | $("#btn-onair").removeClass("btn-danger"); 40 | $("#btn-onair").addClass("btn-secondary"); 41 | } 42 | } 43 | 44 | function RefreshRaisehand() { 45 | if (raisehand) { 46 | $("#btn-raise-hand").removeClass("btn-secondary"); 47 | $("#btn-raise-hand").addClass("btn-danger"); 48 | } else { 49 | $("#btn-raise-hand").removeClass("btn-danger"); 50 | $("#btn-raise-hand").addClass("btn-secondary"); 51 | } 52 | } 53 | 54 | function RefreshAFK() { 55 | if (afk) { 56 | $("#btn-afk").removeClass("btn-secondary"); 57 | $("#btn-afk").addClass("btn-danger"); 58 | } else { 59 | $("#btn-afk").removeClass("btn-danger"); 60 | $("#btn-afk").addClass("btn-secondary"); 61 | } 62 | } 63 | 64 | function RefreshEventListener() { 65 | $(".option-change").on("click", function() { 66 | ws_options.send( 67 | '{"key": "' + 68 | $(this).attr("data-option") + 69 | '", "value": "' + 70 | $(this).attr("data-value") + 71 | '"}' 72 | ); 73 | }); 74 | } 75 | 76 | function RefreshMute() { 77 | if (mute) { 78 | $("#btn-mute").removeClass("btn-primary"); 79 | $("#btn-mute").addClass("btn-danger"); 80 | } else { 81 | $("#btn-mute").removeClass("btn-danger"); 82 | $("#btn-mute").addClass("btn-primary"); 83 | } 84 | } 85 | 86 | function RefreshMonitor() { 87 | if (mute) { 88 | $("#btn-monitor").removeClass("btn-primary"); 89 | $("#btn-monitor").addClass("btn-danger"); 90 | } else { 91 | $("#btn-monitor").removeClass("btn-danger"); 92 | $("#btn-monitor").addClass("btn-primary"); 93 | } 94 | } 95 | 96 | ws_options.onmessage = function(message) { 97 | var msg = JSON.parse(message.data); 98 | 99 | if (msg.bypass) { 100 | if (msg.bypass == "true") { 101 | bypass = true; 102 | } else { 103 | bypass = false; 104 | } 105 | RefreshBypass(); 106 | } 107 | 108 | if (msg.record) { 109 | if (msg.record == "true") { 110 | record = true; 111 | } else { 112 | record = false; 113 | } 114 | RefreshRecord(); 115 | } 116 | 117 | if (msg.onair) { 118 | if (msg.onair == "true") { 119 | onair = true; 120 | } else { 121 | onair = false; 122 | } 123 | RefreshOnair(); 124 | } 125 | 126 | if (msg.raisehand) { 127 | if (msg.raisehand == "true") { 128 | raisehand = true; 129 | } else { 130 | raisehand = false; 131 | } 132 | RefreshRaisehand(); 133 | } 134 | 135 | if (msg.afk) { 136 | if (msg.afk == "true") { 137 | afk = true; 138 | } else { 139 | afk = false; 140 | } 141 | RefreshAFK(); 142 | } 143 | 144 | if (msg.mute) { 145 | if (msg.mute == "true") { 146 | mute = true; 147 | } else { 148 | mute = false; 149 | } 150 | RefreshMute(); 151 | } 152 | 153 | delete msg.bypass; 154 | delete msg.record; 155 | delete msg.onair; 156 | delete msg.raisehand; 157 | delete msg.afk; 158 | delete msg.mute; 159 | 160 | Object.keys(msg) 161 | .sort() 162 | .forEach(function(key) { 163 | var value = msg[key]; 164 | delete msg[key]; 165 | msg[key] = value; 166 | }); 167 | 168 | $("#options").html(hoptions(msg)); 169 | 170 | RefreshEventListener(); 171 | }; 172 | 173 | RefreshEventListener(); 174 | 175 | $("#btn-bypass").on("click", function() { 176 | if (bypass) { 177 | bypass = false; 178 | } else { 179 | bypass = true; 180 | } 181 | 182 | ws_options.send('{"key": "bypass", "value": "' + bypass + '"}'); 183 | }); 184 | 185 | $("#btn-mono").on("click", function() { 186 | ws_options.send('{"key": "mono", "value": "true"}'); 187 | }); 188 | 189 | $("#btn-stereo").on("click", function() { 190 | mono = false; 191 | ws_options.send('{"key": "mono", "value": "false"}'); 192 | }); 193 | 194 | $("#btn-record").on("click", function() { 195 | if (record) { 196 | record = false; 197 | } else { 198 | record = true; 199 | } 200 | 201 | ws_options.send('{"key": "record", "value": "' + record + '"}'); 202 | }); 203 | 204 | $("#btn-record-folder").on("click", function() { 205 | ws_options.send('{"key": "record-folder", "value": "1"}'); 206 | }); 207 | 208 | $("#btn-onair").on("click", function() { 209 | if (onair) { 210 | onair = false; 211 | } else { 212 | onair = true; 213 | } 214 | 215 | ws_options.send('{"key": "onair", "value": "' + onair + '"}'); 216 | }); 217 | 218 | $("#btn-raise-hand").on("click", function() { 219 | if (raisehand) { 220 | raisehand = false; 221 | } else { 222 | raisehand = true; 223 | } 224 | 225 | ws_options.send('{"key": "raisehand", "value": "' + raisehand + '"}'); 226 | }); 227 | 228 | $("#btn-afk").on("click", function() { 229 | if (afk) { 230 | afk = false; 231 | } else { 232 | afk = true; 233 | } 234 | 235 | ws_options.send('{"key": "afk", "value": "' + afk + '"}'); 236 | }); 237 | 238 | $("#btn-mute").on("click", function() { 239 | if (mute) { 240 | mute = false; 241 | } else { 242 | mute = true; 243 | } 244 | 245 | ws_options.send('{"key": "mute", "value": "' + mute + '"}'); 246 | }); 247 | 248 | document.onkeydown = function(e) { 249 | //"shortcut: m" 250 | if (e.which == 77) { 251 | if ($("#sipnumbercall").is(":focus")) return; 252 | if (mute) { 253 | mute = false; 254 | } else { 255 | mute = true; 256 | } 257 | 258 | ws_options.send('{"key": "mute", "value": "' + mute + '"}'); 259 | } 260 | }; 261 | }; 262 | -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_rtaudio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var audio_config = {}; 3 | 4 | window.ws_rtaudio_init = function() { 5 | var ws_rtaudio = new WebSocket("ws://" + window.ws_host + "/ws_rtaudio"); 6 | var audiointerface = require("../templates/audiointerface.handlebars"); 7 | 8 | function RefreshEventListener() { 9 | $("#interface_driver").on("change", function() { 10 | ws_rtaudio.send( 11 | '{"command": "driver", "id": ' + 12 | parseInt($("#interface_driver").val()) + 13 | "}" 14 | ); 15 | }); 16 | $("#interface_input").on("change", function() { 17 | ws_rtaudio.send( 18 | '{"command": "input", "id": ' + 19 | parseInt($("#interface_input").val()) + 20 | "}" 21 | ); 22 | }); 23 | $("#first_input_channel").on("change", function() { 24 | ws_rtaudio.send( 25 | '{"command": "first_input_channel", "id": ' + 26 | parseInt($("#first_input_channel").val()) + 27 | "}" 28 | ); 29 | }); 30 | $("#interface_output").on("change", function() { 31 | ws_rtaudio.send( 32 | '{"command": "output", "id": ' + 33 | parseInt($("#interface_output").val()) + 34 | "}" 35 | ); 36 | }); 37 | $("#interface_monitor").on("change", function() { 38 | ws_rtaudio.send( 39 | '{"command": "monitor", "id": ' + 40 | parseInt($("#interface_monitor").val()) + 41 | "}" 42 | ); 43 | }); 44 | $("#btn-audio-loop").on("click", function() { 45 | if (audio_config.loop) { 46 | ws_rtaudio.send('{"command": "loop", "value": false}'); 47 | } else { 48 | ws_rtaudio.send('{"command": "loop", "value": true}'); 49 | } 50 | }); 51 | 52 | if (audio_config.loop) { 53 | $("#btn-audio-loop").removeClass("btn-default"); 54 | $("#btn-audio-loop").addClass("btn-danger"); 55 | $("#btn-audio-loop").html("Stop Audio Test Loop"); 56 | } else { 57 | $("#btn-audio-loop").removeClass("btn-danger"); 58 | $("#btn-audio-loop").addClass("btn-default"); 59 | $("#btn-audio-loop").html("Start Audio Test Loop"); 60 | } 61 | } 62 | 63 | ws_rtaudio.onmessage = function(message) { 64 | audio_config = JSON.parse(message.data); 65 | $(".bootbox-body").html(audiointerface(audio_config)); 66 | RefreshEventListener(); 67 | }; 68 | 69 | $("#btn-interface").on("click", function() { 70 | bootbox.dialog({ 71 | title: "Change audio device", 72 | message: audiointerface(audio_config), 73 | buttons: { 74 | reload: { 75 | label: "Reload", 76 | callback: function() { 77 | ws_rtaudio.send('{"command": "reload"}'); 78 | return false; 79 | }, 80 | }, 81 | close: { 82 | label: "Close", 83 | callback: function() { 84 | return true; 85 | }, 86 | }, 87 | }, 88 | }); 89 | RefreshEventListener(); 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /src/webui/src/websockets/ws_sipchat.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | $(function () { 3 | var ws_sipchat = new WebSocket('ws://'+location.host+'/ws_chat'); 4 | 5 | ws_sipchat.onmessage = function (message) { 6 | var msg = JSON.parse(message.data); 7 | $( "#chatmessages" ).html(Handlebars.templates.chatmessages(msg)); 8 | }; 9 | 10 | $( "#btn-chat" ).on( "click", function() { 11 | var chatmsg = $( "#chatmessage" ).val(); 12 | var peer = $("#current_contact").val(); 13 | if(chatmsg != "") 14 | ws_sipchat.send('{"command": "message", "text": "'+ chatmsg + '", "peer": "'+ peer +'"}'); 15 | 16 | $( "#chatmessage" ).val(""); 17 | }); 18 | 19 | $('#chatmessage').keypress(function(ev) { 20 | if (ev.which === 13) 21 | $('#btn-chat').click(); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/webui/webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require("laravel-mix"); 2 | require("laravel-mix-copy-watched"); 3 | require("laravel-mix-purgecss"); 4 | 5 | /* 6 | |-------------------------------------------------------------------------- 7 | | Mix Asset Management 8 | |-------------------------------------------------------------------------- 9 | | 10 | | Mix provides a clean, fluent API for defining some Webpack build steps 11 | | for your Laravel application. By default, we are compiling the Sass 12 | | file for your application, as well as bundling up your JS files. 13 | | 14 | */ 15 | 16 | mix 17 | .js( 18 | [ 19 | "src/init.js", 20 | "src/app.js", 21 | "src/notify.js", 22 | "src/websockets/ws_baresip.js", 23 | "src/websockets/ws_calls.js", 24 | "src/websockets/ws_meter.js", 25 | "src/websockets/ws_contacts.js", 26 | "src/websockets/ws_options.js", 27 | "src/websockets/ws_rtaudio.js", 28 | ], 29 | "dist/app.js" 30 | ) 31 | .vue({ version: 2 }); 32 | 33 | mix 34 | .sass("src/app.scss", "dist/") 35 | .purgeCss({ 36 | enabled: mix.inProduction(), 37 | content: [ 38 | "src/index.html", 39 | "src/**/*.js", 40 | "src/**/*.handlebars", 41 | "src/**/*.vue", 42 | "node_modules/bootbox/dist/*.js", 43 | "node_modules/vue-tour/dist/*.js", 44 | "node_modules/vue-tour/dist/*.css" 45 | ], 46 | }) 47 | .copyWatched("src/index.html", "dist/") 48 | .copyDirectory("src/images", "dist/images"); 49 | 50 | mix.webpackConfig({ 51 | module: { 52 | rules: [ 53 | { 54 | test: /\.handlebars?$/, 55 | loader: "handlebars-loader", 56 | }, 57 | ], 58 | }, 59 | }); 60 | --------------------------------------------------------------------------------