├── .clang-format ├── .clang-tidy ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build_and_test.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── License.md ├── ReadMe.md ├── appveyor.yml ├── cmake └── FindFFmpeg.cmake ├── copy_externals.sh ├── docs ├── mc.rtpreceive~.maxref.xml └── mc.rtpsend~.maxref.xml ├── help ├── mc.rtpsend~.maxhelp ├── rtphelpstarter.js └── rtpsendreceive.hello-world.maxhelp ├── icon.png ├── package-info.json.in ├── rtpsendreceive.code-workspace ├── screenshot.jpg ├── setup_dev_env.sh ├── source ├── projects │ ├── mc.rtpreceive_tilde │ │ ├── CMakeLists.txt │ │ ├── mc.rtpreceive_tilde.cpp │ │ ├── mc.rtpreceive_tilde_test.cmake │ │ └── mc.rtpreceive_tilde_test.cpp │ └── mc.rtpsend_tilde │ │ ├── CMakeLists.txt │ │ ├── mc.rtpsend_tilde.cpp │ │ ├── mc.rtpsend_tilde_test.cmake │ │ └── mc.rtpsend_tilde_test.cpp └── rtpsendreceive_lib │ ├── CMakeLists.txt │ ├── lockfree_ringbuffer.hpp │ ├── lockfree_ringbuffer_test.hpp │ ├── min_debugutils.hpp │ ├── rtpreceiver.cpp │ ├── rtpreceiver.hpp │ ├── rtpsender.cpp │ ├── rtpsender.hpp │ ├── rtpsendreceive_lib.hpp │ ├── rtpsendreceive_lib_test.cmake │ ├── rtpsr_classes_test.cpp │ ├── rtpsrbase.cpp │ └── rtpsrbase.hpp ├── test_patch ├── receiver_test.maxpat ├── self_loop_test.maxpat └── sender_test.maxpat └── vcpkg-helper ├── x64-osx-rel.cmake ├── x64-windows-rel.cmake ├── x64-windows-static-md.cmake └── x64-windows-static-rel.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: WebKit 2 | SortIncludes: false 3 | ColumnLimit: 140 4 | UseTab: ForContinuationAndIndentation 5 | TabWidth: 4 6 | 7 | BreakBeforeBraces: Attach 8 | 9 | Cpp11BracedListStyle: true 10 | AllowShortFunctionsOnASingleLine: Empty 11 | AlwaysBreakTemplateDeclarations: true 12 | SpaceAfterTemplateKeyword: false 13 | ConstructorInitializerIndentWidth: 0 14 | 15 | NamespaceIndentation: All 16 | CompactNamespaces: true 17 | FixNamespaceComments: true 18 | 19 | SpacesBeforeTrailingComments: 4 20 | AllowAllArgumentsOnNextLine: false 21 | BinPackArguments: false 22 | BreakBeforeBraces: Custom 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: true 34 | BeforeElse: true 35 | IndentBraces: false 36 | SplitEmptyFunction: false 37 | SplitEmptyRecord: false 38 | SplitEmptyNamespace: false 39 | 40 | AlignConsecutiveAssignments: true 41 | AlignConsecutiveDeclarations: true 42 | AlignEscapedNewlines: Right 43 | AlignTrailingComments: true 44 | 45 | IndentCaseLabels: true 46 | MaxEmptyLinesToKeep: 2 47 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: "clang-diagnostic-*,\ 2 | clang-analyzer-*,\ 3 | cppcoreguidelines-*,\ 4 | google-*,\ 5 | modernize-*,\ 6 | misc-*,\ 7 | readability-*,\ 8 | performance-*,\ 9 | portability-*,\ 10 | -cppcoreguidelines-pro-type-member-init,\ 11 | -cppcoreguidelines-special-member-functions,\ 12 | -cppcoreguidelines-pro-type-reinterpret-cast,\ 13 | -performance-unnecessary-value-param,\ 14 | -modernize-use-trailing-return-type,\ 15 | -misc-non-private-member-variables-in-classes,\ 16 | -google-runtime-references\ 17 | " 18 | HeaderFilterRegex: 'src.*\.(hpp|h)' 19 | CheckOptions: 20 | - key: readability-identifier-naming.ClassCase 21 | value: CamelCase 22 | - key: readability-identifier-naming.EnumCase 23 | value: CamelCase 24 | - key: readability-identifier-naming.FunctionCase 25 | value: camelBack 26 | - key: readability-identifier-naming.MemberCase 27 | value: lower_case 28 | - key: readability-identifier-naming.ParameterCase 29 | value: lower_case 30 | - key: readability-identifier-naming.UnionCase 31 | value: CamelCase 32 | - key: readability-identifier-naming.VariableCase 33 | value: lower_case -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: build & test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - dev 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | timeout-minutes: 120 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - macos-latest 18 | - windows-latest 19 | config: 20 | - Debug 21 | - Release 22 | include: 23 | - os: windows-latest 24 | vcpkg-target-triplet: x64-windows-rel 25 | cache-path: \vcpkg\installed 26 | - os: macos-latest 27 | vcpkg-target-triplet: x64-osx-rel 28 | cache-path: /vcpkg/installed 29 | 30 | steps: 31 | - uses: actions/checkout@v3 32 | with: 33 | submodules: true 34 | - uses: ilammy/msvc-dev-cmd@v1 35 | - uses: actions/cache@v3 36 | id: ffmpeg-cache 37 | with: 38 | path: ${{ github.workspace }}${{ matrix.cache-path }} 39 | key: ${{ runner.os }}-vcpkg-${{ hashFiles('**/vcpkg/ports/ffmpeg/vcpkg.json')}} 40 | - if: contains(matrix.os, 'windows') 41 | name: Install ffmpeg via vcpkg 42 | run: | 43 | cp .\vcpkg-helper\${{ matrix.vcpkg-target-triplet }}.cmake .\vcpkg\triplets\ 44 | .\vcpkg\bootstrap-vcpkg.bat 45 | .\vcpkg\vcpkg.exe install ffmpeg[avformat,avdevice,avcodec,core]:${{ matrix.vcpkg-target-triplet }} 46 | - if: contains(matrix.os, 'macos') 47 | name: Install ffmpeg via vcpkg(macos) 48 | run: | 49 | cp ./vcpkg-helper/${{ matrix.vcpkg-target-triplet }}.cmake ./vcpkg/triplets/ 50 | brew upgrade 51 | brew install yasm nasm 52 | ./vcpkg/bootstrap-vcpkg.sh 53 | ./vcpkg/vcpkg install ffmpeg[avformat,avdevice,avcodec,core]:${{ matrix.vcpkg-target-triplet }} 54 | - name: configure project 55 | run: cmake -B build -DCMAKE_BUILD_CONFIG=${{ matrix.config }} . -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg-target-triplet }} 56 | - name: build 57 | run: cmake --build build --config ${{ matrix.config }} -v 58 | - uses: papeloto/action-zip@v1 59 | with: 60 | files: docs externals help CHANGELOG.md package-info.json ReadMe.md screenshot.jpg License.md 61 | dest: rtpsendreceive-${{matrix.os}}-${{matrix.config}}.zip 62 | - uses: actions/upload-artifact@v3 63 | continue-on-error: true 64 | with: 65 | name: rtpsendreceive-${{matrix.os}}-${{matrix.config}} 66 | path: ${{github.workspace}}/rtpsendreceive-${{matrix.os}}-${{matrix.config}}.zip 67 | # - name: test 68 | # run: cd build && ctest --build-config ${{ matrix.config }} 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | 7 | jobs: 8 | create_release: 9 | name: "Create Release" 10 | runs-on : ubuntu-20.04 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | outputs: 14 | upload_url: ${{ steps.create_release.outputs.upload_url }} 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Create Source Release 18 | id: create_release 19 | uses: actions/create-release@v1.0.0 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: Release ${{ github.ref }} 23 | draft: false 24 | prerelease: false 25 | 26 | build_and_upload: 27 | name: "Build and Release" 28 | needs: create_release 29 | runs-on: ${{ matrix.os }} 30 | timeout-minutes: 120 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | os: 35 | - macos-latest 36 | - windows-latest 37 | config: 38 | - Release 39 | include: 40 | - os: windows-latest 41 | vcpkg-target-triplet: x64-windows-rel 42 | cache-path: \vcpkg\installed 43 | zip_postfix: Windows 44 | - os: macos-latest 45 | vcpkg-target-triplet: x64-osx-rel 46 | cache-path: /vcpkg/installed 47 | zip_postfix: macOS 48 | 49 | steps: 50 | - uses: actions/checkout@v3 51 | with: 52 | submodules: true 53 | - uses: ilammy/msvc-dev-cmd@v1 54 | - uses: actions/cache@v3 55 | id: ffmpeg-cache 56 | with: 57 | path: ${{ github.workspace }}${{ matrix.cache-path }} 58 | key: ${{ runner.os }}-vcpkg-${{ hashFiles('**/vcpkg/ports/ffmpeg/vcpkg.json') }} 59 | - if: contains(matrix.os, 'windows') 60 | name: Install ffmpeg via vcpkg 61 | run: | 62 | cp .\vcpkg-helper\${{ matrix.vcpkg-target-triplet }}.cmake .\vcpkg\triplets\ 63 | .\vcpkg\bootstrap-vcpkg.bat 64 | .\vcpkg\vcpkg.exe install ffmpeg[avformat,avdevice,avcodec,core] 65 | name: Install ffmpeg via vcpkg(macos) 66 | run: | 67 | cp ./vcpkg-helper/${{ matrix.vcpkg-target-triplet }}.cmake ./vcpkg/triplets/ 68 | brew upgrade 69 | brew install yasm nasm 70 | ./vcpkg/bootstrap-vcpkg.sh 71 | ./vcpkg/vcpkg install ffmpeg[avformat,avdevice,avcodec,core]:${{ matrix.vcpkg-target-triplet }} 72 | - name: configure project 73 | run: cmake -B build -DCMAKE_BUILD_CONFIG=${{ matrix.config }} . -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg-target-triplet }} 74 | - name: build 75 | run: cmake --build build --config ${{ matrix.config }} -v 76 | 77 | - name: install 78 | run: | 79 | cmake -E make_directory mc.rtpsendreceive 80 | cmake -E copy_directory docs mc.rtpsendreceive/docs 81 | cmake -E copy_directory externals mc.rtpsendreceive/externals 82 | cmake -E copy_directory help mc.rtpsendreceive/help 83 | cmake -E copy CHANGELOG.md package-info.json ReadMe.md screenshot.jpg License.md mc.rtpsendreceive 84 | 85 | - name: Get the version 86 | id: get_version 87 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 88 | shell: bash 89 | 90 | - uses: papeloto/action-zip@v1 91 | with: 92 | files: mc.rtpsendreceive 93 | dest: rtpsendreceive-${{ steps.get_version.outputs.VERSION }}-binary-${{ matrix.zip_postfix }}.zip 94 | 95 | - name: Upload Compiled Binary 96 | id: upload-release-asset 97 | uses: actions/upload-release-asset@v1.0.1 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | upload_url: ${{ needs.create_release.outputs.upload_url }} 102 | asset_path: ./rtpsendreceive-${{ steps.get_version.outputs.VERSION }}-binary-${{ matrix.zip_postfix }}.zip 103 | asset_name: rtpsendreceive-${{ steps.get_version.outputs.VERSION }}-binary-${{ matrix.zip_postfix }}.zip 104 | asset_content_type: application/zip 105 | 106 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __* 2 | sysbuild 3 | *.sdf 4 | *.suo 5 | *.sln 6 | *.opensdf 7 | log.txt 8 | externals 9 | extensions 10 | support 11 | build 12 | tests 13 | *.o 14 | *.dylib 15 | tmp 16 | .DS_Store 17 | .vscode/ 18 | 19 | test_patch/*.mxo 20 | .dsp_cache 21 | 22 | 23 | package-info.json 24 | 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "source/min-api"] 2 | path = source/min-api 3 | url = https://github.com/tomoyanonymous/min-api.git 4 | [submodule "source/min-lib"] 5 | path = source/min-lib 6 | url = https://github.com/Cycling74/min-lib.git 7 | [submodule "vcpkg"] 8 | path = vcpkg 9 | url = https://github.com/microsoft/vcpkg 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - objective-c 3 | - c++ 4 | 5 | compiler: 6 | - clang 7 | 8 | os: osx 9 | 10 | matrix: 11 | include: 12 | # - osx_image: xcode10.1 13 | # env: CONFIG=Debug 14 | # - osx_image: xcode10.1 15 | # env: CONFIG=Release 16 | - osx_image: xcode11.2 17 | env: CONFIG=Debug 18 | - osx_image: xcode11.2 19 | env: CONFIG=Release 20 | - osx_image: xcode11.4 21 | env: CONFIG=Debug 22 | - osx_image: xcode11.4 23 | env: CONFIG=Release 24 | 25 | before_script: 26 | - brew install opus 27 | - git clone https://git.ffmpeg.org/ffmpeg.git /tmp/ffmpeg 28 | - pushd /tmp/ffmpeg 29 | - ./configure --prefix=/usr/local/Cellar/ffmpeg/HEAD_Debug --disable-avfoundation --disable-iconv --disable-filters --disable-devices --disable-shared --enable-static --disable-optimizations --disable-mmx --disable-audiotoolbox --disable-videotoolbox --disable-stripping --disable-appkit --disable-zlib --disable-coreimage --disable-bzlib --disable-securetransport --disable-sdl2 --disable-lzma --enable-libopus --pkg-config-flags=--static --cc=/usr/bin/clang --cxx=/usr/bin/clang++ 30 | - make -j 31 | - sudo make install 32 | - popd 33 | 34 | 35 | script: 36 | - mkdir build 37 | - cd build 38 | - cmake -G Xcode .. 39 | - cmake --build . --config ${CONFIG} | sed 's/-Wl,-.*//g' 40 | # - ctest -C ${CONFIG} . -V 41 | - cd .. 42 | - PACKAGE_NAME=`echo $TRAVIS_REPO_SLUG | sed 's/.*\///g'` 43 | - PACKAGE_REV=`echo $TRAVIS_COMMIT | sed -e 's/^[[:alnum:]]\{7\}/&-/g' | sed 's/-.*//'` 44 | - mkdir $PACKAGE_NAME 45 | - if [ -e package-info.json ]; then cp package-info.json $PACKAGE_NAME; fi 46 | - for f in *.md; do [ -e "$f" ] && cp "$f" $PACKAGE_NAME ; done 47 | - if [ -e icon.png ]; then cp icon.png $PACKAGE_NAME; fi 48 | - if [ -e CMakeLists.txt ]; then cp CMakeLists.txt $PACKAGE_NAME; fi 49 | - if [ -d code ]; then cp -r code $PACKAGE_NAME; fi 50 | - if [ -d docs ]; then cp -r docs $PACKAGE_NAME; fi 51 | - if [ -d examples ]; then cp -r examples $PACKAGE_NAME; fi 52 | - if [ -d extensions ]; then cp -r extensions $PACKAGE_NAME; fi 53 | - if [ -d externals ]; then cp -r externals $PACKAGE_NAME; fi 54 | - if [ -d extras ]; then cp -r extras $PACKAGE_NAME; fi 55 | - if [ -d help ]; then cp -r help $PACKAGE_NAME; fi 56 | - if [ -d init ]; then cp -r init $PACKAGE_NAME; fi 57 | - if [ -d java-classes ]; then cp -r java-classes $PACKAGE_NAME; fi 58 | - if [ -d java-doc ]; then cp -r java-doc $PACKAGE_NAME; fi 59 | - if [ -d javascript ]; then cp -r javascript $PACKAGE_NAME; fi 60 | - if [ -d jsui ]; then cp -r jsui $PACKAGE_NAME; fi 61 | - if [ -d media ]; then cp -r media $PACKAGE_NAME; fi 62 | - if [ -d misc ]; then cp -r misc $PACKAGE_NAME; fi 63 | - if [ -d patchers ]; then cp -r patchers $PACKAGE_NAME; fi 64 | - if [ -d support ]; then cp -r support $PACKAGE_NAME; fi 65 | - if [ -d source ]; then cp -r source $PACKAGE_NAME; fi 66 | - if [ -d tests ]; then cp -r tests $PACKAGE_NAME; fi 67 | - if [ -e $PACKAGE_NAME/ReadMe-Public.md ]; then rm -f $PACKAGE_NAME/ReadMe.md; mv $PACKAGE_NAME/ReadMe-Public.md $PACKAGE_NAME/ReadMe.md; fi 68 | - mkdir dist 69 | - CONFIG_LOWERCASE=`echo $CONFIG | tr '[A-Z]' '[a-z]'` 70 | - zip -r dist/$PACKAGE_NAME-mac-$PACKAGE_REV-$CONFIG_LOWERCASE.zip $PACKAGE_NAME 71 | 72 | 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | 4 | ## 2020-10-28 v0.3.0 5 | 6 | ### Windows version is now available. 7 | 8 | ffmpeg is now installed from vcpkg, registered as submodule. 9 | 10 | ### New Attributes 11 | 12 | ringbuf_framenum: set the buffer size of internal ringbuffer to collect samples asynchrounously between audio thread and network. Note that the number will be multiplied to Signal Vector Size in Audio Setting. Default is x4. 13 | 14 | ### Packet Latency 15 | 16 | "getlatency" message for `mc.rtpreceiver~` outputs packet latency from second outlet. Note that this latency just taken from RTP packets' timestamp and does not include a latency caused from buffering. Generally, large signal vector size may causes a large latency, too small buffer size causes audio glitch. 128~512 is recommended. 17 | 18 | 19 | 20 | ## 2020-10-28 v0.2.3 21 | 22 | C++ class refactorings & cleeanups. 23 | 24 | 2 new Attributes are added. 25 | 26 | use_rtsp(bool, default=true): you can choose either of using rtsp handshaking or raw rtp protocol. 27 | 28 | Note that, when using raw rtp protocol, receiver assumes audio parameter from internal parameters because it does no handshaking. Thus different paramters between sender and receiver in this mode may cause audio glitch or some unexpected behaviour. 29 | 30 | Also, it has a bug that turning on&off of dsp quickly will cause temporary hang for 4~5 second. 31 | 32 | ## 2020-09-03 v0.2.2 33 | 34 | Added GitHub workflow and funding pages. 35 | 36 | ### Fixed Bugs 37 | Fixed hangs when receiver is reinstanciated before finishes connection. 38 | 39 | 40 | ## 2020-09-03 v0.2.1 41 | 42 | ### Fixed Bugs 43 | 44 | Fixed hanging when sender/receiver could not connect to server. 45 | Now sender also transmits packets asynchronously from audio/main thread. 46 | 47 | ## 2020-09-03 v0.2.0 48 | 49 | This release contains many many refactorings. 50 | Especially, receiving packets and pushing samples to object's output works asynchronously on `mc.rtpreceiver~`. 51 | This resolved the problem that receiver blocks audio thread when no packet is arrived. 52 | 53 | Also, an initial connection protocol has been changed from raw rtp to RTSP. 54 | 55 | ### working issue 56 | 57 | - An attribute for choosing audio codec(PCM 16bit and Opus) has been added on this release but Opus is not working properly. 58 | - Receiver's audio codec parameters have not been taken from an infomation given by RTSP initialization. 59 | 60 | ## 2020-08-02 v0.1.3 61 | 62 | - fixed a problem that the app crashes when a frame does not contain format information. 63 | 64 | ## 2020-07-30 v0.1.2 65 | 66 | - working with multiple channels(confirmed up to 8ch) 67 | - fixed some crashes 68 | 69 | ## 2020-05-25 v0.1.0 70 | 71 | SUPER EARLY RELEASE BUILD for macOS. 72 | 73 | Zip file includes all externals, XML for object infos and source codes as well. 74 | To use the packages, drop unzipped folder into `~/Documents/Max 8/Packages` -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19) 2 | 3 | project(rtpsendreceive 4 | LANGUAGES CXX) 5 | 6 | string(REGEX REPLACE "(.*)/" "" THIS_PACKAGE_NAME "${CMAKE_CURRENT_SOURCE_DIR}") 7 | 8 | 9 | set (CMAKE_CXX_STANDARD 17) 10 | set (CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 13 | 14 | elseif(CMAKE_EXPORT_COMPILE_COMMANDS) 15 | set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) 16 | endif() 17 | 18 | if (APPLE) 19 | if (${CMAKE_GENERATOR} MATCHES "Xcode") 20 | if (${XCODE_VERSION} VERSION_LESS 10) 21 | message(STATUS "Xcode 10 or higher is required. Please install from the Mac App Store.") 22 | return () 23 | elseif(${XCODE_VERSION} VERSION_GREATER_EQUAL 12) 24 | set(C74_BUILD_FAT YES) 25 | endif () 26 | endif () 27 | 28 | if (NOT CMAKE_OSX_ARCHITECTURES) 29 | if(C74_BUILD_FAT) 30 | set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "macOS architecture" FORCE) 31 | else() 32 | set(CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} CACHE STRING "macOS architecture" FORCE) 33 | endif() 34 | message("CMAKE_OSX_ARCHITECTURES set to ${CMAKE_OSX_ARCHITECTURES}") 35 | endif() 36 | endif() 37 | 38 | # Fetch the correct version of the min-api 39 | message(STATUS "Updating Git Submodules") 40 | execute_process( 41 | COMMAND git submodule update --init --recursive 42 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 43 | ) 44 | 45 | 46 | # Misc setup and subroutines 47 | include(${CMAKE_CURRENT_SOURCE_DIR}/source/min-api/script/min-package.cmake) 48 | 49 | 50 | # Add unit tests for the API 51 | enable_testing() 52 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/source/min-api) 53 | 54 | 55 | # Add the Lib, if it exists 56 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/source/min-lib/CMakeLists.txt") 57 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/source/min-lib) 58 | endif () 59 | 60 | 61 | ### PROJECT_SPECIFIC LIBRARY(FFMPEG) 62 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 63 | 64 | if(NOT DEFINED VCPKG_TARGET_TRIPLET) 65 | if(APPLE) 66 | set(VCPKG_TARGET_TRIPLET x64-osx) 67 | elseif(WIN32) 68 | set(VCPKG_TARGET_TRIPLET x64-windows-static) 69 | endif() 70 | endif() 71 | 72 | set(VCPKG_PATH 73 | ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/installed/${VCPKG_TARGET_TRIPLET}) 74 | message("vcpkg path: ${VCPKG_PATH}") 75 | find_package(FFmpeg 76 | REQUIRED 77 | ) #add here the list of ffmpeg components required 78 | # memo: building barematal ffmpeg for macos 79 | 80 | if(FFMPEG_FOUND) 81 | # FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. 82 | # FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. 83 | # FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. 84 | message("FFMPEG_INCLUDE_DIRS = ${FFMPEG_INCLUDE_DIRS} ") 85 | message("FFMPEG_LIBRARIES = ${FFMPEG_LIBRARIES} ") 86 | message("FFMPEG_DEFINITIONS = ${FFMPEG_DEFINITIONS} ") 87 | endif() 88 | # find_library(OPUS_LIB 89 | # NAMES libopus.a opus 90 | # PATHS /usr/lib /usr/local/lib /usr/local/opt/opus/lib C:/ProgramData/chocolatey/lib/opus-tools/tools/lib 91 | # NO_DEFAULT_PATH 92 | # # REQUIRED 93 | # ) 94 | # message("OPUS_LIB =${OPUS_LIB}") 95 | 96 | set(DEPENDENT_LIBS 97 | ${FFMPEG_LIBRARIES} 98 | # ${OPUS_LIB} 99 | ) 100 | 101 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/source/rtpsendreceive_lib) 102 | 103 | # Generate a project for every folder in the "source/projects" folder 104 | SUBDIRLIST(PROJECT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/source/projects) 105 | foreach (project_dir ${PROJECT_DIRS}) 106 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/source/projects/${project_dir}/CMakeLists.txt") 107 | message("Generating: ${project_dir}") 108 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/source/projects/${project_dir}) 109 | endif () 110 | endforeach () 111 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # mc.rtpsend~ / mc.rtpreceive~ 2 | 3 | master:[![build & test](https://github.com/tomoyanonymous/rtpsendreceive/workflows/build%20&%20test/badge.svg?branch=master)](https://github.com/tomoyanonymous/rtpsendreceive/actions?query=workflow%3A%22build+%26+test%22) dev: [![build & test](https://github.com/tomoyanonymous/rtpsendreceive/workflows/build%20&%20test/badge.svg?branch=dev)](https://github.com/tomoyanonymous/rtpsendreceive/actions?query=workflow%3A%22build+%26+test%22) 4 | 5 | *2022/01/06: This project is not actively maintained. [jit.ndi](https://github.com/pixsper/jit.ndi) also supports the transmission of uncompressed audio over local network and it should be more stable now. If you want to continue to maintain or develop this project, I will support. Just send PR or contact me.* 6 | 7 | External objects for Cycling'74 Max to send MSP signal over network using rtp protocol. An modern alternative to legacy `netsend~` & `netreceive~` objects. 8 | 9 | ![](./screenshot.jpg) 10 | 11 | - Send mc audio signal through 16bit-integer PCM signals. 12 | - Sending signals over NAT (= Global Network) is not supported. Consider use VPN. 13 | 14 | ## notes & todos 15 | 16 | - **When use this external, in Max's "Audio Setting", "IO Vector Size" and "Signal Vector Size" must be the same. If signal vector size is too smaller than IO vector size, it fails to send audio correctly.** 17 | - Currently number of channels are fixed by an attribute "channels", an auto-adaptation depending on input channels is not available due to a limitation of min-api. 18 | - A codec is fixed to Linear PCM 16bit(Big Endian). For future, Opus will be added. 19 | 20 | ## Installation 21 | 22 | Get latest version from [release](https://github.com/tomoyanonymous/rtpsendreceive/releases) page. 23 | 24 | Unzip downloaded folder and drop the root folder into Max package directory (for macOS, `~/Documents/Max 8/Packages`) 25 | if you don't need source anymore, you can delete files other than in `externals`,`docs`,`help` directories. 26 | 27 | ## Build from Source 28 | 29 | You need cmake and C,C++ compiler. This project uses ffmpeg library for rtp streaming. 30 | This project installs ffmpeg using [vcpkg](https://github.com/microsoft/vcpkg), a cross-platform paackage manager registered as submodule at `./vcpkg`. 31 | 32 | For a more detailed workflow, read GitHub Actions workflow in `./.github/workflows/build_and_test.yml`. 33 | 34 | ### macOS 35 | 36 | Currently tested on macOS 10.15.3, XCode 11.4.1. 37 | 38 | Open a terminal and type following commands. 39 | 40 | ```bash 41 | git clone https://github.com/tomoyanonymous/rtpsendreceive.git --recursive 42 | cd rtpsendreceive 43 | 44 | ./vcpkg/bootstrap-vcpkg.sh 45 | ./vcpkg/vcpkg install ffmpeg\[avformat,avdevice,avcodec,core\] 46 | 47 | cmake . -B build 48 | cmake --build build --target all 49 | ``` 50 | ### Windows 51 | 52 | Currently tested on Visual Studio Community 2019 53 | 54 | Open "Developer Command Prompt for VS 2019" and type following commands. 55 | 56 | ```cmd 57 | 58 | git clone https://github.com/tomoyanonymous/rtpsendreceive.git --recursive 59 | cd rtpsendreceive 60 | 61 | .\vcpkg\bootstrap-vcpkg.bat 62 | .\vcpkg\vcpkg install ffmpeg[avformat,avdevice,avcodec,core]:x64-windows 63 | 64 | cmake . -B build 65 | cmake --build build --target all 66 | ``` 67 | 68 | ## Copyrights 69 | 70 | Tomoya Matsuura 松浦知也 71 | 72 | https://matsuuratomoya.com/en 73 | 74 | ## License 75 | 76 | [LGPL v3.0](./License.md) 77 | 78 | ## Acknowledgements 79 | 80 | The objects are originally made for works cooparated with [stu.inc](http://stu.inc/). 81 | 82 | 83 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | shallow_clone: false 2 | 3 | image: 4 | - Visual Studio 2017 5 | - Visual Studio 2019 6 | 7 | platform: 8 | - x64 9 | 10 | configuration: 11 | - Debug 12 | - Release 13 | 14 | install: 15 | - git submodule update --init --recursive 16 | 17 | build_script: 18 | - mkdir build 19 | - cd build 20 | - SET VS_ARCH="" 21 | - if "%platform%" == "x64" SET VS_ARCH=-A%PLATFORM% 22 | - echo VS_ARCH = %VS_ARCH% 23 | - cmake %VS_ARCH% .. > %APPVEYOR_BUILD_FOLDER%\configure.log 24 | - cmake --build . --config %CONFIGURATION% > %APPVEYOR_BUILD_FOLDER%\build.log 25 | - cd .. 26 | - mkdir %APPVEYOR_PROJECT_NAME% 27 | - if exist docs cp -r docs %APPVEYOR_PROJECT_NAME% 28 | - if exist examples cp -r examples %APPVEYOR_PROJECT_NAME% 29 | - if exist extensions cp -r extensions %APPVEYOR_PROJECT_NAME% 30 | - if exist externals cp -r externals %APPVEYOR_PROJECT_NAME% 31 | - if exist extras cp -r extras %APPVEYOR_PROJECT_NAME% 32 | - if exist help cp -r help %APPVEYOR_PROJECT_NAME% 33 | - if exist init cp -r init %APPVEYOR_PROJECT_NAME% 34 | - if exist java-classes cp -r java-classes %APPVEYOR_PROJECT_NAME% 35 | - if exist java-doc cp -r java-doc %APPVEYOR_PROJECT_NAME% 36 | - if exist jsui cp -r jsui %APPVEYOR_PROJECT_NAME% 37 | - if exist patchers cp -r patchers %APPVEYOR_PROJECT_NAME% 38 | - if exist tests cp -r tests %APPVEYOR_PROJECT_NAME% 39 | - cp icon.png %APPVEYOR_PROJECT_NAME% 40 | - cp License.md %APPVEYOR_PROJECT_NAME% 41 | - cp package-info.json %APPVEYOR_PROJECT_NAME% 42 | - cp ReadMe.md %APPVEYOR_PROJECT_NAME% 43 | - set SHORT_COMMIT=%APPVEYOR_REPO_COMMIT:~0,7% 44 | - 7z a %APPVEYOR_PROJECT_NAME%-win-%platform%-%SHORT_COMMIT%-%CONFIGURATION%.zip %APPVEYOR_PROJECT_NAME% > %APPVEYOR_BUILD_FOLDER%\archive.log 45 | 46 | test_script: 47 | - cd build 48 | - ctest -C %CONFIGURATION% . -V > %APPVEYOR_BUILD_FOLDER%\test.log 49 | 50 | artifacts: 51 | - name: Build 52 | path: '*.zip' 53 | - name: Log files 54 | path: '*.log' 55 | 56 | on_failure: 57 | - appveyor PushArtifact %APPVEYOR_BUILD_FOLDER%\configure.log 58 | - appveyor PushArtifact %APPVEYOR_BUILD_FOLDER%\build.log 59 | - appveyor PushArtifact %APPVEYOR_BUILD_FOLDER%\test.log 60 | -------------------------------------------------------------------------------- /cmake/FindFFmpeg.cmake: -------------------------------------------------------------------------------- 1 | # retrieved from https://github.com/snikulov/cmake-modules/blob/master/FindFFmpeg.cmake 2 | 3 | # vim: ts=2 sw=2 4 | # - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC) 5 | # 6 | # Once done this will define 7 | # FFMPEG_FOUND - System has the all required components. 8 | # FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers. 9 | # FFMPEG_LIBRARIES - Link these to use the required ffmpeg components. 10 | # FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components. 11 | # 12 | # For each of the components it will additionally set. 13 | # - AVCODEC 14 | # - AVDEVICE 15 | # - AVFORMAT 16 | # - AVFILTER 17 | # - AVUTIL 18 | # - POSTPROC 19 | # - SWSCALE 20 | # the following variables will be defined 21 | # _FOUND - System has 22 | # _INCLUDE_DIRS - Include directory necessary for using the headers 23 | # _LIBRARIES - Link these to use 24 | # _DEFINITIONS - Compiler switches required for using 25 | # _VERSION - The components version 26 | # 27 | # Copyright (c) 2006, Matthias Kretz, 28 | # Copyright (c) 2008, Alexander Neundorf, 29 | # Copyright (c) 2011, Michael Jansen, 30 | # 31 | # Redistribution and use is allowed according to the terms of the BSD license. 32 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 33 | 34 | include(FindPackageHandleStandardArgs) 35 | 36 | # The default components were taken from a survey over other FindFFMPEG.cmake files 37 | if (NOT FFmpeg_FIND_COMPONENTS) 38 | set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL) 39 | endif () 40 | 41 | # 42 | ### Macro: set_component_found 43 | # 44 | # Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present. 45 | # 46 | macro(set_component_found _component ) 47 | if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS) 48 | message(STATUS " - ${_component} found.") 49 | set(${_component}_FOUND TRUE) 50 | else () 51 | message(STATUS " - ${_component} not found.") 52 | endif () 53 | endmacro() 54 | 55 | # 56 | ### Macro: find_component 57 | # 58 | # Checks for the given component by invoking pkgconfig and then looking up the libraries and 59 | # include directories. 60 | # 61 | 62 | macro(find_component _component _pkgconfig _library _header) 63 | 64 | # if (NOT WIN32) 65 | # # use pkg-config to get the directories and then use these values 66 | # # in the FIND_PATH() and FIND_LIBRARY() calls 67 | # find_package(PkgConfig) 68 | # if (PKG_CONFIG_FOUND) 69 | # pkg_check_modules(PC_${_component} ${_pkgconfig}) 70 | # endif () 71 | # else() 72 | # endif () 73 | 74 | find_path(${_component}_INCLUDE_DIRS ${_header} 75 | PATHS 76 | ${VCPKG_PATH}/include 77 | ${PC_LIB${_component}_INCLUDEDIR} 78 | ${PC_LIB${_component}_INCLUDE_DIRS} 79 | PATH_SUFFIXES 80 | ffmpeg 81 | ) 82 | 83 | find_library(${_component}_LIBRARIES NAMES ${_library} 84 | PATHS 85 | ${VCPKG_PATH}/lib 86 | ${PC_LIB${_component}_LIBDIR} 87 | ${PC_LIB${_component}_LIBRARY_DIRS} 88 | ) 89 | 90 | set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.") 91 | set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.") 92 | 93 | set_component_found(${_component}) 94 | 95 | mark_as_advanced( 96 | ${_component}_INCLUDE_DIRS 97 | ${_component}_LIBRARIES 98 | ${_component}_DEFINITIONS 99 | ${_component}_VERSION) 100 | 101 | endmacro() 102 | if(NOT DEFINED FFMPEG_PLATFORM_DEPENDENT_LIBS) 103 | if(APPLE) 104 | find_library(VT_UNIT VideoToolbox) 105 | if (NOT VT_UNIT) 106 | message(FATAL_ERROR "VideoToolbox not found") 107 | endif() 108 | find_library(AT_UNIT AudioToolbox) 109 | if (NOT AT_UNIT) 110 | message(FATAL_ERROR "AudioToolbox not found") 111 | endif() 112 | find_library(SEC_UNIT Security) 113 | if (NOT SEC_UNIT) 114 | message(FATAL_ERROR "Security not found") 115 | endif() 116 | find_library(CF_UNIT CoreFoundation) 117 | if (NOT CF_UNIT) 118 | message(FATAL_ERROR "CoreFoundation not found") 119 | endif() 120 | find_library(CM_UNIT CoreMedia) 121 | if (NOT CM_UNIT) 122 | message(FATAL_ERROR "CoreMedia not found") 123 | endif() 124 | find_library(CV_UNIT CoreVideo) 125 | if (NOT CV_UNIT) 126 | message(FATAL_ERROR "CoreVideo not found") 127 | endif() 128 | list(APPEND FFMPEG_PLATFORM_DEPENDENT_LIBS ${VT_UNIT} ${AT_UNIT} ${SEC_UNIT} ${CF_UNIT} ${CM_UNIT} ${CV_UNIT}) 129 | endif() 130 | if(WIN32) 131 | if(NOT CYGWIN) 132 | list(APPEND FFMPEG_PLATFORM_DEPENDENT_LIBS wsock32 ws2_32 secur32 bcrypt strmiids Vfw32 Shlwapi mfplat mfuuid) 133 | endif() 134 | else() 135 | list(APPEND FFMPEG_PLATFORM_DEPENDENT_LIBS m) 136 | endif() 137 | message("platform dependent libs :${FFMPEG_PLATFORM_DEPENDENT_LIBS}") 138 | endif() 139 | 140 | # Check for cached results. If there are skip the costly part. 141 | if (NOT FFMPEG_LIBRARIES) 142 | 143 | 144 | # Check for all possible component. 145 | find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h) 146 | find_component(AVFORMAT libavformat avformat libavformat/avformat.h) 147 | find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h) 148 | find_component(AVUTIL libavutil avutil libavutil/avutil.h) 149 | find_component(AVFILTER libavfilter avfilter libavfilter/avfilter.h) 150 | find_component(SWSCALE libswscale swscale libswscale/swscale.h) 151 | find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h) 152 | find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h) 153 | 154 | # Check if the required components were found and add their stuff to the FFMPEG_* vars. 155 | foreach (_component ${FFmpeg_FIND_COMPONENTS}) 156 | if (${_component}_FOUND) 157 | # message(STATUS "Required component ${_component} present.") 158 | set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES}) 159 | set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS}) 160 | list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) 161 | else () 162 | # message(STATUS "Required component ${_component} missing.") 163 | endif () 164 | endforeach () 165 | 166 | # Build the include path with duplicates removed. 167 | if (FFMPEG_INCLUDE_DIRS) 168 | list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) 169 | endif () 170 | # cache the vars. 171 | set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE) 172 | set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${FFMPEG_PLATFORM_DEPENDENT_LIBS} CACHE STRING "The FFmpeg libraries." FORCE) 173 | set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE) 174 | 175 | mark_as_advanced(FFMPEG_INCLUDE_DIRS 176 | FFMPEG_LIBRARIES 177 | FFMPEG_DEFINITIONS) 178 | 179 | endif () 180 | 181 | # Now set the noncached _FOUND vars for the components. 182 | foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE) 183 | set_component_found(${_component}) 184 | endforeach () 185 | 186 | # Compile the list of required vars 187 | set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) 188 | foreach (_component ${FFmpeg_FIND_COMPONENTS}) 189 | list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS) 190 | endforeach () 191 | 192 | # Give a nice error message if some of the required vars are missing. 193 | find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS}) -------------------------------------------------------------------------------- /copy_externals.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | /bin/cp -r -f "externals/mc.rtpsend~.mxo" "/Users/${USER}/Documents/Max 8/Projects/rtpsender/externals" 4 | /bin/cp -r -f "externals/mc.rtpreceive~.mxo" "/Users/${USER}/Documents/Max 8/Projects/rtpreceiver/externals" 5 | 6 | -------------------------------------------------------------------------------- /docs/mc.rtpreceive~.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Receive audio stream via rtp protocol 10 | Receive audio stream via rtp protocol. 11 | 12 | 13 | 14 | 15 | 16 | Tomoya Matsuura 17 | Audio 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Outputs a packet latency in milliseconds at second outlet 34 | Outputs a packet latency in milliseconds at second outlet. 35 | 36 | 37 | 38 | toggle play and pause 39 | toggle play and pause 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Size of an internal ring buffer (multiplied with signal vector size 51 | Size of an internal ring buffer (multiplied with signal vector size.) 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | maximum accepted delay for reordering(in microseconds) 61 | maximum accepted delay for reordering(in microseconds) 62 | 63 | 64 | 65 | number of packets for reorder queue 66 | number of packets for reorder queue 67 | 68 | 69 | 70 | if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored) 71 | if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored). 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | A main incoming network port number 81 | A main incoming network port number. 82 | 83 | 84 | 85 | minimum port number used for rtsp internal transport 86 | minimum port number used for rtsp internal transport 87 | 88 | 89 | 90 | minimum port number used for rtsp internal transport 91 | minimum port number used for rtsp internal transport 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Your IP address for an appropriate network interface 101 | Your IP address for an appropriate network interface. 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/mc.rtpsend~.maxref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | send audio stream via rtp protocol 10 | send audio stream via rtp protocol 11 | 12 | 13 | 14 | 15 | 16 | Tomoya Matsuura 17 | Audio 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored) 46 | if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored). 47 | 48 | 49 | 50 | Retry intervals in milliseconds at connection 51 | Retry intervals in milliseconds at connection 52 | 53 | 54 | 55 | Size of an internal ring buffer (multiplied with signal vector size 56 | Size of an internal ring buffer (multiplied with signal vector size.) 57 | 58 | 59 | 60 | Destination Main Port 61 | Destination Main Port 62 | 63 | 64 | 65 | minimum port number used for rtsp internal transport 66 | minimum port number used for rtsp internal transport 67 | 68 | 69 | 70 | minimum port number used for rtsp internal transport 71 | minimum port number used for rtsp internal transport 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Destination IP Address(can be a domain name) 81 | Destination IP Address(can be a domain name) 82 | 83 | 84 | 85 | number of packets for reorder queue 86 | number of packets for reorder queue 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /help/mc.rtpsend~.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 1, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "openrect" : [ 174.0, 125.0, 853.0, 749.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 853.0, 35 | "description" : "Transmit mc audio signal over IP network using rtp protocol. An internal sample format is an uncompressed PCM, not floating-point but a 16bit integer. The rtp packets are send on UDP protocol. User can choose way of initializing connection from either raw RTP (No handshake) or RTSP (handshake with TCP).", 36 | "digest" : "send audio stream via rtp protocol", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "showrootpatcherontab" : 0, 41 | "showontab" : 0, 42 | "assistshowspatchername" : 0, 43 | "boxes" : [ { 44 | "box" : { 45 | "id" : "obj-9", 46 | "maxclass" : "attrui", 47 | "numinlets" : 1, 48 | "numoutlets" : 1, 49 | "outlettype" : [ "" ], 50 | "patching_rect" : [ 295.0, 115.0, 150.0, 22.0 ] 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "fontname" : "Arial", 57 | "fontsize" : 13.0, 58 | "id" : "obj-1", 59 | "maxclass" : "newobj", 60 | "numinlets" : 1, 61 | "numoutlets" : 1, 62 | "outlettype" : [ "" ], 63 | "patching_rect" : [ 295.0, 155.0, 174.0, 23.0 ], 64 | "saved_object_attributes" : { 65 | "filename" : "rtphelpstarter.js", 66 | "parameter_enable" : 0 67 | } 68 | , 69 | "text" : "js rtphelpstarter mc.rtpsend~" 70 | } 71 | 72 | } 73 | , { 74 | "box" : { 75 | "fontname" : "Arial", 76 | "fontsize" : 13.0, 77 | "id" : "obj-5", 78 | "maxclass" : "newobj", 79 | "numinlets" : 0, 80 | "numoutlets" : 0, 81 | "patcher" : { 82 | "fileversion" : 1, 83 | "appversion" : { 84 | "major" : 8, 85 | "minor" : 1, 86 | "revision" : 5, 87 | "architecture" : "x64", 88 | "modernui" : 1 89 | } 90 | , 91 | "classnamespace" : "box", 92 | "rect" : [ 0.0, 26.0, 853.0, 723.0 ], 93 | "bglocked" : 0, 94 | "openinpresentation" : 0, 95 | "default_fontsize" : 13.0, 96 | "default_fontface" : 0, 97 | "default_fontname" : "Arial", 98 | "gridonopen" : 1, 99 | "gridsize" : [ 15.0, 15.0 ], 100 | "gridsnaponopen" : 1, 101 | "objectsnaponopen" : 1, 102 | "statusbarvisible" : 2, 103 | "toolbarvisible" : 1, 104 | "lefttoolbarpinned" : 0, 105 | "toptoolbarpinned" : 0, 106 | "righttoolbarpinned" : 0, 107 | "bottomtoolbarpinned" : 0, 108 | "toolbars_unpinned_last_save" : 0, 109 | "tallnewobj" : 0, 110 | "boxanimatetime" : 200, 111 | "enablehscroll" : 1, 112 | "enablevscroll" : 1, 113 | "devicewidth" : 0.0, 114 | "description" : "", 115 | "digest" : "", 116 | "tags" : "", 117 | "style" : "", 118 | "subpatcher_template" : "", 119 | "showontab" : 1, 120 | "assistshowspatchername" : 0, 121 | "boxes" : [ ], 122 | "lines" : [ ] 123 | } 124 | , 125 | "patching_rect" : [ 106.0, 88.0, 50.0, 23.0 ], 126 | "saved_object_attributes" : { 127 | "description" : "", 128 | "digest" : "", 129 | "fontsize" : 13.0, 130 | "globalpatchername" : "", 131 | "tags" : "" 132 | } 133 | , 134 | "text" : "p ?", 135 | "varname" : "q_tab" 136 | } 137 | 138 | } 139 | , { 140 | "box" : { 141 | "id" : "obj-2", 142 | "maxclass" : "newobj", 143 | "numinlets" : 0, 144 | "numoutlets" : 0, 145 | "patcher" : { 146 | "fileversion" : 1, 147 | "appversion" : { 148 | "major" : 8, 149 | "minor" : 1, 150 | "revision" : 5, 151 | "architecture" : "x64", 152 | "modernui" : 1 153 | } 154 | , 155 | "classnamespace" : "box", 156 | "rect" : [ 174.0, 151.0, 853.0, 723.0 ], 157 | "bglocked" : 0, 158 | "openinpresentation" : 0, 159 | "default_fontsize" : 12.0, 160 | "default_fontface" : 0, 161 | "default_fontname" : "Arial", 162 | "gridonopen" : 1, 163 | "gridsize" : [ 15.0, 15.0 ], 164 | "gridsnaponopen" : 1, 165 | "objectsnaponopen" : 1, 166 | "statusbarvisible" : 2, 167 | "toolbarvisible" : 1, 168 | "lefttoolbarpinned" : 0, 169 | "toptoolbarpinned" : 0, 170 | "righttoolbarpinned" : 0, 171 | "bottomtoolbarpinned" : 0, 172 | "toolbars_unpinned_last_save" : 0, 173 | "tallnewobj" : 0, 174 | "boxanimatetime" : 200, 175 | "enablehscroll" : 1, 176 | "enablevscroll" : 1, 177 | "devicewidth" : 0.0, 178 | "description" : "", 179 | "digest" : "", 180 | "tags" : "", 181 | "style" : "", 182 | "subpatcher_template" : "", 183 | "showontab" : 1, 184 | "isolateaudio" : 1, 185 | "assistshowspatchername" : 0, 186 | "boxes" : [ { 187 | "box" : { 188 | "fontname" : "Lato", 189 | "fontsize" : 13.0, 190 | "id" : "obj-3", 191 | "maxclass" : "comment", 192 | "numinlets" : 1, 193 | "numoutlets" : 0, 194 | "patching_rect" : [ 10.0, 58.540092766284943, 585.0, 22.0 ], 195 | "text" : "send audio stream via rtp protocol", 196 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ] 197 | } 198 | 199 | } 200 | , { 201 | "box" : { 202 | "id" : "obj-37", 203 | "maxclass" : "scope~", 204 | "numinlets" : 2, 205 | "numoutlets" : 0, 206 | "patching_rect" : [ 30.834952294826508, 589.330088555812836, 346.0, 113.495145857334137 ] 207 | } 208 | 209 | } 210 | , { 211 | "box" : { 212 | "id" : "obj-36", 213 | "maxclass" : "newobj", 214 | "numinlets" : 1, 215 | "numoutlets" : 1, 216 | "outlettype" : [ "bang" ], 217 | "patching_rect" : [ 30.834952294826508, 178.912618577480316, 58.0, 22.0 ], 218 | "text" : "loadbang" 219 | } 220 | 221 | } 222 | , { 223 | "box" : { 224 | "id" : "obj-35", 225 | "maxclass" : "message", 226 | "numinlets" : 2, 227 | "numoutlets" : 1, 228 | "outlettype" : [ "" ], 229 | "patching_rect" : [ 30.834952294826508, 216.504851400852203, 128.0, 22.0 ], 230 | "text" : "increment 1000. 1000." 231 | } 232 | 233 | } 234 | , { 235 | "box" : { 236 | "id" : "obj-32", 237 | "maxclass" : "newobj", 238 | "numinlets" : 1, 239 | "numoutlets" : 2, 240 | "outlettype" : [ "multichannelsignal", "" ], 241 | "patching_rect" : [ 30.834952294826508, 545.388345122337341, 346.0, 22.0 ], 242 | "text" : "mc.rtpreceive~ @address 127.0.0.1 @port 30000 @channels 8" 243 | } 244 | 245 | } 246 | , { 247 | "box" : { 248 | "bubble" : 1, 249 | "fontname" : "Arial", 250 | "fontsize" : 13.0, 251 | "id" : "obj-31", 252 | "linecount" : 3, 253 | "maxclass" : "comment", 254 | "numinlets" : 1, 255 | "numoutlets" : 0, 256 | "patching_rect" : [ 660.378758460283279, 389.825237154960632, 162.0, 54.0 ], 257 | "text" : "Internal buffer size relative to signal vector size" 258 | } 259 | 260 | } 261 | , { 262 | "box" : { 263 | "attr" : "ringbuf_framenum", 264 | "id" : "obj-30", 265 | "maxclass" : "attrui", 266 | "numinlets" : 1, 267 | "numoutlets" : 1, 268 | "outlettype" : [ "" ], 269 | "patching_rect" : [ 508.378758460283279, 405.825237154960632, 150.0, 22.0 ], 270 | "text_width" : 112.980456322431564 271 | } 272 | 273 | } 274 | , { 275 | "box" : { 276 | "bubble" : 1, 277 | "fontname" : "Arial", 278 | "fontsize" : 13.0, 279 | "id" : "obj-29", 280 | "linecount" : 2, 281 | "maxclass" : "comment", 282 | "numinlets" : 1, 283 | "numoutlets" : 0, 284 | "patching_rect" : [ 660.378758460283279, 319.412617206573486, 171.349513351917267, 40.0 ], 285 | "text" : "Acceptable number of packets for reordering" 286 | } 287 | 288 | } 289 | , { 290 | "box" : { 291 | "attr" : "reorder_queue_size", 292 | "id" : "obj-27", 293 | "maxclass" : "attrui", 294 | "numinlets" : 1, 295 | "numoutlets" : 1, 296 | "outlettype" : [ "" ], 297 | "patching_rect" : [ 508.378758460283279, 328.412617206573486, 150.0, 22.0 ], 298 | "text_width" : 112.980456322431564 299 | } 300 | 301 | } 302 | , { 303 | "box" : { 304 | "bubble" : 1, 305 | "fontname" : "Arial", 306 | "fontsize" : 13.0, 307 | "id" : "obj-26", 308 | "linecount" : 3, 309 | "maxclass" : "comment", 310 | "numinlets" : 1, 311 | "numoutlets" : 0, 312 | "patching_rect" : [ 660.378758460283279, 256.752423644065857, 171.349513351917267, 54.0 ], 313 | "text" : "Available Port Range for Audio Transmittion in RTSP mode" 314 | } 315 | 316 | } 317 | , { 318 | "box" : { 319 | "bubble" : 1, 320 | "fontname" : "Arial", 321 | "fontsize" : 13.0, 322 | "id" : "obj-25", 323 | "linecount" : 2, 324 | "maxclass" : "comment", 325 | "numinlets" : 1, 326 | "numoutlets" : 0, 327 | "patching_rect" : [ 313.165046334266663, 396.825237154960632, 177.174755990505219, 40.0 ], 328 | "text" : "Connection Retry Interval (in ms)" 329 | } 330 | 331 | } 332 | , { 333 | "box" : { 334 | "bubble" : 1, 335 | "fontname" : "Arial", 336 | "fontsize" : 13.0, 337 | "id" : "obj-24", 338 | "maxclass" : "comment", 339 | "numinlets" : 1, 340 | "numoutlets" : 0, 341 | "patching_rect" : [ 313.165046334266663, 367.247568130493164, 177.174755990505219, 25.0 ], 342 | "text" : "Use RTSP for handshake" 343 | } 344 | 345 | } 346 | , { 347 | "box" : { 348 | "bubble" : 1, 349 | "fontname" : "Arial", 350 | "fontsize" : 13.0, 351 | "id" : "obj-23", 352 | "maxclass" : "comment", 353 | "numinlets" : 1, 354 | "numoutlets" : 0, 355 | "patching_rect" : [ 313.165046334266663, 328.412617206573486, 177.174755990505219, 25.0 ], 356 | "text" : " Audio Channels" 357 | } 358 | 359 | } 360 | , { 361 | "box" : { 362 | "bubble" : 1, 363 | "fontname" : "Arial", 364 | "fontsize" : 13.0, 365 | "id" : "obj-22", 366 | "maxclass" : "comment", 367 | "numinlets" : 1, 368 | "numoutlets" : 0, 369 | "patching_rect" : [ 313.165046334266663, 290.548540055751801, 177.174755990505219, 25.0 ], 370 | "text" : "Destination Main Port" 371 | } 372 | 373 | } 374 | , { 375 | "box" : { 376 | "bubble" : 1, 377 | "fontname" : "Arial", 378 | "fontsize" : 13.0, 379 | "id" : "obj-28", 380 | "maxclass" : "comment", 381 | "numinlets" : 1, 382 | "numoutlets" : 0, 383 | "patching_rect" : [ 313.165046334266663, 256.752423644065857, 177.174755990505219, 25.0 ], 384 | "text" : "Destination IP Address" 385 | } 386 | 387 | } 388 | , { 389 | "box" : { 390 | "attr" : "max_port", 391 | "id" : "obj-21", 392 | "maxclass" : "attrui", 393 | "numinlets" : 1, 394 | "numoutlets" : 1, 395 | "outlettype" : [ "" ], 396 | "patching_rect" : [ 508.378758460283279, 290.548540055751801, 150.0, 22.0 ], 397 | "text_width" : 72.815532982349396 398 | } 399 | 400 | } 401 | , { 402 | "box" : { 403 | "attr" : "min_port", 404 | "id" : "obj-18", 405 | "maxclass" : "attrui", 406 | "numinlets" : 1, 407 | "numoutlets" : 1, 408 | "outlettype" : [ "" ], 409 | "patching_rect" : [ 508.378758460283279, 256.752423644065857, 150.0, 22.0 ], 410 | "text_width" : 72.815532982349396 411 | } 412 | 413 | } 414 | , { 415 | "box" : { 416 | "attr" : "retry_rate", 417 | "id" : "obj-19", 418 | "maxclass" : "attrui", 419 | "numinlets" : 1, 420 | "numoutlets" : 1, 421 | "outlettype" : [ "" ], 422 | "patching_rect" : [ 161.165046334266663, 405.825237154960632, 150.0, 22.0 ], 423 | "text_width" : 72.815532982349396 424 | } 425 | 426 | } 427 | , { 428 | "box" : { 429 | "attr" : "use_rtsp", 430 | "id" : "obj-20", 431 | "maxclass" : "attrui", 432 | "numinlets" : 1, 433 | "numoutlets" : 1, 434 | "outlettype" : [ "" ], 435 | "patching_rect" : [ 161.165046334266663, 369.90290755033493, 150.0, 22.0 ], 436 | "text_width" : 72.815532982349396 437 | } 438 | 439 | } 440 | , { 441 | "box" : { 442 | "attr" : "channels", 443 | "id" : "obj-17", 444 | "maxclass" : "attrui", 445 | "numinlets" : 1, 446 | "numoutlets" : 1, 447 | "outlettype" : [ "" ], 448 | "patching_rect" : [ 161.165046334266663, 330.097082853317261, 150.0, 22.0 ], 449 | "text_width" : 72.815532982349396 450 | } 451 | 452 | } 453 | , { 454 | "box" : { 455 | "attr" : "port", 456 | "id" : "obj-16", 457 | "maxclass" : "attrui", 458 | "numinlets" : 1, 459 | "numoutlets" : 1, 460 | "outlettype" : [ "" ], 461 | "patching_rect" : [ 161.165046334266663, 294.174753248691559, 150.0, 22.0 ], 462 | "text_width" : 72.815532982349396 463 | } 464 | 465 | } 466 | , { 467 | "box" : { 468 | "attr" : "address", 469 | "id" : "obj-15", 470 | "maxclass" : "attrui", 471 | "numinlets" : 1, 472 | "numoutlets" : 1, 473 | "outlettype" : [ "" ], 474 | "patching_rect" : [ 161.165046334266663, 258.252423644065857, 150.0, 22.0 ], 475 | "text_width" : 72.815532982349396 476 | } 477 | 478 | } 479 | , { 480 | "box" : { 481 | "fontname" : "Lato", 482 | "fontsize" : 13.0, 483 | "id" : "obj-13", 484 | "maxclass" : "comment", 485 | "numinlets" : 1, 486 | "numoutlets" : 0, 487 | "patching_rect" : [ 518.602054506540298, 680.825234413146973, 313.126217305660248, 22.0 ], 488 | "text" : "https://github.com/tomoyanonymous/rtpsendreceive", 489 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ], 490 | "textjustification" : 2 491 | } 492 | 493 | } 494 | , { 495 | "box" : { 496 | "fontname" : "Lato", 497 | "fontsize" : 13.0, 498 | "id" : "obj-12", 499 | "maxclass" : "comment", 500 | "numinlets" : 1, 501 | "numoutlets" : 0, 502 | "patching_rect" : [ 626.369043320417404, 656.825234413146973, 205.359228491783142, 22.0 ], 503 | "text" : "Made by Tomoya Matsuura", 504 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ], 505 | "textjustification" : 2 506 | } 507 | 508 | } 509 | , { 510 | "box" : { 511 | "id" : "obj-11", 512 | "maxclass" : "newobj", 513 | "numinlets" : 2, 514 | "numoutlets" : 1, 515 | "outlettype" : [ "multichannelsignal" ], 516 | "patching_rect" : [ 30.834952294826508, 258.252423644065857, 120.0, 22.0 ], 517 | "text" : "mc.cycle~ @chans 8" 518 | } 519 | 520 | } 521 | , { 522 | "box" : { 523 | "fontname" : "Lato", 524 | "fontsize" : 13.0, 525 | "id" : "obj-10", 526 | "linecount" : 4, 527 | "maxclass" : "comment", 528 | "numinlets" : 1, 529 | "numoutlets" : 0, 530 | "patching_rect" : [ 10.320512861013412, 82.540092766284943, 585.0, 69.0 ], 531 | "text" : "Transmit mc audio signal over IP network using rtp protocol. An internal sample format is an uncompressed PCM, not floating-point but a 16bit integer.\nThe rtp packets are send on UDP protocol. User can choose way of initializing connection from either raw RTP (No handshake) or RTSP (handshake with TCP).", 532 | "textcolor" : [ 0.490196078431373, 0.498039215686275, 0.517647058823529, 1.0 ] 533 | } 534 | 535 | } 536 | , { 537 | "box" : { 538 | "fontname" : "Lato", 539 | "fontsize" : 13.0, 540 | "id" : "obj-9", 541 | "linecount" : 7, 542 | "maxclass" : "comment", 543 | "numinlets" : 1, 544 | "numoutlets" : 0, 545 | "patching_rect" : [ 533.165161103010178, 491.019413828849792, 298.563110709190369, 116.0 ], 546 | "text" : "Note for port assignment:\nOne more port next to main port will be used for RTCP protocol.\nIn RTSP mode, initial handshake is done via main port and a port for audio transmission will be choosen automatically. User can limit the port by min_port and max_port attribute.", 547 | "textcolor" : [ 0.0, 0.0, 0.0, 1.0 ] 548 | } 549 | 550 | } 551 | , { 552 | "box" : { 553 | "fontname" : "Lato", 554 | "fontsize" : 48.0, 555 | "id" : "obj-5", 556 | "maxclass" : "comment", 557 | "numinlets" : 1, 558 | "numoutlets" : 0, 559 | "patching_rect" : [ 10.0, 0.0, 275.941748917102814, 64.0 ], 560 | "text" : "mc.rtpsend~" 561 | } 562 | 563 | } 564 | , { 565 | "box" : { 566 | "id" : "obj-1", 567 | "maxclass" : "newobj", 568 | "numinlets" : 1, 569 | "numoutlets" : 0, 570 | "patching_rect" : [ 30.834952294826508, 491.019413828849792, 333.0, 22.0 ], 571 | "text" : "mc.rtpsend~ @address 127.0.0.1 @port 30000 @channels 8" 572 | } 573 | 574 | } 575 | ], 576 | "lines" : [ { 577 | "patchline" : { 578 | "destination" : [ "obj-1", 0 ], 579 | "source" : [ "obj-11", 0 ] 580 | } 581 | 582 | } 583 | , { 584 | "patchline" : { 585 | "destination" : [ "obj-1", 0 ], 586 | "midpoints" : [ 170.665046334266663, 289.349513351917267, 40.334952294826508, 289.349513351917267 ], 587 | "source" : [ "obj-15", 0 ] 588 | } 589 | 590 | } 591 | , { 592 | "patchline" : { 593 | "destination" : [ "obj-1", 0 ], 594 | "midpoints" : [ 170.665046334266663, 325.349513351917267, 40.334952294826508, 325.349513351917267 ], 595 | "source" : [ "obj-16", 0 ] 596 | } 597 | 598 | } 599 | , { 600 | "patchline" : { 601 | "destination" : [ "obj-1", 0 ], 602 | "midpoints" : [ 170.665046334266663, 365.233008444309235, 40.334952294826508, 365.233008444309235 ], 603 | "source" : [ "obj-17", 0 ] 604 | } 605 | 606 | } 607 | , { 608 | "patchline" : { 609 | "destination" : [ "obj-1", 0 ], 610 | "midpoints" : [ 517.878758460283279, 281.233008444309235, 494.815534353256226, 281.233008444309235, 494.815534353256226, 467.281553983688354, 40.334952294826508, 467.281553983688354 ], 611 | "source" : [ "obj-18", 0 ] 612 | } 613 | 614 | } 615 | , { 616 | "patchline" : { 617 | "destination" : [ "obj-1", 0 ], 618 | "midpoints" : [ 170.665046334266663, 434.233008444309235, 40.334952294826508, 434.233008444309235 ], 619 | "source" : [ "obj-19", 0 ] 620 | } 621 | 622 | } 623 | , { 624 | "patchline" : { 625 | "destination" : [ "obj-1", 0 ], 626 | "midpoints" : [ 170.665046334266663, 398.233008444309235, 40.334952294826508, 398.233008444309235 ], 627 | "source" : [ "obj-20", 0 ] 628 | } 629 | 630 | } 631 | , { 632 | "patchline" : { 633 | "destination" : [ "obj-1", 0 ], 634 | "midpoints" : [ 517.878758460283279, 322.970872402191162, 494.815534353256226, 322.970872402191162, 494.815534353256226, 465.339806437492371, 40.334952294826508, 465.339806437492371 ], 635 | "source" : [ "obj-21", 0 ] 636 | } 637 | 638 | } 639 | , { 640 | "patchline" : { 641 | "destination" : [ "obj-1", 0 ], 642 | "midpoints" : [ 517.878758460283279, 363.106795251369476, 494.815534353256226, 363.106795251369476, 494.815534353256226, 467.281553983688354, 40.334952294826508, 467.281553983688354 ], 643 | "source" : [ "obj-27", 0 ] 644 | } 645 | 646 | } 647 | , { 648 | "patchline" : { 649 | "destination" : [ "obj-1", 0 ], 650 | "midpoints" : [ 517.878758460283279, 467.281553983688354, 40.334952294826508, 467.281553983688354 ], 651 | "source" : [ "obj-30", 0 ] 652 | } 653 | 654 | } 655 | , { 656 | "patchline" : { 657 | "destination" : [ "obj-37", 0 ], 658 | "source" : [ "obj-32", 0 ] 659 | } 660 | 661 | } 662 | , { 663 | "patchline" : { 664 | "destination" : [ "obj-11", 0 ], 665 | "source" : [ "obj-35", 0 ] 666 | } 667 | 668 | } 669 | , { 670 | "patchline" : { 671 | "destination" : [ "obj-35", 0 ], 672 | "source" : [ "obj-36", 0 ] 673 | } 674 | 675 | } 676 | ] 677 | } 678 | , 679 | "patching_rect" : [ 18.0, 87.5, 47.0, 22.0 ], 680 | "saved_object_attributes" : { 681 | "description" : "", 682 | "digest" : "", 683 | "globalpatchername" : "", 684 | "tags" : "" 685 | } 686 | , 687 | "text" : "p basic" 688 | } 689 | 690 | } 691 | ], 692 | "lines" : [ { 693 | "patchline" : { 694 | "destination" : [ "obj-1", 0 ], 695 | "source" : [ "obj-9", 0 ] 696 | } 697 | 698 | } 699 | ], 700 | "dependency_cache" : [ { 701 | "name" : "rtphelpstarter.js", 702 | "bootpath" : "~/codes/rtpsendreceive/help", 703 | "patcherrelativepath" : ".", 704 | "type" : "TEXT", 705 | "implicit" : 1 706 | } 707 | , { 708 | "name" : "mc.rtpsend~.mxo", 709 | "type" : "iLaX" 710 | } 711 | , { 712 | "name" : "mc.rtpreceive~.mxo", 713 | "type" : "iLaX" 714 | } 715 | ], 716 | "autosave" : 0 717 | } 718 | 719 | } 720 | -------------------------------------------------------------------------------- /help/rtphelpstarter.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var task = new Task(init, this); 4 | task.schedule(100); 5 | 6 | function init() 7 | { 8 | 9 | var b = this.patcher.getnamed("q_tab"); 10 | if (b == null) 11 | { 12 | this.patcher.message("script", "newobject", "newobj", "@text","p ?", "@varname", "q_tab", "@patching_rect", 205, 205, 50, 20); 13 | var q=this.patcher.getnamed("q_tab"); 14 | q.subpatcher().message("wclose"); 15 | q.message("showontab", 1); 16 | } 17 | } 18 | 19 | function resize(x, y) 20 | { 21 | if(x==null) 22 | { 23 | this.patcher.wind.size=[853, 725]; 24 | } 25 | else 26 | { 27 | this.patcher.wind.size= [x, y]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /help/rtpsendreceive.hello-world.maxhelp: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 0, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "rect" : [ 63.0, 104.0, 540.0, 384.0 ], 13 | "bglocked" : 0, 14 | "openinpresentation" : 0, 15 | "default_fontsize" : 12.0, 16 | "default_fontface" : 0, 17 | "default_fontname" : "Lato Light", 18 | "gridonopen" : 1, 19 | "gridsize" : [ 5.0, 5.0 ], 20 | "gridsnaponopen" : 2, 21 | "objectsnaponopen" : 0, 22 | "statusbarvisible" : 2, 23 | "toolbarvisible" : 1, 24 | "lefttoolbarpinned" : 2, 25 | "toptoolbarpinned" : 2, 26 | "righttoolbarpinned" : 2, 27 | "bottomtoolbarpinned" : 2, 28 | "toolbars_unpinned_last_save" : 15, 29 | "tallnewobj" : 0, 30 | "boxanimatetime" : 200, 31 | "enablehscroll" : 1, 32 | "enablevscroll" : 1, 33 | "devicewidth" : 0.0, 34 | "description" : "", 35 | "digest" : "", 36 | "tags" : "", 37 | "style" : "tap", 38 | "subpatcher_template" : "tap.template", 39 | "boxes" : [ { 40 | "box" : { 41 | "clickedimage" : 1, 42 | "id" : "obj-16", 43 | "maxclass" : "pictctrl", 44 | "name" : "min.edit-button.png", 45 | "numinlets" : 1, 46 | "numoutlets" : 1, 47 | "outlettype" : [ "int" ], 48 | "parameter_enable" : 0, 49 | "patching_rect" : [ 297.0, 144.0, 16.0, 16.0 ], 50 | "presentation_rect" : [ 297.0, 144.0, 16.0, 16.0 ] 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "hidden" : 1, 57 | "id" : "obj-15", 58 | "linecount" : 2, 59 | "maxclass" : "message", 60 | "numinlets" : 2, 61 | "numoutlets" : 1, 62 | "outlettype" : [ "" ], 63 | "patching_rect" : [ 180.0, 265.0, 291.0, 37.0 ], 64 | "presentation_linecount" : 2, 65 | "presentation_rect" : [ 180.0, 265.0, 291.0, 37.0 ], 66 | "style" : "", 67 | "text" : "generate \"Macintosh HD:/Users/tim/Documents/Max 8/Packages/afts/source/projects/afts.demo\"" 68 | } 69 | 70 | } 71 | , { 72 | "box" : { 73 | "hidden" : 1, 74 | "id" : "obj-10", 75 | "maxclass" : "newobj", 76 | "numinlets" : 1, 77 | "numoutlets" : 1, 78 | "outlettype" : [ "" ], 79 | "patching_rect" : [ 180.0, 305.0, 67.0, 23.0 ], 80 | "presentation_rect" : [ 180.0, 305.0, 67.0, 23.0 ], 81 | "style" : "", 82 | "text" : "min.project" 83 | } 84 | 85 | } 86 | , { 87 | "box" : { 88 | "id" : "obj-6", 89 | "maxclass" : "message", 90 | "numinlets" : 2, 91 | "numoutlets" : 1, 92 | "outlettype" : [ "" ], 93 | "patching_rect" : [ 110.0, 200.0, 145.0, 23.0 ], 94 | "presentation_rect" : [ 110.0, 200.0, 145.0, 23.0 ], 95 | "style" : "" 96 | } 97 | 98 | } 99 | , { 100 | "box" : { 101 | "id" : "obj-4", 102 | "maxclass" : "button", 103 | "numinlets" : 1, 104 | "numoutlets" : 1, 105 | "outlettype" : [ "bang" ], 106 | "patching_rect" : [ 155.0, 90.0, 24.0, 24.0 ], 107 | "presentation_rect" : [ 155.0, 90.0, 24.0, 24.0 ], 108 | "style" : "" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "fontname" : "Arial", 115 | "fontsize" : 14.0, 116 | "id" : "obj-2", 117 | "maxclass" : "newobj", 118 | "numinlets" : 1, 119 | "numoutlets" : 1, 120 | "outlettype" : [ "" ], 121 | "patching_rect" : [ 155.0, 140.0, 160.0, 24.0 ], 122 | "presentation_rect" : [ 155.0, 140.0, 160.0, 24.0 ], 123 | "style" : "", 124 | "text" : "rtpsendreceive.hello-world aloha" 125 | } 126 | 127 | } 128 | ], 129 | "lines" : [ { 130 | "patchline" : { 131 | "destination" : [ "obj-10", 0 ], 132 | "hidden" : 1, 133 | "source" : [ "obj-15", 0 ] 134 | } 135 | 136 | } 137 | , { 138 | "patchline" : { 139 | "destination" : [ "obj-15", 0 ], 140 | "hidden" : 1, 141 | "source" : [ "obj-16", 0 ] 142 | } 143 | 144 | } 145 | , { 146 | "patchline" : { 147 | "destination" : [ "obj-6", 1 ], 148 | "source" : [ "obj-2", 0 ] 149 | } 150 | 151 | } 152 | , { 153 | "patchline" : { 154 | "destination" : [ "obj-2", 0 ], 155 | "source" : [ "obj-4", 0 ] 156 | } 157 | 158 | } 159 | ], 160 | "dependency_cache" : [ { 161 | "name" : "min.edit-button.png", 162 | "bootpath" : "~/Documents/Max 8/Packages/min-devkit/help", 163 | "patcherrelativepath" : ".", 164 | "type" : "PNG", 165 | "implicit" : 1 166 | } 167 | , { 168 | "name" : "rtpsendreceive.hello-world.mxo", 169 | "type" : "iLaX" 170 | } 171 | , { 172 | "name" : "min.project.mxo", 173 | "type" : "iLaX" 174 | } 175 | ], 176 | "autosave" : 0, 177 | "styles" : [ { 178 | "name" : "tap", 179 | "default" : { 180 | "fontname" : [ "Lato Light" ] 181 | } 182 | , 183 | "parentstyle" : "", 184 | "multi" : 0 185 | } 186 | ], 187 | "bgfillcolor_type" : "gradient", 188 | "bgfillcolor_color1" : [ 0.376471, 0.384314, 0.4, 1.0 ], 189 | "bgfillcolor_color2" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 190 | "bgfillcolor_color" : [ 0.290196, 0.309804, 0.301961, 1.0 ], 191 | "bgfillcolor_angle" : 270.0, 192 | "bgfillcolor_proportion" : 0.39 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomoyanonymous/rtpsendreceive/f13ef19b8ee085b96471e6894196c7f0dc1e1ad0/icon.png -------------------------------------------------------------------------------- /package-info.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "author" : "Tomoya Matsuura", 3 | "description" : "Send & Receive multichannel audio signals via network using rtp protocols", 4 | "homepatcher" : "rtpsendreceive_home.maxpat", 5 | "max_version_min" : "8.0", "max_version_max" : "none", 6 | "name" : "@C74_PACKAGE_NAME@", 7 | "os" : { 8 | "macintosh" : { 9 | "platform" : [ "x64" ], 10 | "min_version" : "none" 11 | }, 12 | "windows" : { 13 | "platform" : [ "x64" ], 14 | "min_version" : "none" 15 | } 16 | }, 17 | "package_extra" : { 18 | "reverse_domain" : "com.cycling74", 19 | "copyright" : "Copyright (c) Tomoya Matsuura" 20 | }, 21 | "tags" : [ ], 22 | "version" : "@GIT_VERSION_MAJ@.@GIT_VERSION_MIN@.@GIT_VERSION_SUB@", 23 | "website" : "https://github.com/tomoyanonymous/rtpsendreceive" 24 | } 25 | -------------------------------------------------------------------------------- /rtpsendreceive.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "C_Cpp.configurationWarnings": "Disabled", 9 | "clangd.arguments": [ 10 | "-clang-tidy", 11 | "-background-index", 12 | "-compile-commands-dir=build", 13 | "-header-insertion=never", 14 | "--query-driver=\"/usr/bin/clang++\"" 15 | ], 16 | "cmake.debugConfig": { 17 | "stopAtEntry": false, 18 | "MIMode": "lldb", 19 | "miDebuggerPath": "/Users/tomoya/.vscode/extensions/ms-vscode.cpptools-0.26.3/debugAdapters/lldb/bin/lldb-mi", 20 | "logging": { 21 | // "trace": true, 22 | // "engineLogging": true, 23 | // "traceResponse": true 24 | } 25 | }, 26 | "cmake.configureSettings": { 27 | "CMAKE_TOOLCHAIN_FILE": "./vcpkg/scripts/buildsystems/vcpkg.cmake" 28 | }, 29 | "clangd.checkUpdates": true 30 | }, 31 | "launch": { 32 | "version": "0.2.0", 33 | "configurations": [ 34 | { 35 | "type": "lldb", 36 | "request": "launch", 37 | "name": "Sender Debug", 38 | "program": "/Applications/Max.app/Contents/MacOS/Max", 39 | "args": [ 40 | "${workspaceFolder}/test_patch/sender_test.maxpat" 41 | ], 42 | "cwd": "${workspaceFolder}", 43 | "preLaunchTask": "build before send_debug" 44 | }, 45 | { 46 | "type": "lldb", 47 | "request": "launch", 48 | "name": "Sender Project Debug", 49 | "program": "/Applications/Max.app/Contents/MacOS/Max", 50 | "args": [ 51 | "/Users/${env:USER}/Documents/Max 8/Projects/rtpsender/rtpsender.maxproj" 52 | ], 53 | "cwd": "${workspaceFolder}", 54 | "preLaunchTask": "build before send_debug" 55 | }, 56 | { 57 | "type": "lldb", 58 | "request": "launch", 59 | "name": "Receiver Debug", 60 | "program": "/Applications/Max.app/Contents/MacOS/Max", 61 | "args": [ 62 | "${workspaceFolder}/test_patch/receiver_test.maxpat" 63 | ], 64 | "cwd": "${workspaceFolder}", 65 | "preLaunchTask": "build before receive_debug" 66 | }, 67 | { 68 | "type": "lldb", 69 | "request": "launch", 70 | "name": "Receiver Project Debug", 71 | "program": "/Applications/Max.app/Contents/MacOS/Max", 72 | "args": [ 73 | "/Users/${env:USER}/Documents/Max 8/Projects/rtpreceiver/rtpreceiver.maxproj" 74 | ], 75 | "cwd": "${workspaceFolder}", 76 | "preLaunchTask": "build before receive_debug" 77 | }, 78 | { 79 | "type": "lldb", 80 | "request": "launch", 81 | "name": "Self-loop Debug", 82 | "program": "/Applications/Max.app/Contents/MacOS/Max", 83 | "args": [ 84 | "${workspaceFolder}/test_patch/self_loop_test.maxpat" 85 | ], 86 | "cwd": "${workspaceFolder}", 87 | "preLaunchTask": "build_selfloop" 88 | }, 89 | { 90 | "name": "cmake default", 91 | "type": "lldb", 92 | "request": "launch", 93 | "program": "${command:cmake.launchTargetPath}", 94 | "args": [], 95 | "cwd": "${workspaceFolder}" 96 | } 97 | ] 98 | }, 99 | "tasks": { 100 | // See https://go.microsoft.com/fwlink/?LinkId=733558 101 | // for the documentation about the tasks.json format 102 | "version": "2.0.0", 103 | "tasks": [ 104 | { 105 | "label": "copy external into project", 106 | "type": "shell", 107 | "command": "/bin/zsh", 108 | "args": [ 109 | "${workspaceFolder}/copy_externals.sh" 110 | ], 111 | "problemMatcher": [] 112 | }, 113 | { 114 | "label": "build_selfloop", 115 | "type": "shell", 116 | "dependsOn": [ 117 | "build before send_debug", 118 | "build before receive_debug" 119 | ] 120 | }, 121 | { 122 | "label": "build before send_debug", 123 | "type": "shell", 124 | "command": "cmake", 125 | "args": [ 126 | "--build", 127 | "${workspaceFolder}/build", 128 | "--config", 129 | "debug", 130 | "--target", 131 | "mc.rtpsend_tilde", 132 | "-j", 133 | "18" 134 | ], 135 | "group": { 136 | "kind": "build", 137 | "isDefault": true 138 | }, 139 | "problemMatcher": [] 140 | }, 141 | { 142 | "label": "build before receive_debug", 143 | "type": "shell", 144 | "command": "cmake", 145 | "args": [ 146 | "--build", 147 | "${workspaceFolder}/build", 148 | "--config", 149 | "debug", 150 | "--target", 151 | "mc.rtpreceive_tilde", 152 | "-j", 153 | "18" 154 | ], 155 | "group": { 156 | "kind": "build", 157 | "isDefault": true 158 | }, 159 | "problemMatcher": [] 160 | } 161 | ] 162 | } 163 | } -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomoyanonymous/rtpsendreceive/f13ef19b8ee085b96471e6894196c7f0dc1e1ad0/screenshot.jpg -------------------------------------------------------------------------------- /setup_dev_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | xcode-select --install 4 | 5 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" 6 | 7 | brew cask install visual-studio-code 8 | 9 | #install ffmpeg built from source (debug&static) 10 | 11 | brew install cmake pkg-config nasm freetype opus 12 | 13 | git clone https://git.ffmpeg.org/ffmpeg.git /tmp/ffmpeg 14 | 15 | pushd /tmp/ffmpeg 16 | 17 | ./configure --prefix=/usr/local/Cellar/ffmpeg/HEAD_Debug --disable-avfoundation --disable-iconv --disable-filters --disable-devices --disable-shared --enable-static --disable-optimizations --disable-mmx --disable-audiotoolbox --disable-videotoolbox --disable-stripping --disable-appkit --disable-zlib --disable-coreimage --disable-bzlib --disable-securetransport --disable-sdl2 --disable-lzma --enable-libopus --pkg-config-flags=--static --cc=/usr/bin/clang --cxx=/usr/bin/clang++ 18 | 19 | make -j 20 | 21 | make install 22 | 23 | popd 24 | 25 | git clone https://github.com/tomoyanonymous/rtpsendreceive.git ~/Desktop/rtpsendreceive 26 | 27 | pushd ~/Desktop/rtpsendreceive 28 | 29 | mkdir build && cd build && cmake .. 30 | 31 | cmake --build . --target all 32 | 33 | popd 34 | 35 | -------------------------------------------------------------------------------- /source/projects/mc.rtpreceive_tilde/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Min-DevKit Authors. All rights reserved. 2 | # Use of this source code is governed by the MIT License found in the License.md file. 3 | 4 | cmake_minimum_required(VERSION 3.0) 5 | 6 | set(C74_MIN_API_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../min-api) 7 | include(${C74_MIN_API_DIR}/script/min-pretarget.cmake) 8 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) 9 | 10 | 11 | ############################################################# 12 | # MAX EXTERNAL 13 | ############################################################# 14 | 15 | set( SOURCE_FILES 16 | ${PROJECT_NAME}.cpp 17 | ) 18 | # set(RTPSRLIB ${}) 19 | add_library( 20 | ${PROJECT_NAME} 21 | MODULE 22 | ${SOURCE_FILES} 23 | ) 24 | target_include_directories(${PROJECT_NAME} PUBLIC 25 | ${C74_INCLUDES} 26 | ${CMAKE_CURRENT_SOURCE_DIR}/../../rtpsendreceive_lib 27 | ) 28 | set(RTPSR_LIB "rtpsendreceive") 29 | 30 | message(STATUS testlib!${RTPSR_LIB}) 31 | target_link_libraries(${PROJECT_NAME} PUBLIC ${RTPSR_LIB}) 32 | target_link_libraries(${PROJECT_NAME} PUBLIC ${FFMPEG_LIBRARIES}) 33 | 34 | include(${C74_MIN_API_DIR}/script/min-posttarget.cmake) 35 | message(STATUS ${CMAKE_SOURCE_DIR}/externals/mc.rtpreceive~.mxo) 36 | execute_process(COMMAND ln -s ${CMAKE_SOURCE_DIR}/externals/mc.rtpreceive~.mxo ${CMAKE_SOURCE_DIR}/test_patch/) 37 | 38 | ############################################################# 39 | # UNIT TEST 40 | ############################################################# 41 | 42 | include(mc.rtpreceive_tilde_test.cmake) 43 | -------------------------------------------------------------------------------- /source/projects/mc.rtpreceive_tilde/mc.rtpreceive_tilde.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @ingroup minexamples 3 | /// @copyright Copyright 2018 The Min-DevKit Authors. All rights 4 | /// reserved. 5 | /// @license Use of this source code is governed by the MIT License 6 | /// found in the License.md file. 7 | 8 | #include "c74_min.h" 9 | #include "min_debugutils.hpp" 10 | #include "rtpreceiver.hpp" 11 | 12 | using namespace c74::min; 13 | 14 | class rtpreceive_tilde : public object, public mc_operator<> { 15 | private: 16 | bool m_initialized {false}; 17 | 18 | public: 19 | 20 | MIN_DESCRIPTION {"Receive audio stream via rtp protocol."}; 21 | MIN_TAGS {"Audio"}; 22 | MIN_AUTHOR {"Tomoya Matsuura"}; 23 | MIN_RELATED {"mc.rtpsend_tilde"}; 24 | rtpreceive_tilde(const atoms& args = {}) { 25 | // resetReceiver(); 26 | 27 | m_initialized = true; 28 | } 29 | 30 | attribute address {this, "address", "127.0.0.1", description {"Your IP address for an appropriate network interface."}}; 31 | attribute port {this, "port", 30000, description {"A main incoming network port number."}}; 32 | attribute codec {this, "codec", "pcm_s16be", setter {MIN_FUNCTION {auto c = rtpsr::getCodecByName(args[0]); 33 | if (c == rtpsr::Codec::INVALID) { 34 | cerr << "Invalid Codec Name. Using pcm_s16be" << endl; 35 | std::ostream& out = cerr; 36 | c = rtpsr::Codec::PCM_s16BE; 37 | } 38 | return args; 39 | } 40 | } 41 | } 42 | ; 43 | attribute active {this, "active", 0, setter {MIN_FUNCTION {int res = args[0]; 44 | if (m_initialized && rtpreceiver != nullptr) { } 45 | return {}; 46 | } 47 | } 48 | } 49 | ; 50 | attribute reorder_queue_size {this, "reorder_queue_size", 500.0, description {"number of packets for reorder queue"}}; 51 | attribute ringbuf_framenum { 52 | this, "ringbuf_framenum", 4, range {1, 1000}, description {"Size of an internal ring buffer (multiplied with signal vector size.)"}}; 53 | attribute max_delay {this, "max_delay", 500000, description {"maximum accepted delay for reordering(in microseconds)"}}; 54 | attribute min_port { 55 | this, "min_port", 5000, range {0, 1000000}, description {"minimum port number used for rtsp internal transport"}}; 56 | attribute max_port { 57 | this, "max_port", 65000, range {0, 1000000}, description {"minimum port number used for rtsp internal transport"}}; 58 | 59 | attribute use_rtsp {this, 60 | "use_rtsp", 61 | true, 62 | description {"if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored)."}}; 63 | 64 | // attribute play {this, "play", true}; 65 | attribute channels {this, "channels", 1, range {1, 16}}; 66 | inlet<> input {this, "(int) toggle subscription"}; 67 | outlet<> m_output {this, "(multichannelsignal) received output", "multichannelsignal"}; 68 | outlet<> latency {this, "(double) current latency"}; 69 | 70 | // post to max window == but only when the class is loaded the first time 71 | // message<> maxclass_setup {this, "maxclass_setup"}; 72 | message<> toggle {this, "int", "toggle play and pause", MIN_FUNCTION {int num = args[0]; 73 | // bool state = num > 0; 74 | // bool changed = state != play; 75 | // if (changed && state) { 76 | // resetReceiver(frame_size); 77 | // } 78 | // if (changed && !state) { 79 | // rtpreceiver->pause(); 80 | // } 81 | // play = state; 82 | return {}; 83 | } 84 | } 85 | ; 86 | message<> setup {this, "maxclass_setup", MIN_FUNCTION {auto thisclass = args[0]; 87 | c74::max::class_addmethod(thisclass, (c74::max::method)setOutChans, "multichanneloutputs", c74::max::A_CANT, 0); 88 | c74::max::class_addmethod(thisclass, (c74::max::method)setDspState, "dspstate", c74::max::A_CANT, 0); 89 | return {}; 90 | } 91 | } 92 | ; 93 | message<> dspsetup {this, "dspsetup", MIN_FUNCTION {double newvecsize = args[1]; 94 | resetReceiver(newvecsize, channels); 95 | return {}; 96 | } 97 | } 98 | ; 99 | message<> getlatency {this, "getlatency", MIN_FUNCTION {if (rtpreceiver != nullptr) {latency.send(rtpreceiver->getLatency()); 100 | } 101 | return {}; 102 | } 103 | , description { 104 | "Outputs a packet latency in milliseconds at second outlet." 105 | } 106 | } 107 | ; 108 | 109 | static long setDspState(void* obj, long state) { 110 | c74::max::object_attr_setlong(obj, c74::max::gensym("active"), state); 111 | return state; 112 | } 113 | void operator()(audio_bundle input, audio_bundle output) { 114 | output.clear(); 115 | if (rtpreceiver == nullptr) { 116 | return; 117 | } 118 | if (!rtpreceiver->isConnected()) { 119 | return; 120 | } 121 | int chs = std::min(output.channel_count(), channels.get()); 122 | bool readres = rtpreceiver->readFromOutput(iarray); 123 | misscount.second++; 124 | if (!readres) { 125 | misscount.first++; 126 | cout << "stream underflow detected! " << std::to_string(misscount.first) << "/" << std::to_string(misscount.second) << std::endl; 127 | return; 128 | } 129 | for (auto i = 0; i < output.frame_count(); i++) { 130 | for (auto channel = 0; channel < chs; channel++) { 131 | output.samples(channel)[i] = rtpsr::convertSampleToDouble(iarray.at(i * channels.get() + channel)); 132 | } 133 | } 134 | } 135 | 136 | private: 137 | std::shared_ptr rtpreceiver {nullptr}; 138 | std::vector iarray; 139 | std::pair misscount; 140 | double frame_size = vector_size(); 141 | 142 | void resetChannel(int channel) { 143 | resetReceiver(vector_size(), channels); 144 | } 145 | 146 | void resetReceiver(double newvecsize, int channel) { 147 | iarray.resize(newvecsize * channel); 148 | frame_size = newvecsize; 149 | misscount = {0, 0}; 150 | rtpsr::Url url {address.get(), port}; 151 | if (use_rtsp) { 152 | auto option = std::make_unique(url, samplerate(), channel, (int)newvecsize); 153 | setOptions(*static_cast(option.get())); 154 | resetReceiver(std::move(option)); 155 | } 156 | else { 157 | auto option = std::make_unique(url, samplerate(), channel, (int)newvecsize); 158 | setOptions(*static_cast(option.get())); 159 | resetReceiver(std::move(option)); 160 | } 161 | } 162 | void resetReceiver(std::unique_ptr s) { 163 | rtpreceiver.reset(); 164 | try { 165 | s->port_range = getPortRange(); 166 | rtpreceiver = std::make_unique(std::move(s), rtpsr::getCodecByName(codec.get()), ringbuf_framenum, cout); 167 | rtpreceiver->launchLoop(); 168 | } 169 | catch (std::exception& e) { 170 | std::cerr << e.what() << std::endl; 171 | } 172 | } 173 | void resetReceiver(std::unique_ptr s) { 174 | rtpreceiver.reset(); 175 | try { 176 | rtpreceiver = std::make_unique(std::move(s), rtpsr::getCodecByName(codec.get()), ringbuf_framenum, cout); 177 | rtpreceiver->launchLoop(); 178 | } 179 | catch (std::exception& e) { 180 | std::cerr << e.what() << std::endl; 181 | } 182 | } 183 | 184 | void setOptions(rtpsr::RtpOptionsBase& opt) { 185 | opt.reorder_queue_size = reorder_queue_size; 186 | opt.max_delay = max_delay; 187 | } 188 | std::pair getPortRange() { 189 | if (min_port > max_port) { 190 | min_port = max_port.get(); 191 | } 192 | return std::pair(min_port.get(), max_port.get()); 193 | } 194 | static long setOutChans(void* obj, long outletindex) { 195 | auto chs = c74::max::object_attr_getlong(obj, c74::max::gensym("channels")); 196 | return chs; 197 | } 198 | } 199 | ; 200 | 201 | MIN_EXTERNAL(rtpreceive_tilde); 202 | -------------------------------------------------------------------------------- /source/projects/mc.rtpreceive_tilde/mc.rtpreceive_tilde_test.cmake: -------------------------------------------------------------------------------- 1 | ## copied and modified target_include_directories from min-api/test/min_object_unittst.cmake 2 | 3 | # Copyright 2018 The Min-API Authors. All rights reserved. 4 | # Use of this source code is governed by the MIT License found in the License.md file. 5 | 6 | cmake_minimum_required(VERSION 3.10) 7 | 8 | set(ORIGINAL_NAME "${PROJECT_NAME}") 9 | set(TEST_NAME "${PROJECT_NAME}_test") 10 | #project(${PROJECT_NAME}_test) 11 | 12 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.cpp") 13 | 14 | enable_testing() 15 | 16 | include_directories( 17 | "${C74_INCLUDES}" 18 | "${C74_MIN_API_DIR}/test" 19 | 20 | # "${C74_MIN_API_DIR}/test/mock" 21 | ) 22 | 23 | set(TEST_SOURCE_FILES "") 24 | FOREACH(SOURCE_FILE ${SOURCE_FILES}) 25 | set(ORIGINAL_WITH_EXT "${ORIGINAL_NAME}.cpp") 26 | if (SOURCE_FILE STREQUAL ORIGINAL_WITH_EXT) 27 | set(TEST_SOURCE_FILES ${TEST_SOURCE_FILES} ${TEST_NAME}.cpp) 28 | else() 29 | set(TEST_SOURCE_FILES "${TEST_SOURCE_FILES}" ${SOURCE_FILE}) 30 | endif() 31 | ENDFOREACH() 32 | 33 | # set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage") 34 | # set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage") 35 | # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") 36 | 37 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../../tests") 38 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 39 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 40 | 41 | add_executable(${TEST_NAME} ${TEST_NAME}.cpp ${TEST_SOURCE_FILES}) 42 | 43 | if (NOT TARGET mock_kernel) 44 | set(C74_MOCK_TARGET_DIR "${C74_MIN_API_DIR}/test") 45 | add_subdirectory(${C74_MIN_API_DIR}/test/mock ${CMAKE_BINARY_DIR}/mock) 46 | endif () 47 | 48 | add_dependencies(${TEST_NAME} mock_kernel) 49 | 50 | target_compile_definitions(${TEST_NAME} PUBLIC -DMIN_TEST) 51 | 52 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD 17) 53 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) 54 | target_include_directories(${TEST_NAME} PUBLIC 55 | ${C74_INCLUDES} 56 | ${C74_MIN_API_DIR}/test 57 | ${CMAKE_CURRENT_SOURCE_DIR}/../../rtpsendreceive_lib 58 | ${FFMPEG_INCLUDE_DIRS} 59 | ) 60 | target_link_libraries(${TEST_NAME} PUBLIC "mock_kernel" rtpsendreceive) 61 | 62 | 63 | if (APPLE) 64 | set_target_properties(${TEST_NAME} PROPERTIES LINK_FLAGS "-Wl,-F'${MAX_SDK_JIT_INCLUDES}', -weak_framework JitterAPI") 65 | endif () 66 | if (WIN32) 67 | set_target_properties(${TEST_NAME} PROPERTIES COMPILE_PDB_NAME ${TEST_NAME}) 68 | 69 | # target_link_libraries(${TEST_NAME} ${MaxAPI_LIB}) 70 | # target_link_libraries(${TEST_NAME} ${MaxAudio_LIB}) 71 | # target_link_libraries(${TEST_NAME} ${Jitter_LIB}) 72 | endif () 73 | 74 | add_test(NAME ${TEST_NAME} 75 | COMMAND ${TEST_NAME}) 76 | 77 | endif () -------------------------------------------------------------------------------- /source/projects/mc.rtpreceive_tilde/mc.rtpreceive_tilde_test.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @ingroup minexamples 3 | /// @copyright Copyright 2018 The Min-DevKit Authors. All rights 4 | /// reserved. 5 | /// @license Use of this source code is governed by the MIT License 6 | /// found in the License.md file. 7 | #define CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 8 | #include "c74_min_unittest.h" // required unit test header 9 | 10 | #include "mc.rtpreceive_tilde.cpp" // need the source of our object so that we can access it 11 | 12 | //Instantiation of mc.rtpreceive~ requires calling Max API directly, which is not available in min-unittest framework currently 13 | 14 | // SCENARIO("object produces correct output") { 15 | // ext_main(nullptr); // every unit test must call ext_main() once to configure 16 | // // the class 17 | 18 | // GIVEN("An instance of our object") { 19 | // test_wrapper an_instance; 20 | // rtpreceive_tilde& my_object = an_instance; 21 | // // check that default attr values are correct 22 | // // REQUIRE((my_object.greeting == symbol("hello world"))); 23 | 24 | // // now proceed to testing various sequences of events 25 | // WHEN("toggle 1 is received") { 26 | // my_object.toggle(1); 27 | 28 | // THEN("internal play flag is activated") { 29 | // c74::max::t_atom_long attr = 30 | // c74::max::object_attr_getlong(my_object.maxobj(), symbol("play")); 31 | // REQUIRE(attr == 1); 32 | // } 33 | // } 34 | // } 35 | // } -------------------------------------------------------------------------------- /source/projects/mc.rtpsend_tilde/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Min-DevKit Authors. All rights reserved. 2 | # Use of this source code is governed by the MIT License found in the License.md file. 3 | 4 | cmake_minimum_required(VERSION 3.0) 5 | 6 | set(C74_MIN_API_DIR ${CMAKE_SOURCE_DIR}/source/min-api) 7 | include(${C74_MIN_API_DIR}/script/min-pretarget.cmake) 8 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) 9 | 10 | 11 | ############################################################# 12 | # MAX EXTERNAL 13 | ############################################################# 14 | 15 | 16 | set( SOURCE_FILES 17 | ${PROJECT_NAME}.cpp 18 | ) 19 | # set(RTPSRLIB ${}) 20 | add_library( 21 | ${PROJECT_NAME} 22 | MODULE 23 | ${SOURCE_FILES} 24 | ) 25 | 26 | set(RTPSR_LIB "rtpsendreceive") 27 | 28 | target_include_directories(${PROJECT_NAME} PUBLIC 29 | ${C74_INCLUDES} 30 | ${CMAKE_CURRENT_SOURCE_DIR}/../../rtpsendreceive_lib 31 | ) 32 | 33 | message(STATUS testlib!${RTPSR_LIB}) 34 | target_link_libraries(${PROJECT_NAME} PUBLIC ${RTPSR_LIB} ${FFMPEG_LIBRARIES}) 35 | 36 | include(${C74_MIN_API_DIR}/script/min-posttarget.cmake) 37 | message(STATUS ${CMAKE_SOURCE_DIR}/externals/mc.rtpsend~.mxo) 38 | execute_process(COMMAND ln -s ${CMAKE_SOURCE_DIR}/externals/mc.rtpsend~.mxo ${CMAKE_SOURCE_DIR}/test_patch/) 39 | 40 | ############################################################# 41 | # UNIT TEST 42 | ############################################################# 43 | 44 | include(mc.rtpsend_tilde_test.cmake) 45 | -------------------------------------------------------------------------------- /source/projects/mc.rtpsend_tilde/mc.rtpsend_tilde.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @ingroup rtpsendreceive 3 | /// @copyright Copyright 2020 Tomoya Matsuura. All rights reserved. 4 | /// @license Use of this source code is governed by the LGPL License 5 | /// found in the License.md file. 6 | 7 | 8 | #include "c74_max.h" 9 | #include "c74_min.h" 10 | #include "rtpsender.hpp" 11 | 12 | using namespace c74::min; 13 | 14 | class rtpsend_tilde : public object, public mc_operator<> { 15 | bool m_initialized {false}; 16 | 17 | public: 18 | MIN_DESCRIPTION {"send audio stream via rtp protocol"}; 19 | MIN_TAGS {"Audio"}; 20 | MIN_AUTHOR {"Tomoya Matsuura"}; 21 | MIN_RELATED {"mc.rtpreceive_tilde"}; 22 | rtpsend_tilde(const atoms& args = {}) { 23 | instance_count += 1; 24 | m_initialized = true; 25 | } 26 | ~rtpsend_tilde() { 27 | instance_count -= 1; 28 | } 29 | attribute channels {this, "channels", 1, range {1, 16}}; 30 | attribute address {this, "address", "127.0.0.1", description {"Destination IP Address(can be a domain name)"}}; 31 | attribute port {this, "port", 30000, description {"Destination Main Port"}}; 32 | attribute codec {this, "codec", "pcm_s16be", setter {MIN_FUNCTION {auto c = rtpsr::getCodecByName(args[0]); 33 | if (c == rtpsr::Codec::INVALID) { 34 | cerr << "Invalid Codec Name. Using pcm_s16be" << endl; 35 | c = rtpsr::Codec::PCM_s16BE; 36 | } 37 | return args; 38 | } 39 | } 40 | } 41 | ; 42 | attribute ringbuf_framenum { 43 | this, "ringbuf_framenum", 4, range {1, 1000}, description {"Size of an internal ring buffer (multiplied with signal vector size.)"}}; 44 | attribute retry_rate {this, "retry_rate", 500.0, description {"Retry intervals in milliseconds at connection"}}; 45 | attribute reorder_queue_size {this, "reorder_queue_size", 500.0, description {"number of packets for reorder queue"}}; 46 | attribute use_rtsp {this, 47 | "use_rtsp", 48 | true, 49 | description {"if set to false, use raw rtp protocol instead of using rtsp mux/demuxer(Some options are ignored)."}}; 50 | 51 | attribute min_port { 52 | this, "min_port", 5000, range {0, 1000000}, description {"minimum port number used for rtsp internal transport"}}; 53 | attribute max_port { 54 | this, "max_port", 65000, range {0, 1000000}, description {"minimum port number used for rtsp internal transport"}}; 55 | 56 | 57 | attribute active {this, "active", 0, setter {MIN_FUNCTION {int res = args[0]; 58 | if (m_initialized && rtpsender != nullptr) { 59 | if (res <= 0) { 60 | rtpsender->init_asyncloop.halt(); 61 | rtpsender->asynclooper.halt(); 62 | } 63 | else { 64 | rtpsender->launchLoop(); 65 | } 66 | } 67 | return {}; 68 | } 69 | } 70 | } 71 | ; 72 | 73 | inlet<> m_inlet {this, "(multichannelsignal) input to be transmited"}; 74 | 75 | // static long inputChanged(rtpsend_tilde* s, long index, long count) { 76 | // if (count != s->channels) { 77 | // s->channels = count; 78 | // s->resetSender(); 79 | // return true; 80 | // } else 81 | // return false; 82 | // } 83 | message<> maxclass_setup {this, "maxclass_setup", MIN_FUNCTION {c74::max::t_class* c = args[0]; 84 | c74::max::class_addmethod(c, (c74::max::method)setDspState, "dspstate", c74::max::A_CANT, 0); 85 | return {}; 86 | } 87 | } 88 | ; 89 | message<> setup {this, "setup", MIN_FUNCTION { 90 | // double newvecsize = args[1]; 91 | return {}; 92 | } 93 | } 94 | ; 95 | 96 | message<> dspsetup {this, "dspsetup", MIN_FUNCTION {double newvecsize = args[1]; 97 | if (m_initialized) { 98 | resetSender(newvecsize); 99 | } 100 | return {}; 101 | } 102 | } 103 | ; 104 | 105 | void operator()(audio_bundle input, audio_bundle output) { 106 | 107 | if (rtpsender == nullptr) { 108 | return; 109 | } 110 | if (!rtpsender->isConnected()) { 111 | return; 112 | } 113 | int chs = std::min(input.channel_count(), channels.get()); 114 | for (auto i = 0; i < input.frame_count(); i++) { 115 | for (auto channel = 0; channel < chs; channel++) { 116 | iarray.at(i * chs + channel) = rtpsr::convertDoubleToSample(input.samples(channel)[i]); 117 | } 118 | } 119 | bool write_success = rtpsender->writeToInput(iarray); 120 | if (!write_success) { 121 | // cerr << "ring buffer is full!" << std::endl; 122 | } 123 | } 124 | static long setDspState(void* obj, long state) { 125 | c74::max::object_attr_setlong(obj, c74::max::gensym("active"), state); 126 | return state; 127 | } 128 | 129 | private: 130 | void resetSender(double newvecsize) { 131 | iarray.resize((int)newvecsize * channels); 132 | symbol c = codec; 133 | try { 134 | 135 | if (use_rtsp) { 136 | auto option = std::make_unique(rtpsr::Url {address.get(), port}, samplerate(), channels, (int)newvecsize); 137 | option->port_range = getPortRange(); 138 | setOptions(*static_cast(option.get())); 139 | 140 | rtpsender = std::make_unique(std::move(option), 141 | rtpsr::getCodecByName(c), 142 | std::chrono::milliseconds((long long)retry_rate.get()), 143 | ringbuf_framenum, 144 | cout); 145 | } 146 | else { 147 | auto option = std::make_unique(rtpsr::Url {address.get(), port}, samplerate(), channels, (int)newvecsize); 148 | setOptions(*static_cast(option.get())); 149 | rtpsender = std::make_unique(std::move(option), 150 | rtpsr::getCodecByName(c), 151 | std::chrono::milliseconds((long long)retry_rate.get()), 152 | ringbuf_framenum, 153 | cout); 154 | } 155 | } 156 | catch (std::exception& e) { 157 | std::cerr << e.what() << std::endl; 158 | } 159 | } 160 | void setOptions(rtpsr::RtpOptionsBase& opt) { 161 | opt.reorder_queue_size = reorder_queue_size; 162 | } 163 | std::pair getPortRange() { 164 | if (min_port > max_port) { 165 | min_port = max_port.get(); 166 | } 167 | return std::pair(min_port.get(), max_port.get()); 168 | } 169 | 170 | std::unique_ptr rtpsender; 171 | std::vector iarray; 172 | static int instance_count; 173 | } 174 | ; 175 | int rtpsend_tilde::instance_count = 0; 176 | MIN_EXTERNAL(rtpsend_tilde); 177 | -------------------------------------------------------------------------------- /source/projects/mc.rtpsend_tilde/mc.rtpsend_tilde_test.cmake: -------------------------------------------------------------------------------- 1 | ## copied and modified target_include_directories from min-api/test/min_object_unittst.cmake 2 | 3 | # Copyright 2018 The Min-API Authors. All rights reserved. 4 | # Use of this source code is governed by the MIT License found in the License.md file. 5 | 6 | cmake_minimum_required(VERSION 3.10) 7 | 8 | set(ORIGINAL_NAME "${PROJECT_NAME}") 9 | set(TEST_NAME "${PROJECT_NAME}_test") 10 | #project(${PROJECT_NAME}_test) 11 | 12 | if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.cpp") 13 | 14 | enable_testing() 15 | 16 | include_directories( 17 | "${C74_INCLUDES}" 18 | "${C74_MIN_API_DIR}/test" 19 | 20 | # "${C74_MIN_API_DIR}/test/mock" 21 | ) 22 | 23 | set(TEST_SOURCE_FILES "") 24 | FOREACH(SOURCE_FILE ${SOURCE_FILES}) 25 | set(ORIGINAL_WITH_EXT "${ORIGINAL_NAME}.cpp") 26 | if (SOURCE_FILE STREQUAL ORIGINAL_WITH_EXT) 27 | set(TEST_SOURCE_FILES ${TEST_SOURCE_FILES} ${TEST_NAME}.cpp) 28 | else() 29 | set(TEST_SOURCE_FILES "${TEST_SOURCE_FILES}" ${SOURCE_FILE}) 30 | endif() 31 | ENDFOREACH() 32 | 33 | # set(CMAKE_CXX_FLAGS "-fprofile-arcs -ftest-coverage") 34 | # set(CMAKE_C_FLAGS "-fprofile-arcs -ftest-coverage") 35 | # set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") 36 | 37 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../../tests") 38 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 39 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}") 40 | 41 | add_executable(${TEST_NAME} ${TEST_NAME}.cpp ${TEST_SOURCE_FILES}) 42 | 43 | if (NOT TARGET mock_kernel) 44 | set(C74_MOCK_TARGET_DIR "${C74_MIN_API_DIR}/test") 45 | add_subdirectory(${C74_MIN_API_DIR}/test/mock ${CMAKE_BINARY_DIR}/mock) 46 | endif () 47 | 48 | add_dependencies(${TEST_NAME} mock_kernel) 49 | 50 | target_compile_definitions(${TEST_NAME} PUBLIC -DMIN_TEST) 51 | 52 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD 17) 53 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) 54 | target_include_directories(${TEST_NAME} PUBLIC 55 | ${C74_INCLUDES} 56 | ${C74_MIN_API_DIR}/test 57 | ${CMAKE_CURRENT_SOURCE_DIR}/../../rtpsendreceive_lib 58 | ${FFMPEG_INCLUDE_DIRS} 59 | ) 60 | target_link_libraries(${TEST_NAME} PUBLIC "mock_kernel" rtpsendreceive) 61 | 62 | if (APPLE) 63 | set_target_properties(${TEST_NAME} PROPERTIES LINK_FLAGS "-Wl,-F'${MAX_SDK_JIT_INCLUDES}', -weak_framework JitterAPI") 64 | endif () 65 | if (WIN32) 66 | set_target_properties(${TEST_NAME} PROPERTIES COMPILE_PDB_NAME ${TEST_NAME}) 67 | 68 | # target_link_libraries(${TEST_NAME} ${MaxAPI_LIB}) 69 | # target_link_libraries(${TEST_NAME} ${MaxAudio_LIB}) 70 | # target_link_libraries(${TEST_NAME} ${Jitter_LIB}) 71 | endif () 72 | 73 | add_test(NAME ${TEST_NAME} 74 | COMMAND ${TEST_NAME}) 75 | 76 | endif () -------------------------------------------------------------------------------- /source/projects/mc.rtpsend_tilde/mc.rtpsend_tilde_test.cpp: -------------------------------------------------------------------------------- 1 | /// @file 2 | /// @ingroup rtpsendreceive 3 | /// @copyright Copyright 2020 Tomoya Matsuura. All rights reserved. 4 | /// @license Use of this source code is governed by the LGPL License found in the License.md file. 5 | #define CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 6 | 7 | #include "c74_min_unittest.h" // required unit test header 8 | 9 | #include "mc.rtpsend_tilde.cpp" // need the source of our object so that we can access it 10 | 11 | // Unit tests are written using the Catch framework as described at 12 | // https://github.com/philsquared/Catch/blob/master/docs/tutorial.md 13 | 14 | SCENARIO("object produces correct output") { 15 | ext_main(nullptr); // every unit test must call ext_main() once to configure the class 16 | 17 | GIVEN("An instance of our object") { 18 | 19 | test_wrapper an_instance; 20 | rtpsend_tilde& my_object = an_instance; 21 | 22 | // check that default attr values are correct 23 | // REQUIRE((my_object.greeting == symbol("hello world"))); 24 | 25 | // now proceed to testing various sequences of events 26 | // WHEN("a 'bang' is received") { 27 | // my_object.bang(); 28 | // THEN("our greeting is produced at the outlet") { 29 | // auto& output = *c74::max::object_getoutput(my_object, 0); 30 | // REQUIRE((output.size() == 1)); 31 | // REQUIRE((output[0].size() == 1)); 32 | // REQUIRE((output[0][0] == symbol("hello world"))); 33 | // } 34 | // } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Min-DevKit Authors. All rights reserved. 2 | # Use of this source code is governed by the MIT License found in the License.md file. 3 | 4 | cmake_minimum_required(VERSION 3.0) 5 | 6 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) 7 | 8 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 9 | set(CMAKE_MACOSX_RPATH 1) 10 | endif() 11 | 12 | message("test!!! ${DEPENDENT_LIBS}") 13 | 14 | 15 | add_library( 16 | rtpsendreceive 17 | STATIC 18 | rtpsrbase.cpp rtpsender.cpp rtpreceiver.cpp 19 | ) 20 | target_include_directories(rtpsendreceive 21 | PUBLIC 22 | ${FFMPEG_INCLUDE_DIRS}) 23 | 24 | target_compile_features(rtpsendreceive PUBLIC cxx_std_17) 25 | target_link_libraries(rtpsendreceive PUBLIC ${DEPENDENT_LIBS}) 26 | 27 | include(rtpsendreceive_lib_test.cmake) 28 | -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/lockfree_ringbuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | class LockFreeRingbuf { 8 | public: 9 | LockFreeRingbuf() = delete; 10 | explicit LockFreeRingbuf(int size) 11 | : buffer(size, 0) { } 12 | void resize(int size) { 13 | buffer.resize(size); 14 | std::fill(buffer.begin(), buffer.end(), 0); 15 | } 16 | bool read(T& res_ref) { 17 | if (read_index == write_index) { 18 | return false; 19 | } 20 | res_ref = buffer[read_index]; 21 | read_index = stepIndex(read_index, 1); 22 | return true; 23 | } 24 | bool write(T elem) { 25 | write_index = stepIndex(write_index, 1); 26 | if (read_index == write_index) { 27 | return false; 28 | } 29 | buffer[write_index] = elem; 30 | return true; 31 | } 32 | // read from read_index to specified size. note that size should be length of 33 | // array, not a byte size 34 | bool readRange(std::vector& res_array, size_t size) { 35 | if (getReadMargin() <= size) { 36 | return false; 37 | } 38 | size_t localread = read_index; 39 | size_t target_index = localread + size; 40 | size_t target_index_ringed = (target_index % buffer.size()); 41 | if (target_index > buffer.size()) { 42 | auto&& iter = std::copy(buffer.begin() + localread, buffer.end(), res_array.begin()); 43 | std::copy(buffer.begin(), buffer.begin() + target_index_ringed, iter); 44 | } 45 | else { 46 | std::copy_n(buffer.begin() + localread, size, res_array.begin()); 47 | } 48 | read_index = target_index_ringed; 49 | return true; 50 | } 51 | bool writeRange(const std::vector& src_array, size_t size) { 52 | if (getWriteMargin() <= size) { 53 | return false; 54 | } 55 | size_t localwrite = write_index; 56 | size_t target_index = localwrite + size; 57 | size_t target_index_ringed = (target_index % buffer.size()); 58 | if (target_index > buffer.size()) { 59 | size_t writetoend = (buffer.size() - localwrite); 60 | auto&& iter = std::copy_n(src_array.begin(), buffer.size() - localwrite, buffer.begin() + localwrite); 61 | std::copy_n(src_array.begin() + writetoend, target_index_ringed, buffer.begin()); 62 | } 63 | else { 64 | std::copy_n(src_array.begin(), size, buffer.begin() + localwrite); 65 | } 66 | write_index = target_index_ringed; 67 | return true; 68 | } 69 | int getReadMargin() { 70 | size_t localread = read_index; 71 | size_t localwrite = write_index; 72 | 73 | auto margin = localwrite - localread; 74 | if (margin < 0) { 75 | margin = buffer.size() - (localread - localwrite); 76 | } 77 | return margin; 78 | } 79 | int getWriteMargin() { 80 | return buffer.size() - getReadMargin(); 81 | } 82 | 83 | private: 84 | std::atomic write_index = 0; 85 | std::atomic read_index = 0; 86 | std::vector buffer; 87 | size_t stepIndex(size_t i, int increment) { 88 | return (i + increment) % buffer.size(); 89 | } 90 | }; -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/lockfree_ringbuffer_test.hpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 8 | #include "lockfree_ringbuffer.hpp" 9 | #include // required unit test header 10 | TEST_CASE("Lockfree Ringbuffer") { 11 | const int size = 512; 12 | LockFreeRingbuf ringbuf(size); 13 | 14 | const int try_size = 2000; 15 | 16 | constexpr int chunk_write = 30; 17 | constexpr int chunk_read = 30; 18 | std::vector reference; 19 | std::vector answer; 20 | std::thread th1 {[&]() { 21 | std::vector src(chunk_write, 0); 22 | int counter = 0; 23 | while (true) { 24 | for (int i = 0; i < chunk_write; i++) { 25 | int16_t num = counter; 26 | reference.push_back(num); 27 | src.at(i) = num; 28 | counter++; 29 | } 30 | while (true) { 31 | bool writeres = ringbuf.writeRange(src, chunk_write); 32 | if (writeres) { 33 | break; 34 | } 35 | } 36 | if (counter > try_size + chunk_read) { 37 | break; 38 | } 39 | } 40 | return true; 41 | }}; 42 | std::thread th2 {[&]() { 43 | std::vector src(chunk_read, 0); 44 | int counter = 0; 45 | while (true) { 46 | bool readres = ringbuf.readRange(src, chunk_read); 47 | if (readres) { 48 | answer.insert(answer.end(), src.begin(), src.end()); 49 | counter += chunk_read; 50 | if (counter > try_size) { 51 | break; 52 | } 53 | } 54 | } 55 | return true; 56 | }}; 57 | th1.join(); 58 | th2.join(); 59 | reference.resize(try_size); 60 | answer.resize(try_size); 61 | std::stringstream strstr; 62 | strstr << "different element: "; 63 | for (int i = 0; i < try_size; i++) { 64 | if (reference[i] != answer[i]) { 65 | strstr << i << ", "; 66 | } 67 | } 68 | strstr << std::endl; 69 | std::cerr << strstr.str() << std::endl; 70 | bool res = reference == answer; 71 | REQUIRE(res); 72 | } -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/min_debugutils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef DEBUG 4 | #define DEBUG_LOG(STRING) cout << #STRING << std::endl; 5 | else 6 | #define DEBUG_LOG(STRING) 7 | #endif -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpreceiver.cpp: -------------------------------------------------------------------------------- 1 | #include "rtpreceiver.hpp" 2 | 3 | namespace rtpsr { 4 | RtpReceiver::RtpReceiver(std::unique_ptr s, Url const& url, Codec codec, double ringbuf_multiple, std::ostream& logger) 5 | : RtpSRBase(*s, logger) { 6 | auto option = std::make_unique(url, setting_ref.samplerate, setting_ref.channels, setting_ref.framesize); 7 | input = std::make_unique(std::move(option)); 8 | output = std::make_unique(setting_ref, frame->nb_samples * ringbuf_multiple); 9 | this->codec = std::make_unique(setting_ref, codec); 10 | init(); 11 | } 12 | RtpReceiver::RtpReceiver(std::unique_ptr s, Codec codec, double ringbuf_multiple, std::ostream& logger) 13 | : RtpSRBase(*s, logger) { 14 | input = std::make_unique(std::move(s)); 15 | output = std::make_unique(setting_ref, frame->nb_samples * ringbuf_multiple); 16 | this->codec = std::make_unique(setting_ref, codec); 17 | init(); 18 | } 19 | RtpReceiver::RtpReceiver(std::unique_ptr s, Codec codec, double ringbuf_multiple, std::ostream& logger) 20 | : RtpSRBase(*s, logger) { 21 | input = std::make_unique(std::move(s)); 22 | time_cache = std::chrono::high_resolution_clock::now(); 23 | input->ctx->interrupt_callback = AVIOInterruptCB {&RtpReceiver::timeoutCallback, this}; 24 | output = std::make_unique(setting_ref, frame->nb_samples * ringbuf_multiple); 25 | this->codec = std::make_unique(setting_ref, codec); 26 | init(); 27 | } 28 | int RtpReceiver::timeoutCallback(void* opaque) { 29 | auto receiver = reinterpret_cast(opaque); 30 | auto dur = std::chrono::high_resolution_clock::now() - receiver->time_cache; 31 | if (dur > std::chrono::seconds(2)) { 32 | return 1; 33 | } 34 | return 0; 35 | } 36 | 37 | RtpReceiver::~RtpReceiver() { 38 | std::cerr << "rtpreceiver destructor called" << std::endl; 39 | bool res1 = init_asyncloop.halt(); 40 | auto status = asynclooper.wait_for(10000); 41 | if (res1) { 42 | avformat_close_input(&input->ctx); 43 | } 44 | } 45 | 46 | void RtpReceiver::init() { 47 | 48 | auto* rtpinput = dynamic_cast(input.get()); 49 | tmpbuf.resize(frame->nb_samples * setting_ref.channels * 2); 50 | dtosbuffer.resize(frame->nb_samples * setting_ref.channels); 51 | input->ctx->max_delay = rtpinput->option->max_delay; 52 | init_asyncloop.launch([&]() { 53 | logger << "rtpreceiver waiting incoming connection..." << std::endl; 54 | while (init_asyncloop.isActive()) { 55 | // this start blocking... 56 | bool connection_res = dynamic_cast(input.get())->tryConnectInput(); 57 | if (connection_res) { 58 | initStream(); 59 | logger << "rtpreceiver connected" << std::endl; 60 | break; 61 | } 62 | } 63 | isconnected = true; 64 | return true; 65 | }); 66 | } 67 | // return value:stopped_index; 68 | bool RtpReceiver::pushToOutput() { 69 | if (frame == nullptr) { 70 | return false; 71 | } 72 | auto* asyncoutput = dynamic_cast(output.get()); 73 | auto* frameref = av_frame_get_plane_buffer(frame, 0); 74 | auto size = frame->nb_samples * frame->channels; 75 | tmpbuf.resize(size); 76 | std::memcpy(tmpbuf.data(), frameref->data, size * sizeof(int16_t)); 77 | bool res = asyncoutput->tryPushRingBuffer(tmpbuf); 78 | av_packet_unref(packet); 79 | return res; 80 | } 81 | bool RtpReceiver::readFromOutput(std::vector& dest) { 82 | auto* asyncoutput = dynamic_cast(output.get()); 83 | return asyncoutput->tryPopRingBuffer(dest); 84 | } 85 | bool RtpReceiver::readFromOutput(std::vector& dest) { 86 | assert(dtosbuffer.size() == dest.size()); 87 | int count = 0; 88 | std::generate(dtosbuffer.begin(), dtosbuffer.end(), [&]() { return rtpsr::convertDoubleToSample(dest.at(count++)); }); 89 | return readFromOutput(dtosbuffer); 90 | } 91 | 92 | bool RtpReceiver::receiveData() { 93 | time_cache = std::chrono::high_resolution_clock::now(); 94 | int res = av_read_frame(input->ctx, packet); 95 | if (res == AVERROR(ETIMEDOUT)) { 96 | logger << avErrorString(res); 97 | return false; // timeout or eof(ignore) 98 | } 99 | if (res == AVERROR_EOF) { 100 | logger << avErrorString(res); 101 | return false; // timeout or eof(ignore) 102 | } 103 | checkAvError(res); 104 | return true; 105 | } 106 | void RtpReceiver::launchLoop() { 107 | asynclooper.launch([&]() { 108 | auto* decoder = dynamic_cast(codec.get()); 109 | bool res = true; 110 | if (input->ctx == nullptr) { 111 | return false; 112 | } 113 | try { 114 | init_asyncloop.wait(); 115 | timecount = 0; 116 | while (asynclooper.isActive()) { 117 | bool isdecoderfull = false; 118 | // block until data comes 119 | res = receiveData(); 120 | isdecoderfull = decoder->sendPacket(packet); 121 | while (true) { 122 | bool isempty = decoder->receiveFrame(frame); 123 | if (isempty) { 124 | break; 125 | } 126 | while (true) { 127 | bool writeres = pushToOutput(); 128 | if (!writeres) { 129 | std::cerr << "receiver ring buffer is full!!" << std::endl; 130 | std::this_thread::sleep_for(std::chrono::milliseconds(20)); 131 | } 132 | else { 133 | // logger << "receiver pts:" << frame->pts << " |duration : " << frame->nb_samples <<" |latency :" << 134 | // timecount-frame->pts << std::endl; 135 | double newlatency = timecount - frame->pts; 136 | if (newlatency != latency_cache) { 137 | latency.store(1000.0f * newlatency / setting_ref.samplerate); 138 | } 139 | latency_cache = newlatency; 140 | timecount += frame->nb_samples; 141 | break; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | catch (std::exception& e) { 148 | std::cerr << "Error happend in receive polling loop:" << e.what() << std::endl; 149 | return false; 150 | } 151 | return true; 152 | }); 153 | } 154 | bool RtpReceiver::isConnected() { 155 | return isconnected.load(); 156 | } 157 | } // namespace rtpsr -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpreceiver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rtpsrbase.hpp" 3 | 4 | namespace rtpsr { 5 | struct RtpReceiver : public RtpSRBase { 6 | explicit RtpReceiver(std::unique_ptr s, Url const& url, Codec codec, double ringbuf_multiple=4.2,std::ostream& logger = std::cerr); 7 | explicit RtpReceiver(std::unique_ptr s, Codec codec,double ringbuf_multiple=4.2, std::ostream& logger = std::cerr); 8 | explicit RtpReceiver(std::unique_ptr s, Codec codec, double ringbuf_multiple=4.2,std::ostream& logger = std::cerr); 9 | 10 | ~RtpReceiver(); 11 | 12 | void launchLoop() override; 13 | bool readFromOutput(std::vector& dest); 14 | bool readFromOutput(std::vector& dest); 15 | static int timeoutCallback(void * opaque); 16 | bool isConnected() override; 17 | double getLatency() const {return latency.load();}; 18 | std::chrono::high_resolution_clock::time_point time_cache; 19 | private: 20 | std::vector dtosbuffer; 21 | std::vector tmpbuf; 22 | int64_t timecount; 23 | std::atomic latency; 24 | double latency_cache; 25 | void init(); 26 | bool pushToOutput(); 27 | bool receiveData(); 28 | }; 29 | } // namespace rtpsr 30 | -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsender.cpp: -------------------------------------------------------------------------------- 1 | #include "rtpsender.hpp" 2 | 3 | namespace rtpsr { 4 | RtpSender::RtpSender(std::unique_ptr s, Url const& url, Codec codec, std::chrono::milliseconds init_retry_rate, 5 | double ringbuf_multiple, std::ostream& logger) 6 | : RtpSRBase(*s, logger) 7 | , init_retry_rate(init_retry_rate) 8 | , pollingrate(duration_type(static_cast(s->framesize) * 0.5 * 48000 / s->samplerate)) { 9 | input = std::make_unique(setting_ref, setting_ref.framesize * ringbuf_multiple); 10 | auto option = std::make_unique(url, setting_ref.samplerate, setting_ref.channels, setting_ref.framesize); 11 | output = std::make_unique(std::move(option)); 12 | this->codec = std::make_unique(*s, codec); 13 | init(); 14 | } 15 | RtpSender::RtpSender(std::unique_ptr option, Codec codec, std::chrono::milliseconds init_retry_rate, 16 | double ringbuf_multiple, std::ostream& logger) 17 | : RtpSRBase(*static_cast(option.get()), logger) 18 | , init_retry_rate(init_retry_rate) { 19 | pollingrate = duration_type(static_cast(setting_ref.framesize) * 0.5 * 48000 / setting_ref.samplerate); 20 | input = std::make_unique(setting_ref, setting_ref.framesize * ringbuf_multiple); 21 | output = std::make_unique(std::move(option)); 22 | this->codec = std::make_unique(setting_ref, codec); 23 | init(); 24 | } 25 | 26 | RtpSender::RtpSender(std::unique_ptr option, Codec codec, std::chrono::milliseconds init_retry_rate, 27 | double ringbuf_multiple, std::ostream& logger) 28 | : RtpSRBase(*static_cast(option.get()), logger) 29 | , init_retry_rate(init_retry_rate) { 30 | pollingrate = duration_type(static_cast(setting_ref.framesize) * 0.5 * 48000 / setting_ref.samplerate); 31 | input = std::make_unique(setting_ref, setting_ref.framesize * ringbuf_multiple); 32 | output = std::make_unique(std::move(option)); 33 | this->codec = std::make_unique(setting_ref, codec); 34 | init(); 35 | } 36 | 37 | 38 | RtpSender::~RtpSender() { 39 | bool res1 = init_asyncloop.halt(); 40 | bool res2 = asynclooper.halt(); 41 | if (res1 && res2) { 42 | checkAvError(av_write_trailer(output->ctx)); 43 | 44 | if (output->ctx->pb != nullptr) { 45 | avio_close(output->ctx->pb); 46 | } 47 | avformat_close_input(&input->ctx); 48 | } 49 | } 50 | void RtpSender::init() { 51 | initStream(); 52 | dtosbuffer.resize(setting_ref.framesize * 2 * setting_ref.channels); 53 | init_asyncloop.launch([&]() { 54 | while (init_asyncloop.isActive()) { 55 | auto* rtpout = dynamic_cast(output.get()); 56 | try { 57 | bool res = rtpout->tryConnect(); 58 | if (res) { 59 | logger << "rtpsender: connected" << std::endl; 60 | break; 61 | } 62 | logger << "rtpsender: connection to " << rtpout->option->url.address << ":" << std::to_string(rtpout->option->url.port) 63 | << " refused,retry in " << std::to_string(init_retry_rate.count()) << "ms." << std::endl; 64 | std::this_thread::sleep_for(init_retry_rate); 65 | } 66 | catch (std::runtime_error& e) { 67 | throw e; 68 | break; 69 | } 70 | } 71 | isconnected = true; 72 | return true; // return result; 73 | }); 74 | } 75 | bool RtpSender::writeToInput(std::vector const& input) { 76 | auto* asyncinput = dynamic_cast(this->input.get()); 77 | return asyncinput->tryPushRingBuffer(input); 78 | } 79 | bool RtpSender::writeToInput(std::vector const& input) { 80 | assert(dtosbuffer.size() == input.size()); 81 | int count = 0; 82 | std::generate(dtosbuffer.begin(), dtosbuffer.end(), [&]() { return rtpsr::convertDoubleToSample(input.at(count++)); }); 83 | return writeToInput(dtosbuffer); 84 | } 85 | bool RtpSender::fillFrame() { 86 | auto* asyncinput = dynamic_cast(input.get()); 87 | size_t packet_framesize = frame->nb_samples * setting_ref.channels; 88 | 89 | framebuf.resize(packet_framesize); 90 | bool res = asyncinput->tryPopRingBuffer(framebuf); 91 | if (res) { 92 | checkAvError(avcodec_fill_audio_frame(frame, 93 | setting_ref.channels, 94 | AV_SAMPLE_FMT_S16, 95 | (uint8_t*)framebuf.data(), 96 | sizeof(sample_t) * packet_framesize, 97 | 0)); 98 | return true; 99 | } 100 | return false; 101 | } 102 | void RtpSender::sendData() { 103 | packet->pos = -1; 104 | packet->stream_index = 0; 105 | // packet->duration = setting_ref.framesize; 106 | auto itb = codec->ctx->time_base; 107 | auto otb = output->ctx->streams[0]->time_base; 108 | av_packet_rescale_ts(packet, otb, otb); // maybe unnecessary 109 | checkAvError(av_write_frame(output->ctx, packet)); 110 | } 111 | void RtpSender::launchLoop() { 112 | asynclooper.launch([&]() { 113 | try { 114 | while (true) { 115 | if (!asynclooper.isActive()) { 116 | return false; 117 | } 118 | auto res = init_asyncloop.wait_for(20); 119 | if (res == std::future_status::ready) { 120 | break; 121 | } 122 | } 123 | auto* encoder = dynamic_cast(codec.get()); 124 | frame->format = AV_SAMPLE_FMT_S16; 125 | frame->channels = setting_ref.channels; 126 | frame->channel_layout = codec->ctx->channel_layout; 127 | frame->nb_samples = setting_ref.framesize; 128 | 129 | while (asynclooper.isActive()) { 130 | bool hasinputframe = fillFrame(); 131 | if (!hasinputframe) { } 132 | else { 133 | frame->pts = timecount; 134 | timecount += setting_ref.framesize; 135 | bool isfull = encoder->sendFrame(frame); 136 | if (isfull) { 137 | logger << "sender encoder is full" << std::endl; 138 | } 139 | while (true) { 140 | bool isempty = encoder->receivePacket(packet); 141 | if (isempty) { 142 | av_packet_unref(packet); 143 | break; // frame is fully flushed, finish sending 144 | } 145 | sendData(); 146 | av_packet_unref(packet); 147 | } 148 | } 149 | 150 | 151 | std::this_thread::sleep_for(duration_type(64)); 152 | } 153 | } 154 | catch (std::exception& e) { 155 | std::cerr << "Error happend in polling loop:" << e.what() << std::endl; 156 | return false; 157 | } 158 | return true; 159 | }); 160 | } 161 | bool RtpSender::isConnected() { 162 | return isconnected.load(); 163 | } 164 | 165 | } // namespace rtpsr -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsender.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "rtpsrbase.hpp" 3 | 4 | namespace rtpsr { 5 | struct RtpSender : public RtpSRBase { 6 | explicit RtpSender(std::unique_ptr s, Url const& url, Codec codec, 7 | std::chrono::milliseconds init_retry_rate = std::chrono::milliseconds(init_retry_rate_int), double ringbuf_multiple = 4.2, 8 | std::ostream& logger = std::cerr); 9 | // launch with raw rtp version; 10 | explicit RtpSender(std::unique_ptr option, Codec codec, 11 | std::chrono::milliseconds init_retry_rate = std::chrono::milliseconds(init_retry_rate_int), double ringbuf_multiple = 4.2, 12 | std::ostream& logger = std::cerr); 13 | // launch with rtsp version 14 | explicit RtpSender(std::unique_ptr option, Codec codec, 15 | std::chrono::milliseconds init_retry_rate = std::chrono::milliseconds(init_retry_rate_int), double ringbuf_multiple = 4.2, 16 | std::ostream& logger = std::cerr); 17 | ~RtpSender(); 18 | 19 | void launchLoop() override; 20 | // communication entrypoint between max 21 | bool writeToInput(std::vector const& input); 22 | bool writeToInput(std::vector const& input); 23 | bool isConnected() override; 24 | 25 | private: 26 | int64_t timecount = 0; 27 | std::vector framebuf; 28 | 29 | // constructor must call init() 30 | void init(); 31 | bool fillFrame(); 32 | void sendData(); 33 | std::vector dtosbuffer; 34 | duration_type pollingrate; 35 | const std::chrono::milliseconds init_retry_rate; 36 | static constexpr int init_retry_rate_int = 500; 37 | }; 38 | 39 | } // namespace rtpsr -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsendreceive_lib.hpp: -------------------------------------------------------------------------------- 1 | // A simple ffmpeg wrapper to send/receive audio stream with rtp protocol 2 | // written in C++. 3 | // Copyright 2020 Tomoya Matsuura All rights Reserved. 4 | // This source code is released under LGPL lisence. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "libavutil/mem.h" 17 | 18 | #ifndef THREAD_SLEEP 19 | #define THREAD_SLEEP(s) std::this_thread::sleep_for(std::chrono::seconds(s)); 20 | #endif 21 | 22 | namespace rtpsr { 23 | using sample_t = int16_t; 24 | 25 | inline double convertSampleToDouble(sample_t s) { 26 | return ((double)s) / INT16_MAX; 27 | } 28 | inline sample_t convertDoubleToSample(double d) { 29 | return (int16_t)(d * INT16_MAX); 30 | } 31 | 32 | struct buffer_data { 33 | sample_t* ptr; 34 | size_t size; 35 | }; 36 | 37 | using buffertype = std::vector; 38 | 39 | using readfn_type = int (*)(void*, uint8_t*, int); 40 | using seekfn_type = int64_t (*)(void*, int64_t, int); 41 | 42 | using duration_type = std::chrono::duration>; 43 | 44 | enum class Codec { PCM_s16BE, OPUS, INVALID = -1 }; 45 | 46 | 47 | template 48 | class AvSmartPtr { 49 | public: 50 | AvSmartPtr() { 51 | size_t s = sizeof(T); 52 | ptr = av_malloc(s); 53 | } 54 | ~AvSmartPtr() { 55 | av_free(ptr); 56 | } 57 | // do not copy 58 | T operator=(AvSmartPtr i) = delete; 59 | 60 | T* get() { 61 | return reinterpret_cast(ptr); 62 | } 63 | T* operator->() { 64 | return get(); 65 | } 66 | T& operator*() { 67 | return *get(); 68 | } 69 | 70 | private: 71 | void* ptr; 72 | }; 73 | 74 | 75 | inline std::string getCodecName(Codec c) { 76 | switch (c) { 77 | case Codec::PCM_s16BE: 78 | return "pcm_s16be"; 79 | break; 80 | case Codec::OPUS: 81 | return "libopus"; 82 | break; 83 | default: 84 | return ""; 85 | } 86 | } 87 | inline size_t getCodecDataSize(Codec c) { 88 | if (c == Codec::PCM_s16BE) { 89 | return sizeof(int16_t); 90 | } 91 | return sizeof(double); 92 | } 93 | 94 | inline Codec getCodecByName(const std::string& name) { 95 | if (name == "pcm_s16be") { 96 | return Codec::PCM_s16BE; 97 | } 98 | if (name == "opus") { 99 | return Codec::OPUS; 100 | } 101 | return Codec::INVALID; 102 | } 103 | } // namespace rtpsr -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsendreceive_lib_test.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | set(TEST_NAME rtpsr_classes_test) 3 | # project(${PROJECT_NAME}) 4 | set(C74_MIN_API_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../min-api) 5 | include(${C74_MIN_API_DIR}/script/min-pretarget.cmake) 6 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "Minimum OS X deployment version" FORCE) 7 | enable_testing() 8 | 9 | include_directories( 10 | "${C74_INCLUDES}" 11 | "${C74_MIN_API_DIR}" 12 | "${C74_MIN_API_DIR}/test" 13 | # "${C74_MIN_API_DIR}/test/mock" 14 | ) 15 | 16 | 17 | add_executable(${TEST_NAME} ${TEST_NAME}.cpp ) 18 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD 17) 19 | set_property(TARGET ${TEST_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) 20 | target_compile_definitions( 21 | ${TEST_NAME} 22 | PUBLIC 23 | -DMIN_TEST 24 | ) 25 | 26 | target_link_libraries(${TEST_NAME} PUBLIC "mock_kernel" rtpsendreceive) 27 | 28 | add_test(NAME ${TEST_NAME} 29 | COMMAND ${TEST_NAME}) -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsr_classes_test.cpp: -------------------------------------------------------------------------------- 1 | // A simple ffmpeg wrapper to send/receive audio stream with rtp protocol 2 | // written in C++. 3 | // Copyright 2020 Tomoya Matsuura All rights Reserved. 4 | // This source code is released under LGPL 3.0 lisence. 5 | #include "rtpsender.hpp" 6 | #include "rtpreceiver.hpp" 7 | 8 | #include 9 | #define CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS 10 | #include "c74_min_unittest.h" // required unit test header 11 | 12 | TEST_CASE("RTSP loopback") { 13 | // av_log_set_level(AV_LOG_TRACE); 14 | auto setting_r = std::make_unique(rtpsr::RtpSRSetting{48000, 1, 128}); 15 | auto setting_s = std::make_unique(rtpsr::RtpSRSetting{48000, 1, 128}); 16 | 17 | rtpsr::Url url = {"127.0.0.1", 30000}; 18 | 19 | auto receiver = std::make_unique(std::move(setting_r), url, rtpsr::Codec::PCM_s16BE); 20 | auto sender = std::make_unique(std::move(setting_s), url, rtpsr::Codec::PCM_s16BE); 21 | 22 | std::vector ref; 23 | std::vector input; 24 | for (int i = 0; i < 128; i++) { 25 | int16_t s = std::rand() / RAND_MAX; 26 | auto ds = ((double)s) * 2 - 1.0; 27 | ref.emplace_back(ds); 28 | input.emplace_back(s); 29 | } 30 | sender->writeToInput(input); 31 | sender->launchLoop(); 32 | receiver->launchLoop(); 33 | std::vector answer_s(input.size(), 0); 34 | std::vector answer; 35 | while (true) { 36 | auto res = receiver->readFromOutput(answer_s); 37 | if (res) 38 | break; 39 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 40 | } 41 | sender.reset(); 42 | receiver.reset(); 43 | for (int i = 0; i < input.size(); i++) { 44 | answer.emplace_back(rtpsr::convertSampleToDouble(answer_s[i])); 45 | } 46 | 47 | REQUIRE(REQUIRE_VECTOR_APPROX(answer, ref) == true); 48 | } 49 | // TEST_CASE("RTSP Opus loopback") { 50 | // try { 51 | // // av_log_set_level(AV_LOG_TRACE); 52 | // rtpsr::RtpSRSetting setting = {48000, 1, 256}; 53 | // rtpsr::Url url = {"127.0.0.1", 30000}; 54 | 55 | // rtpsr::RtpReceiver receiver(setting, url, rtpsr::Codec::OPUS); 56 | // rtpsr::RtpSender sender(setting, url, rtpsr::Codec::OPUS); 57 | // std::vector ref; 58 | // for (int i = 0; i < 128; i++) { 59 | // auto r = ((double)std::rand() / RAND_MAX) * 2 - 1.0; 60 | // ref.emplace_back(r); 61 | // sender.getInput().setBuffer(r, i, 0); 62 | // } 63 | // sender.sendData(); 64 | // receiver.receiveData(); 65 | // std::vector answer; 66 | 67 | // for (int i = 0; i < 128; i++) { 68 | // answer.emplace_back(receiver.getOutput().readBuffer(i, 0)); 69 | // } 70 | 71 | // REQUIRE(REQUIRE_VECTOR_APPROX(answer, ref) == true); 72 | 73 | // // std::exit(EXIT_SUCCESS); 74 | // } catch (std::exception &err) { 75 | // std::cerr << err.what() << "\n"; 76 | // std::exit(EXIT_FAILURE); 77 | // } 78 | // } 79 | -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsrbase.cpp: -------------------------------------------------------------------------------- 1 | #include "rtpsrbase.hpp" 2 | 3 | 4 | #include 5 | 6 | namespace rtpsr { 7 | void checkAvError(int error_code) { 8 | if (error_code < 0) { 9 | throw std::runtime_error(avErrorString(error_code)); 10 | } 11 | } 12 | std::string avErrorString(int error_code) { 13 | if (error_code < 0) { 14 | std::string res("", 4096); 15 | av_make_error_string(res.data(), 4096, error_code); 16 | return res; 17 | } 18 | return ""; 19 | } 20 | AVOptionBase::AVOptionBase(container_t&& init) { 21 | for (auto& [key, val] : init) { 22 | assert(!std::holds_alternative(val)); 23 | if (std::holds_alternative(val)) { 24 | checkAvError(av_dict_set_int(&this->params, key.c_str(), std::get(val), 0)); 25 | allocated |= true; 26 | } 27 | if (std::holds_alternative(val)) { 28 | checkAvError(av_dict_set(&this->params, key.c_str(), std::get(val).c_str(), 0)); 29 | allocated |= true; 30 | } 31 | } 32 | container = std::move(init); 33 | } 34 | AVOptionBase::~AVOptionBase() { 35 | if (allocated) { 36 | av_dict_free(¶ms); 37 | } 38 | } 39 | std::string getSdpUrl(std::string const& address, int port) { 40 | return "udp://" + address + ":" + std::to_string(port) + "/live.sdp"; 41 | } 42 | std::string getSdpUrl(Url url) { 43 | return getSdpUrl(url.address, url.port); 44 | } 45 | RtpOptionsBase::RtpOptionsBase(Url url) 46 | : url(std::move(url)) { 47 | dict.emplace("protocol_whitelist", "file,udp,rtp,tcp,rtsp"); 48 | } 49 | void RtpOptionsBase::generateOptions() { 50 | dict.clear(); 51 | dict.emplace("reorder_queue_size", (int)reorder_queue_size); 52 | dict.emplace("packet_size", (int)packet_size); 53 | } 54 | RtpOption::RtpOption(Url url, double samplerate, int channels, int buffersize) 55 | : RtpOptionsBase(std::move(url)) 56 | , RtpSRSetting({samplerate, channels, buffersize}) { 57 | buffer_size = buffersize * channels * 4; 58 | } 59 | void RtpOption::generateOptions() { 60 | RtpOptionsBase::generateOptions(); 61 | dict.emplace("buffer_size", (int)buffer_size); 62 | // todo::filter_source; 63 | } 64 | 65 | RtspOption::RtspOption(Url url, double samplerate, int channels, int buffersize) 66 | : RtpOptionsBase(std::move(url)) 67 | , RtpSRSetting({samplerate, channels, buffersize}) { 68 | // dict.emplace("allowed_media_types",nullptr); 69 | dict.emplace("protocol_whitelist", "file,udp,rtp,tcp,rtsp"); 70 | } 71 | void RtspOption::generateOptions() { 72 | RtpOptionsBase::generateOptions(); 73 | dict.emplace("rtsp_transport", getProtocolString(rtsp_transport)); 74 | dict.emplace("min_port", port_range.first); 75 | dict.emplace("max_port", port_range.second); 76 | dict.emplace("listen_timeout", listen_timeout); 77 | // for compatibility of old ffmpeg 78 | dict.emplace("timeout", listen_timeout); 79 | dict.emplace("stimeout", socket_timeout); 80 | } 81 | void RtspInOption::generateOptions() { 82 | RtspOption::generateOptions(); 83 | dict.emplace("rtsp_flags", "listen"); 84 | } 85 | std::string RtspOption::getProtocolString(TransPortProtocol p) { 86 | switch (p) { 87 | using Protocol = RtspOption::TransPortProtocol; 88 | case Protocol::UDP: 89 | return "udp"; 90 | case Protocol::TCP: 91 | return "tcp"; 92 | case Protocol::UDP_MULTICAST: 93 | return "udp_multicast"; 94 | case Protocol::HTTP: 95 | return "http"; 96 | case Protocol::HTTPS: 97 | return "https"; 98 | } 99 | } 100 | 101 | std::string RtpInOption::makeDummySdp() { 102 | std::string sdp_content = 103 | R"(SDP: 104 | v=0 105 | o=- 0 0 IN IP4 $address$ 106 | s=DUMMY SDP 107 | c=IN IP4 $address$ 108 | t=0 0 109 | a=tool:libavformat 58.29.100 110 | m=audio $port$ RTP/AVP 97 111 | b=AS:$bitrate$ 112 | a=rtpmap:97 L16/$samplerate$/$channels$)"; 113 | std::vector> replace_pairs = {{"\\$port\\$", std::to_string(url.port)}, 114 | {"\\$address\\$", url.address}, 115 | {"\\$channels\\$", std::to_string(channels)}, 116 | {"\\$bitrate\\$", std::to_string((samplerate / 1000) * 16 * channels)}, 117 | {"\\$samplerate\\$", std::to_string(samplerate)}}; 118 | std::for_each(replace_pairs.begin(), replace_pairs.end(), [&](auto pair) { 119 | sdp_content = std::regex_replace(sdp_content, std::regex(pair.first), pair.second); 120 | }); 121 | return sdp_content; 122 | } 123 | int RtpInOption::readDummySdp(void* userdata, uint8_t* avio_buf, int buf_size) { 124 | auto octx = static_cast(userdata); 125 | if (octx->pos == octx->data.end()) { 126 | return 0; 127 | } 128 | auto dist = static_cast(std::distance(octx->pos, octx->data.end())); 129 | auto count = std::min(buf_size, dist); 130 | std::copy(octx->pos, octx->pos + count, avio_buf); 131 | octx->pos += count; 132 | return count; 133 | } 134 | 135 | // IO Format 136 | 137 | IOFormat::IOFormat() 138 | : ctx(avformat_alloc_context()) { } 139 | IOFormat::~IOFormat() { 140 | avformat_free_context(ctx); 141 | } 142 | // InFormat 143 | InFormat::InFormat() = default; 144 | // OutFormat 145 | OutFormat::OutFormat() = default; 146 | 147 | // old cutomcallback format 148 | CustomCbInFormat::CustomCbInFormat(RtpSRSetting const& s) 149 | : InFormat() { 150 | buffer.resize(setting.framesize * setting.channels); 151 | auto bufsize = getBufSize(s); 152 | auto* aviobuf = static_cast(av_malloc(bufsize)); 153 | auto* avioctx = avio_alloc_context(aviobuf, bufsize, 0, this, readPacket, nullptr, nullptr); 154 | ctx->flags |= AVFMT_FLAG_CUSTOM_IO; 155 | ctx->pb = avioctx; 156 | } 157 | 158 | 159 | int CustomCbInFormat::readPacket(void* userdata, uint8_t* avio_buf, int buf_size) { 160 | auto* sender = reinterpret_cast(userdata); 161 | auto* address = sender->buffer.data(); 162 | getBufSize(sender->setting); 163 | memcpy(avio_buf, address, buf_size); 164 | return buf_size; 165 | } 166 | CustomCbOutFormat::CustomCbOutFormat(RtpSRSetting const& s) 167 | : OutFormat() { 168 | buffer.resize(setting.framesize); 169 | 170 | auto bufsize = getBufSize(s); 171 | auto* aviobuf = static_cast(av_malloc(bufsize)); 172 | auto* avioctx = avio_alloc_context(aviobuf, bufsize, 1, this, nullptr, writePacket, nullptr); 173 | ctx->flags |= AVFMT_FLAG_CUSTOM_IO; 174 | ctx->pb = avioctx; 175 | } 176 | int CustomCbOutFormat::writePacket(void* userdata, uint8_t* avio_buf, int buf_size) { 177 | auto* receiver = reinterpret_cast(userdata); 178 | auto* address = receiver->buffer.data(); 179 | memcpy(address, avio_buf, buf_size); 180 | return buf_size; 181 | } 182 | 183 | // CustomCbAsyncFormat 184 | bool CustomCbAsyncFormat::tryPushRingBuffer(std::vector const& input) { 185 | return buffer.writeRange(input, input.size()); 186 | } 187 | bool CustomCbAsyncFormat::tryPopRingBuffer(std::vector& dest) { 188 | return buffer.readRange(dest, dest.size()); 189 | } 190 | CustomCbAsyncInFormat::CustomCbAsyncInFormat(RtpSRSetting const& s, size_t buffer_size) 191 | : InFormat() 192 | , CustomCbAsyncFormat(std::max((int)buffer_size, s.framesize) * s.channels) { } 193 | CustomCbAsyncOutFormat::CustomCbAsyncOutFormat(RtpSRSetting const& s, size_t buffer_size) 194 | : OutFormat() 195 | , CustomCbAsyncFormat(std::max((int)buffer_size, s.framesize) * s.channels) { } 196 | 197 | // Rtp Format base(for now, nothing) 198 | 199 | 200 | // Rtp Input 201 | RtpFormatBase::RtpFormatBase(std::unique_ptr opt) 202 | : option(std::move(opt)) { 203 | avformat_network_init(); 204 | } 205 | RtspInFormat::RtspInFormat(std::unique_ptr rtpoptions) 206 | : RtpInFormatBase(std::move(rtpoptions)) { } 207 | 208 | bool RtspInFormat::tryConnectInput() { 209 | option->generateOptions(); 210 | 211 | auto* ifmt = av_find_input_format("rtsp"); 212 | auto res = avformat_open_input(&ctx, getSdpUrl(option->url).c_str(), ifmt, option->getParam()); 213 | if (res < 0) { 214 | try { 215 | checkAvError(res); 216 | } 217 | catch (std::exception& e) { 218 | std::cerr << e.what() << "\nFailed to launch server at Specified address, wait connection at 127.0.0.1..." << std::endl; 219 | option->url = rtpsr::Url {"127.0.0.1", option->url.port}; 220 | } 221 | return false; 222 | } 223 | return true; 224 | } 225 | RtpInFormat::RtpInFormat(std::unique_ptr rtpoptions) 226 | : RtpInFormatBase(std::move(rtpoptions)) { 227 | sdp_avio = (unsigned char*)av_mallocz(aviobufsize); 228 | } 229 | RtpInFormat::~RtpInFormat() { 230 | if (sdp_avio != nullptr) { 231 | // av_free(sdp_avio); 232 | } 233 | } 234 | 235 | bool RtpInFormat::tryConnectInput() { 236 | auto* opt = dynamic_cast(option.get()); 237 | opt->generateOptions(); 238 | const auto* ifmt = av_find_input_format("sdp"); 239 | auto sdp = opt->makeDummySdp(); 240 | sdp_opaque = std::make_unique(); 241 | sdp_opaque->data = SdpOpaque::Vector(sdp.c_str(), sdp.c_str() + strlen(sdp.c_str())); 242 | sdp_opaque->pos = sdp_opaque->data.begin(); 243 | ctx->pb = avio_alloc_context(sdp_avio, aviobufsize, 0, sdp_opaque.get(), RtpInOption::readDummySdp, nullptr, nullptr); 244 | auto res = avformat_open_input(&ctx, "internal.sdp", ifmt, option->getParam()); 245 | if (res == -60 || res == -61 || res == -22) { 246 | return false; 247 | } 248 | if (res < 0) { 249 | checkAvError(res); 250 | } 251 | return true; 252 | } 253 | 254 | RtspOutFormat::RtspOutFormat(std::unique_ptr rtpoptions) 255 | : RtpOutFormatBase(std::move(rtpoptions)) { } 256 | RtpOutFormat::RtpOutFormat(std::unique_ptr rtpoptions) 257 | : RtpOutFormatBase(std::move(rtpoptions)) { } 258 | 259 | 260 | bool RtspOutFormat::tryConnect() { 261 | option->generateOptions(); 262 | auto url_tmp = getSdpUrl(option->url); 263 | auto* fmt_output = av_guess_format("rtsp", url_tmp.c_str(), nullptr); 264 | ctx->oformat = fmt_output; 265 | ctx->url = (char*)av_malloc(url_tmp.size() + sizeof(char)); 266 | av_strlcpy(ctx->url, url_tmp.c_str(), url_tmp.size() + sizeof(char)); 267 | checkAvError(avformat_init_output(ctx, option->getParam())); 268 | checkAvError(avio_open(&ctx->pb, ctx->url, AVIO_FLAG_WRITE)); 269 | int rcode = avformat_write_header(ctx, nullptr); 270 | if (rcode >= 0) { 271 | return true; 272 | } 273 | if (rcode == -60 || rcode == -61 || rcode == -22) { 274 | return false; 275 | } 276 | checkAvError(rcode); 277 | return false; 278 | } 279 | 280 | bool RtpOutFormat::tryConnect() { 281 | option->generateOptions(); 282 | auto url_tmp = getSdpUrl(option->url); 283 | const auto* fmt_output = av_guess_format("rtp", url_tmp.c_str(), "audio/L16"); 284 | 285 | auto codec_id = av_guess_codec(fmt_output, "pcm_s16be", nullptr, "audio/L16", AVMEDIA_TYPE_AUDIO); 286 | ctx->audio_codec_id = codec_id; 287 | ctx->oformat = fmt_output; 288 | ctx->url = static_cast(av_malloc(url_tmp.size() + sizeof(char))); 289 | av_strlcpy(ctx->url, url_tmp.c_str(), url_tmp.size() + sizeof(char)); 290 | checkAvError(avformat_init_output(ctx, option->getParam())); 291 | checkAvError(avio_open(&ctx->pb, ctx->url, AVIO_FLAG_WRITE)); 292 | int rcode = avformat_write_header(ctx, nullptr); 293 | if (rcode >= 0) { 294 | return true; 295 | } 296 | if (rcode == -60 || rcode == -61 || rcode == -22) { 297 | return false; 298 | } 299 | checkAvError(rcode); 300 | return false; 301 | } 302 | 303 | CodecBase::CodecBase(RtpSRSetting const& s, Codec c, bool isencoder) 304 | : codec(c) { 305 | auto* avcodec = (isencoder) ? avcodec_find_encoder_by_name(getCodecName(codec).c_str()) 306 | : avcodec_find_decoder_by_name(getCodecName(codec).c_str()); 307 | ctx = avcodec_alloc_context3(avcodec); 308 | ctx->bit_rate = bitrate; 309 | ctx->sample_rate = s.samplerate; 310 | ctx->frame_size = s.framesize; 311 | ctx->channels = s.channels; 312 | ctx->channel_layout = av_get_default_channel_layout(s.channels); 313 | ctx->sample_fmt = AV_SAMPLE_FMT_S16; 314 | avcodec_open2(ctx, avcodec, nullptr); 315 | } 316 | bool CodecBase::checkIsErrAgain(int error_code) { 317 | if (error_code == AVERROR(EAGAIN)) { 318 | return true; // return isempty 319 | } 320 | checkAvError(error_code); 321 | return false; 322 | } 323 | 324 | Decoder::Decoder(RtpSRSetting const& s, Codec c) 325 | : CodecBase(s, c, false) { } 326 | Encoder::Encoder(RtpSRSetting const& s, Codec c) 327 | : CodecBase(s, c, true) { 328 | ctx->frame_size = s.framesize; 329 | } 330 | bool Decoder::sendPacket(AVPacket* packet) { 331 | return checkIsErrAgain(avcodec_send_packet(ctx, packet)); 332 | } 333 | bool Decoder::receiveFrame(AVFrame* frame) { 334 | return checkIsErrAgain(avcodec_receive_frame(ctx, frame)); 335 | } 336 | 337 | bool Encoder::sendFrame(AVFrame* frame) { 338 | return checkIsErrAgain(avcodec_send_frame(ctx, frame)); 339 | } 340 | bool Encoder::receivePacket(AVPacket* packet) { 341 | return checkIsErrAgain(avcodec_receive_packet(ctx, packet)); 342 | } 343 | 344 | bool AsyncLooper::halt() { 345 | if (active) { 346 | active = false; 347 | wait(); 348 | } 349 | return true; 350 | } 351 | bool AsyncLooper::forceHalt() { 352 | active = false; 353 | return true; 354 | } 355 | void AsyncLooper::wait() { 356 | future.wait(); 357 | bool res = future.get(); 358 | } 359 | 360 | bool AsyncLooper::isActive() const { 361 | return active; 362 | } 363 | std::future_status AsyncLooper::wait_for(int mills) { 364 | return future.wait_for(std::chrono::milliseconds(mills)); 365 | } 366 | 367 | // RtpSRBase 368 | 369 | RtpSRBase::RtpSRBase(RtpSRSetting const& s, std::ostream& logger) 370 | : setting_ref(s) 371 | , logger(logger) { 372 | frame = av_frame_alloc(); 373 | frame->nb_samples = s.framesize; 374 | packet = av_packet_alloc(); 375 | av_new_packet(packet, getBufSize(s)); 376 | } 377 | 378 | RtpSRBase::~RtpSRBase() { 379 | av_frame_free(&frame); 380 | av_packet_free(&packet); 381 | } 382 | void RtpSRBase::initStream() const { 383 | auto* instream = avformat_new_stream(input->ctx, codec->ctx->codec); 384 | auto* outstream = avformat_new_stream(output->ctx, codec->ctx->codec); 385 | checkAvError(avcodec_parameters_from_context(instream->codecpar, codec->ctx)); 386 | checkAvError(avcodec_parameters_from_context(outstream->codecpar, codec->ctx)); 387 | instream->start_time = 0; 388 | outstream->start_time = 0; 389 | } 390 | } // namespace rtpsr -------------------------------------------------------------------------------- /source/rtpsendreceive_lib/rtpsrbase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include "libavcodec/avcodec.h" 5 | #include "libavformat/avformat.h" 6 | #include "libavutil/avassert.h" 7 | #include "libavutil/avstring.h" 8 | #include "libavutil/intreadwrite.h" 9 | #include "libavutil/mathematics.h" 10 | #include "libavutil/opt.h" 11 | #include "libavutil/timestamp.h" 12 | #include "libavutil/channel_layout.h" 13 | } 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "rtpsendreceive_lib.hpp" 21 | #include "lockfree_ringbuffer.hpp" 22 | 23 | 24 | namespace rtpsr { 25 | 26 | 27 | void checkAvError(int error_code); 28 | std::string avErrorString(int error_code); 29 | struct SdpOpaque { 30 | using Vector = std::vector; 31 | Vector data; 32 | Vector::iterator pos; 33 | }; 34 | 35 | struct AVOptionBase { 36 | using keytype = std::string; 37 | using valtype = std::variant; 38 | using container_t = std::unordered_map; 39 | AVOptionBase() = delete; 40 | explicit AVOptionBase(container_t&& init); 41 | ~AVOptionBase(); 42 | AVDictionary** get() { 43 | return ¶ms; 44 | } 45 | 46 | private: 47 | AVDictionary* params = nullptr; 48 | container_t container = {}; 49 | bool allocated = false; 50 | }; 51 | 52 | struct RtpSRSetting { 53 | double samplerate; 54 | int channels; 55 | int framesize; 56 | }; 57 | 58 | inline size_t getBufSize(RtpSRSetting const& s) { 59 | // how sample format? 60 | return sizeof(double) * s.framesize * s.channels; 61 | } 62 | struct Url { 63 | std::string address = "127.0.0.1"; 64 | int port = 30000; 65 | }; 66 | std::string getSdpUrl(std::string const& address, int port); 67 | std::string getSdpUrl(Url url); 68 | 69 | enum class RtpFormatKind { RTP = 0, RTSP = 1 }; 70 | 71 | inline std::string toString(RtpFormatKind k) { 72 | switch (k) { 73 | case RtpFormatKind::RTP: 74 | return "rtp"; 75 | case RtpFormatKind::RTSP: 76 | return "rtsp"; 77 | default: 78 | assert(false); 79 | return ""; 80 | } 81 | } 82 | 83 | struct RtpOptionsBase { 84 | explicit RtpOptionsBase(Url url); 85 | virtual ~RtpOptionsBase() = default; 86 | Url url; 87 | 88 | // number of packet of reorder queue 89 | size_t reorder_queue_size = 1000; 90 | size_t packet_size = 1000; 91 | // maximum reorder delay(in microseconds) to be set to audioformatcontext 92 | int max_delay = 500000; 93 | size_t buffer_size; 94 | 95 | virtual RtpFormatKind getKind() = 0; 96 | virtual void generateOptions(); 97 | AVDictionary** getParam() { 98 | avoptions = std::make_unique(std::move(dict)); 99 | return avoptions->get(); 100 | } 101 | AVOptionBase::container_t dict; 102 | std::unique_ptr avoptions; 103 | }; 104 | class RtpOption : public RtpSRSetting, public RtpOptionsBase { 105 | public: 106 | explicit RtpOption(Url url, double samplerate, int channels, int buffersize); 107 | RtpFormatKind getKind() override { 108 | return RtpFormatKind::RTP; 109 | }; 110 | void generateOptions() override; 111 | 112 | bool filter_source = false; 113 | }; 114 | class RtspOption : public RtpSRSetting, public RtpOptionsBase { 115 | public: 116 | explicit RtspOption(Url url, double samplerate, int channels, int buffersize); 117 | 118 | RtpFormatKind getKind() override { 119 | return RtpFormatKind::RTSP; 120 | }; 121 | void generateOptions() override; 122 | enum class TransPortProtocol { UDP = 0, TCP, UDP_MULTICAST, HTTP, HTTPS } rtsp_transport = TransPortProtocol::UDP; 123 | int listen_timeout = 1000; // unit:seconds 124 | int socket_timeout = 500000; // unit:microseconds 125 | std::pair port_range = {5000, 65000}; 126 | 127 | private: 128 | static std::string getProtocolString(TransPortProtocol p); 129 | }; 130 | class RtpInOption : public RtpOption { 131 | public: 132 | explicit RtpInOption(Url url, double samplerate, int channels, int buffersize) 133 | : RtpOption(url, samplerate, channels, buffersize) { } 134 | std::string makeDummySdp(); 135 | static int readDummySdp(void* userdata, uint8_t* avio_buf, int buf_size); 136 | bool use_customio = false; 137 | bool rtcp_to_source = false; 138 | }; 139 | class RtpOutOption : public RtpOption { 140 | public: 141 | explicit RtpOutOption(Url url, double samplerate, int channels, int buffersize) 142 | : RtpOption(url, samplerate, channels, buffersize) { } 143 | }; 144 | 145 | class RtspOutOption : public RtspOption { 146 | public: 147 | explicit RtspOutOption(Url url, double samplerate, int channels, int buffersize) 148 | : RtspOption(url, samplerate, channels, buffersize) { } 149 | }; 150 | class RtspInOption : public RtspOption { 151 | public: 152 | explicit RtspInOption(Url url, double samplerate, int channels, int buffersize) 153 | : RtspOption(url, samplerate, channels, buffersize) { } 154 | void generateOptions() override; 155 | }; 156 | 157 | // IO Format 158 | struct IOFormat { 159 | IOFormat(); 160 | virtual ~IOFormat(); 161 | AVFormatContext* ctx; 162 | RtpSRSetting setting; 163 | }; 164 | struct InFormat : public IOFormat { 165 | InFormat(); 166 | ~InFormat() override = default; 167 | virtual void connectInput() {}; 168 | }; 169 | struct OutFormat : public IOFormat { 170 | OutFormat(); 171 | virtual ~OutFormat() = default; 172 | virtual void startOutput() {}; 173 | }; 174 | 175 | // Custom Callback Async Format 176 | // Uses LockFreeRingbuffer. This does not uses libavformat. Data are assumed to be taken from avframe directly. 177 | class CustomCbAsyncFormat { 178 | public: 179 | virtual ~CustomCbAsyncFormat() = default; 180 | explicit CustomCbAsyncFormat(size_t buffer_size) 181 | : buffer(buffer_size) { } 182 | virtual bool tryPushRingBuffer(std::vector const& input); 183 | virtual bool tryPopRingBuffer(std::vector& dest); 184 | 185 | private: 186 | LockFreeRingbuf buffer; 187 | }; 188 | 189 | class CustomCbAsyncInFormat final : public InFormat, public CustomCbAsyncFormat { 190 | public: 191 | explicit CustomCbAsyncInFormat(RtpSRSetting const& s, size_t buffer_size); 192 | ~CustomCbAsyncInFormat() final = default; 193 | }; 194 | class CustomCbAsyncOutFormat final : public OutFormat, public CustomCbAsyncFormat { 195 | public: 196 | explicit CustomCbAsyncOutFormat(RtpSRSetting const& s, size_t buffer_size); 197 | ~CustomCbAsyncOutFormat() final = default; 198 | }; 199 | 200 | // old classes for synchronous reading 201 | struct CustomCbFormat { 202 | rtpsr::buffertype buffer; 203 | }; 204 | struct CustomCbInFormat : public InFormat, public CustomCbFormat { 205 | explicit CustomCbInFormat(RtpSRSetting const& s); 206 | ~CustomCbInFormat() = default; 207 | static int readPacket(void* userdata, uint8_t* avio_buf, int buf_size); 208 | 209 | void setBuffer(double sample, int pos, int channel) { 210 | buffer[pos * setting.channels + channel] = static_cast(sample * INT16_MAX); 211 | } 212 | }; 213 | struct CustomCbOutFormat : public OutFormat, public CustomCbFormat { 214 | explicit CustomCbOutFormat(RtpSRSetting const& s); 215 | ~CustomCbOutFormat() { } 216 | static int writePacket(void* userdata, uint8_t* avio_buf, int buf_size); 217 | template 218 | T readBuffer(int pos, int channel) { 219 | return static_cast(buffer.at(pos * setting.channels + channel)); 220 | }; 221 | template<> 222 | rtpsr::sample_t readBuffer(int pos, int channel) { 223 | return buffer.at(pos * setting.channels + channel); 224 | }; 225 | template<> 226 | double readBuffer(int pos, int channel) { 227 | return static_cast(buffer.at(pos * setting.channels + channel)) / INT16_MAX; 228 | } 229 | }; 230 | 231 | // rtp in out format. 232 | struct RtpFormatBase { 233 | RtpFormatBase(std::unique_ptr opt); 234 | std::unique_ptr option; 235 | }; 236 | struct RtpInFormatBase : public InFormat, public RtpFormatBase { 237 | RtpInFormatBase(std::unique_ptr opt) 238 | : RtpFormatBase(std::move(opt)) { 239 | 240 | }; 241 | virtual bool tryConnectInput() = 0; 242 | }; 243 | 244 | struct RtspInFormat : public RtpInFormatBase { 245 | RtspInFormat() = delete; 246 | explicit RtspInFormat(std::unique_ptr rtpoptions); 247 | bool tryConnectInput() override; 248 | }; 249 | 250 | struct RtpInFormat : public RtpInFormatBase { 251 | RtpInFormat() = delete; 252 | explicit RtpInFormat(std::unique_ptr rtpoptions); 253 | ~RtpInFormat() override; 254 | bool tryConnectInput() override; 255 | std::unique_ptr sdp_opaque; 256 | unsigned char* sdp_avio; 257 | inline static constexpr size_t aviobufsize = 4096; 258 | }; 259 | 260 | struct RtpOutFormatBase : public OutFormat, public RtpFormatBase { 261 | RtpOutFormatBase(std::unique_ptr opt) 262 | : RtpFormatBase(std::move(opt)) {}; 263 | virtual bool tryConnect() = 0; 264 | }; 265 | struct RtspOutFormat : public RtpOutFormatBase { 266 | RtspOutFormat() = delete; 267 | explicit RtspOutFormat(std::unique_ptr rtpoptions); 268 | bool tryConnect() override; 269 | }; 270 | 271 | struct RtpOutFormat : public RtpOutFormatBase { 272 | RtpOutFormat() = delete; 273 | explicit RtpOutFormat(std::unique_ptr rtpoptions); 274 | bool tryConnect() override; 275 | }; 276 | 277 | struct CodecBase { 278 | explicit CodecBase(RtpSRSetting const& s, Codec c = Codec::PCM_s16BE, bool isencoder = true); 279 | virtual ~CodecBase() = default; 280 | AVCodecContext* ctx = nullptr; 281 | rtpsr::Codec codec; 282 | int64_t bitrate = 192000; // bitrate for lossy compression 283 | static bool checkIsErrAgain(int error_code); 284 | }; 285 | struct Decoder : public CodecBase { 286 | explicit Decoder(RtpSRSetting const& s, Codec c); 287 | ~Decoder() override { 288 | avcodec_free_context(&ctx); 289 | } 290 | bool sendPacket(AVPacket* packet); 291 | bool receiveFrame(AVFrame* frame); 292 | }; 293 | struct Encoder : public CodecBase { 294 | explicit Encoder(RtpSRSetting const& s, Codec c); 295 | ~Encoder() override { 296 | avcodec_free_context(&ctx); 297 | } 298 | bool sendFrame(AVFrame* frame); 299 | bool receivePacket(AVPacket* packet); 300 | }; 301 | struct AsyncLoopState { 302 | std::atomic active = false; 303 | std::future future; 304 | }; 305 | 306 | class AsyncLooper { 307 | public: 308 | AsyncLooper() = default; 309 | using callbacktype = void (*)(AsyncLooper const&); 310 | 311 | template 312 | auto launch(F&& fn) { 313 | active = true; 314 | future = std::async(std::launch::async, std::move(fn)); 315 | return future; 316 | } 317 | bool halt(); 318 | bool forceHalt(); 319 | void wait(); 320 | std::future_status wait_for(int mills); 321 | bool isActive() const; 322 | 323 | private: 324 | std::atomic active = false; 325 | std::shared_future future; 326 | }; 327 | 328 | struct RtpSRBase { 329 | explicit RtpSRBase(RtpSRSetting const& s, std::ostream& logger = std::cerr); 330 | ~RtpSRBase(); 331 | virtual void launchLoop() = 0; 332 | virtual bool isConnected() = 0; 333 | AsyncLooper asynclooper; 334 | AsyncLooper init_asyncloop; 335 | 336 | protected: 337 | void initStream() const; 338 | const RtpSRSetting& setting_ref; 339 | std::unique_ptr input; 340 | std::unique_ptr output; 341 | std::unique_ptr codec; 342 | AVPacket* packet; 343 | AVFrame* frame; 344 | std::ostream& logger; 345 | std::atomic isconnected = false; 346 | }; 347 | 348 | } // namespace rtpsr -------------------------------------------------------------------------------- /test_patch/receiver_test.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 161.0, 165.0, 805.0, 629.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-7", 43 | "lastchannelcount" : 4, 44 | "maxclass" : "mc.live.gain~", 45 | "numinlets" : 1, 46 | "numoutlets" : 4, 47 | "outlettype" : [ "multichannelsignal", "", "float", "list" ], 48 | "parameter_enable" : 1, 49 | "patching_rect" : [ 63.0, 262.0, 48.0, 136.0 ], 50 | "saved_attribute_attributes" : { 51 | "valueof" : { 52 | "parameter_mmin" : -70.0, 53 | "parameter_longname" : "mc.live.gain~[1]", 54 | "parameter_mmax" : 6.0, 55 | "parameter_shortname" : "mc.live.gain~", 56 | "parameter_type" : 0, 57 | "parameter_unitstyle" : 4 58 | } 59 | 60 | } 61 | , 62 | "varname" : "mc.live.gain~[1]" 63 | } 64 | 65 | } 66 | , { 67 | "box" : { 68 | "id" : "obj-9", 69 | "maxclass" : "toggle", 70 | "numinlets" : 1, 71 | "numoutlets" : 1, 72 | "outlettype" : [ "int" ], 73 | "parameter_enable" : 0, 74 | "patching_rect" : [ 250.0, 129.0, 24.0, 24.0 ] 75 | } 76 | 77 | } 78 | , { 79 | "box" : { 80 | "id" : "obj-3", 81 | "maxclass" : "attrui", 82 | "numinlets" : 1, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 69.0, 85.0, 150.0, 22.0 ] 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "id" : "obj-1", 92 | "maxclass" : "newobj", 93 | "numinlets" : 1, 94 | "numoutlets" : 1, 95 | "outlettype" : [ "" ], 96 | "patching_rect" : [ 63.0, 209.0, 260.0, 22.0 ], 97 | "text" : "mc.rtpreceive~.mxo @channels 2 @port 30000" 98 | } 99 | 100 | } 101 | ], 102 | "lines" : [ { 103 | "patchline" : { 104 | "destination" : [ "obj-7", 0 ], 105 | "source" : [ "obj-1", 0 ] 106 | } 107 | 108 | } 109 | , { 110 | "patchline" : { 111 | "destination" : [ "obj-1", 0 ], 112 | "source" : [ "obj-3", 0 ] 113 | } 114 | 115 | } 116 | , { 117 | "patchline" : { 118 | "destination" : [ "obj-1", 0 ], 119 | "source" : [ "obj-9", 0 ] 120 | } 121 | 122 | } 123 | ], 124 | "parameters" : { 125 | "obj-7" : [ "mc.live.gain~[1]", "mc.live.gain~", 0 ], 126 | "parameterbanks" : { 127 | 128 | } 129 | 130 | } 131 | , 132 | "dependency_cache" : [ { 133 | "name" : "mc.rtpreceive~.mxo", 134 | "type" : "iLaX" 135 | } 136 | ], 137 | "autosave" : 0 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /test_patch/self_loop_test.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 1, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 215.0, 124.0, 1349.0, 811.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "assistshowspatchername" : 0, 41 | "boxes" : [ { 42 | "box" : { 43 | "id" : "obj-46", 44 | "maxclass" : "message", 45 | "numinlets" : 2, 46 | "numoutlets" : 1, 47 | "outlettype" : [ "" ], 48 | "patching_rect" : [ 1038.0, 251.0, 50.0, 22.0 ] 49 | } 50 | 51 | } 52 | , { 53 | "box" : { 54 | "id" : "obj-41", 55 | "maxclass" : "message", 56 | "numinlets" : 2, 57 | "numoutlets" : 1, 58 | "outlettype" : [ "" ], 59 | "patching_rect" : [ 1028.0, 152.0, 63.0, 22.0 ], 60 | "text" : "getlatency" 61 | } 62 | 63 | } 64 | , { 65 | "box" : { 66 | "id" : "obj-43", 67 | "maxclass" : "newobj", 68 | "numinlets" : 9, 69 | "numoutlets" : 1, 70 | "outlettype" : [ "signal" ], 71 | "patching_rect" : [ 154.75, 591.0, 103.0, 22.0 ], 72 | "text" : "selector~ 8 1" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "id" : "obj-44", 79 | "maxclass" : "newobj", 80 | "numinlets" : 1, 81 | "numoutlets" : 8, 82 | "outlettype" : [ "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal" ], 83 | "patching_rect" : [ 165.25, 545.0, 92.5, 22.0 ], 84 | "text" : "mc.unpack~ 8" 85 | } 86 | 87 | } 88 | , { 89 | "box" : { 90 | "id" : "obj-36", 91 | "maxclass" : "newobj", 92 | "numinlets" : 9, 93 | "numoutlets" : 1, 94 | "outlettype" : [ "signal" ], 95 | "patching_rect" : [ 678.5, 567.0, 103.0, 22.0 ], 96 | "text" : "selector~ 8 1" 97 | } 98 | 99 | } 100 | , { 101 | "box" : { 102 | "id" : "obj-38", 103 | "maxclass" : "message", 104 | "numinlets" : 2, 105 | "numoutlets" : 1, 106 | "outlettype" : [ "" ], 107 | "patching_rect" : [ 293.0, 172.0, 29.5, 22.0 ], 108 | "text" : "0" 109 | } 110 | 111 | } 112 | , { 113 | "box" : { 114 | "id" : "obj-37", 115 | "maxclass" : "attrui", 116 | "numinlets" : 1, 117 | "numoutlets" : 1, 118 | "outlettype" : [ "" ], 119 | "patching_rect" : [ 1051.0, 122.0, 150.0, 22.0 ] 120 | } 121 | 122 | } 123 | , { 124 | "box" : { 125 | "id" : "obj-39", 126 | "maxclass" : "toggle", 127 | "numinlets" : 1, 128 | "numoutlets" : 1, 129 | "outlettype" : [ "int" ], 130 | "parameter_enable" : 0, 131 | "patching_rect" : [ 339.0, 228.0, 24.0, 24.0 ] 132 | } 133 | 134 | } 135 | , { 136 | "box" : { 137 | "id" : "obj-35", 138 | "maxclass" : "newobj", 139 | "numinlets" : 2, 140 | "numoutlets" : 1, 141 | "outlettype" : [ "bang" ], 142 | "patching_rect" : [ 315.0, 265.0, 63.0, 22.0 ], 143 | "text" : "metro 600" 144 | } 145 | 146 | } 147 | , { 148 | "box" : { 149 | "id" : "obj-34", 150 | "maxclass" : "newobj", 151 | "numinlets" : 5, 152 | "numoutlets" : 4, 153 | "outlettype" : [ "int", "", "", "int" ], 154 | "patching_rect" : [ 297.0, 315.0, 69.0, 22.0 ], 155 | "text" : "counter 1 8" 156 | } 157 | 158 | } 159 | , { 160 | "box" : { 161 | "id" : "obj-33", 162 | "maxclass" : "newobj", 163 | "numinlets" : 2, 164 | "numoutlets" : 8, 165 | "outlettype" : [ "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal" ], 166 | "patching_rect" : [ 33.0, 275.0, 92.5, 22.0 ], 167 | "text" : "gate~ 8" 168 | } 169 | 170 | } 171 | , { 172 | "box" : { 173 | "id" : "obj-32", 174 | "maxclass" : "newobj", 175 | "numinlets" : 2, 176 | "numoutlets" : 1, 177 | "outlettype" : [ "signal" ], 178 | "patching_rect" : [ 354.0, 208.0, 66.0, 22.0 ], 179 | "text" : "cycle~ 100" 180 | } 181 | 182 | } 183 | , { 184 | "box" : { 185 | "id" : "obj-12", 186 | "maxclass" : "scope~", 187 | "numinlets" : 2, 188 | "numoutlets" : 0, 189 | "patching_rect" : [ 967.0, 347.0, 130.0, 130.0 ] 190 | } 191 | 192 | } 193 | , { 194 | "box" : { 195 | "attr" : "use_rtsp", 196 | "id" : "obj-30", 197 | "maxclass" : "attrui", 198 | "numinlets" : 1, 199 | "numoutlets" : 1, 200 | "outlettype" : [ "" ], 201 | "patching_rect" : [ 280.0, 495.0, 150.0, 22.0 ] 202 | } 203 | 204 | } 205 | , { 206 | "box" : { 207 | "attr" : "use_rtsp", 208 | "id" : "obj-29", 209 | "maxclass" : "attrui", 210 | "numinlets" : 1, 211 | "numoutlets" : 1, 212 | "outlettype" : [ "" ], 213 | "patching_rect" : [ 551.0, 24.0, 150.0, 22.0 ] 214 | } 215 | 216 | } 217 | , { 218 | "box" : { 219 | "attr" : "address", 220 | "id" : "obj-27", 221 | "maxclass" : "attrui", 222 | "numinlets" : 1, 223 | "numoutlets" : 1, 224 | "outlettype" : [ "" ], 225 | "patching_rect" : [ 787.0, 61.0, 219.0, 22.0 ] 226 | } 227 | 228 | } 229 | , { 230 | "box" : { 231 | "id" : "obj-31", 232 | "maxclass" : "newobj", 233 | "numinlets" : 1, 234 | "numoutlets" : 1, 235 | "outlettype" : [ "" ], 236 | "patching_rect" : [ 399.5, 61.0, 57.0, 22.0 ], 237 | "text" : "tosymbol" 238 | } 239 | 240 | } 241 | , { 242 | "box" : { 243 | "id" : "obj-28", 244 | "items" : [ "pcm_s16be", ",", "opus" ], 245 | "maxclass" : "umenu", 246 | "numinlets" : 1, 247 | "numoutlets" : 3, 248 | "outlettype" : [ "int", "", "" ], 249 | "parameter_enable" : 0, 250 | "patching_rect" : [ 345.0, 24.0, 128.0, 22.0 ] 251 | } 252 | 253 | } 254 | , { 255 | "box" : { 256 | "attr" : "codec", 257 | "id" : "obj-26", 258 | "maxclass" : "attrui", 259 | "numinlets" : 1, 260 | "numoutlets" : 1, 261 | "outlettype" : [ "" ], 262 | "patching_rect" : [ 97.0, 432.0, 150.0, 22.0 ] 263 | } 264 | 265 | } 266 | , { 267 | "box" : { 268 | "attr" : "codec", 269 | "id" : "obj-23", 270 | "maxclass" : "attrui", 271 | "numinlets" : 1, 272 | "numoutlets" : 1, 273 | "outlettype" : [ "" ], 274 | "patching_rect" : [ 430.0, 161.0, 150.0, 22.0 ] 275 | } 276 | 277 | } 278 | , { 279 | "box" : { 280 | "basictuning" : 440, 281 | "clipheight" : 29.0, 282 | "data" : { 283 | "clips" : [ { 284 | "absolutepath" : "cello-f2.aif", 285 | "filename" : "cello-f2.aif", 286 | "filekind" : "audiofile", 287 | "id" : "u581000496", 288 | "loop" : 1, 289 | "content_state" : { 290 | "play" : [ 0 ], 291 | "pitchcorrection" : [ 0 ], 292 | "originallengthms" : [ 0.0 ], 293 | "mode" : [ "basic" ], 294 | "basictuning" : [ 440 ], 295 | "timestretch" : [ 0 ], 296 | "followglobaltempo" : [ 0 ], 297 | "speed" : [ 1.0 ], 298 | "pitchshift" : [ 1.0 ], 299 | "originallength" : [ 0.0, "ticks" ], 300 | "formantcorrection" : [ 0 ], 301 | "formant" : [ 1.0 ], 302 | "originaltempo" : [ 120.0 ], 303 | "slurtime" : [ 0.0 ], 304 | "quality" : [ "basic" ], 305 | "loop" : 1 306 | } 307 | 308 | } 309 | ] 310 | } 311 | , 312 | "followglobaltempo" : 0, 313 | "formantcorrection" : 0, 314 | "id" : "obj-25", 315 | "maxclass" : "playlist~", 316 | "mode" : "basic", 317 | "numinlets" : 1, 318 | "numoutlets" : 5, 319 | "originallength" : [ 0.0, "ticks" ], 320 | "originaltempo" : 120.0, 321 | "outlettype" : [ "signal", "signal", "signal", "", "dictionary" ], 322 | "parameter_enable" : 0, 323 | "patching_rect" : [ 255.0, 208.0, 63.0, 30.0 ], 324 | "pitchcorrection" : 0, 325 | "quality" : "basic", 326 | "timestretch" : [ 0 ] 327 | } 328 | 329 | } 330 | , { 331 | "box" : { 332 | "basictuning" : 440, 333 | "clipheight" : 29.0, 334 | "data" : { 335 | "clips" : [ { 336 | "absolutepath" : "eroica.aiff", 337 | "filename" : "eroica.aiff", 338 | "filekind" : "audiofile", 339 | "id" : "u981000501", 340 | "loop" : 1, 341 | "content_state" : { 342 | "play" : [ 0 ], 343 | "pitchcorrection" : [ 0 ], 344 | "originallengthms" : [ 0.0 ], 345 | "mode" : [ "basic" ], 346 | "basictuning" : [ 440 ], 347 | "timestretch" : [ 0 ], 348 | "followglobaltempo" : [ 0 ], 349 | "speed" : [ 1.0 ], 350 | "pitchshift" : [ 1.0 ], 351 | "originallength" : [ 0.0, "ticks" ], 352 | "formantcorrection" : [ 0 ], 353 | "formant" : [ 1.0 ], 354 | "originaltempo" : [ 120.0 ], 355 | "slurtime" : [ 0.0 ], 356 | "quality" : [ "basic" ], 357 | "loop" : 1 358 | } 359 | 360 | } 361 | ] 362 | } 363 | , 364 | "followglobaltempo" : 0, 365 | "formantcorrection" : 0, 366 | "id" : "obj-16", 367 | "maxclass" : "playlist~", 368 | "mode" : "basic", 369 | "numinlets" : 1, 370 | "numoutlets" : 5, 371 | "originallength" : [ 0.0, "ticks" ], 372 | "originaltempo" : 120.0, 373 | "outlettype" : [ "signal", "signal", "signal", "", "dictionary" ], 374 | "parameter_enable" : 0, 375 | "patching_rect" : [ 187.0, 208.0, 49.0, 30.0 ], 376 | "pitchcorrection" : 0, 377 | "quality" : "basic", 378 | "timestretch" : [ 0 ] 379 | } 380 | 381 | } 382 | , { 383 | "box" : { 384 | "id" : "obj-24", 385 | "maxclass" : "message", 386 | "numinlets" : 2, 387 | "numoutlets" : 1, 388 | "outlettype" : [ "" ], 389 | "patching_rect" : [ 630.0, 208.0, 57.0, 22.0 ], 390 | "text" : "chans $1" 391 | } 392 | 393 | } 394 | , { 395 | "box" : { 396 | "id" : "obj-20", 397 | "maxclass" : "number", 398 | "numinlets" : 1, 399 | "numoutlets" : 2, 400 | "outlettype" : [ "", "bang" ], 401 | "parameter_enable" : 0, 402 | "patching_rect" : [ 731.0, 45.0, 50.0, 22.0 ] 403 | } 404 | 405 | } 406 | , { 407 | "box" : { 408 | "attr" : "channels", 409 | "id" : "obj-14", 410 | "maxclass" : "attrui", 411 | "numinlets" : 1, 412 | "numoutlets" : 1, 413 | "outlettype" : [ "" ], 414 | "patching_rect" : [ 796.0, 115.0, 150.0, 22.0 ] 415 | } 416 | 417 | } 418 | , { 419 | "box" : { 420 | "id" : "obj-11", 421 | "maxclass" : "newobj", 422 | "numinlets" : 1, 423 | "numoutlets" : 8, 424 | "outlettype" : [ "signal", "signal", "signal", "signal", "signal", "signal", "signal", "signal" ], 425 | "patching_rect" : [ 689.0, 521.0, 92.5, 22.0 ], 426 | "text" : "mc.unpack~ 8" 427 | } 428 | 429 | } 430 | , { 431 | "box" : { 432 | "id" : "obj-1", 433 | "maxclass" : "newobj", 434 | "numinlets" : 2, 435 | "numoutlets" : 0, 436 | "patching_rect" : [ 231.0, 647.0, 35.0, 22.0 ], 437 | "text" : "dac~" 438 | } 439 | 440 | } 441 | , { 442 | "box" : { 443 | "id" : "obj-9", 444 | "lastchannelcount" : 8, 445 | "maxclass" : "mc.live.gain~", 446 | "numinlets" : 1, 447 | "numoutlets" : 4, 448 | "outlettype" : [ "multichannelsignal", "", "float", "list" ], 449 | "parameter_enable" : 1, 450 | "patching_rect" : [ 33.0, 375.0, 48.0, 136.0 ], 451 | "saved_attribute_attributes" : { 452 | "valueof" : { 453 | "parameter_longname" : "mc.live.gain~[1]", 454 | "parameter_mmax" : 6.0, 455 | "parameter_mmin" : -70.0, 456 | "parameter_shortname" : "mc.live.gain~", 457 | "parameter_type" : 0, 458 | "parameter_unitstyle" : 4 459 | } 460 | 461 | } 462 | , 463 | "varname" : "mc.live.gain~[1]" 464 | } 465 | 466 | } 467 | , { 468 | "box" : { 469 | "id" : "obj-5", 470 | "maxclass" : "newobj", 471 | "numinlets" : 1, 472 | "numoutlets" : 2, 473 | "outlettype" : [ "int", "int" ], 474 | "patching_rect" : [ 136.0, 101.0, 29.5, 22.0 ], 475 | "text" : "t i i" 476 | } 477 | 478 | } 479 | , { 480 | "box" : { 481 | "id" : "obj-2", 482 | "maxclass" : "toggle", 483 | "numinlets" : 1, 484 | "numoutlets" : 1, 485 | "outlettype" : [ "int" ], 486 | "parameter_enable" : 0, 487 | "patching_rect" : [ 686.0, 145.0, 24.0, 24.0 ] 488 | } 489 | 490 | } 491 | , { 492 | "box" : { 493 | "id" : "obj-22", 494 | "maxclass" : "newobj", 495 | "numinlets" : 1, 496 | "numoutlets" : 4, 497 | "outlettype" : [ "int", "float", "int", "int" ], 498 | "patching_rect" : [ 136.0, 55.0, 61.0, 22.0 ], 499 | "text" : "dspstate~" 500 | } 501 | 502 | } 503 | , { 504 | "box" : { 505 | "id" : "obj-21", 506 | "maxclass" : "toggle", 507 | "numinlets" : 1, 508 | "numoutlets" : 1, 509 | "outlettype" : [ "int" ], 510 | "parameter_enable" : 0, 511 | "patching_rect" : [ 136.0, 165.0, 24.0, 24.0 ] 512 | } 513 | 514 | } 515 | , { 516 | "box" : { 517 | "id" : "obj-19", 518 | "maxclass" : "newobj", 519 | "numinlets" : 8, 520 | "numoutlets" : 1, 521 | "outlettype" : [ "multichannelsignal" ], 522 | "patching_rect" : [ 33.0, 309.0, 92.5, 22.0 ], 523 | "text" : "mc.pack~ 8" 524 | } 525 | 526 | } 527 | , { 528 | "box" : { 529 | "id" : "obj-18", 530 | "maxclass" : "newobj", 531 | "numinlets" : 1, 532 | "numoutlets" : 1, 533 | "outlettype" : [ "bang" ], 534 | "patching_rect" : [ 33.0, 120.0, 58.0, 22.0 ], 535 | "text" : "loadbang" 536 | } 537 | 538 | } 539 | , { 540 | "box" : { 541 | "id" : "obj-17", 542 | "maxclass" : "message", 543 | "numinlets" : 2, 544 | "numoutlets" : 1, 545 | "outlettype" : [ "" ], 546 | "patching_rect" : [ 33.0, 171.0, 51.0, 22.0 ], 547 | "text" : "loop 1 1" 548 | } 549 | 550 | } 551 | , { 552 | "box" : { 553 | "basictuning" : 440, 554 | "clipheight" : 29.0, 555 | "data" : { 556 | "clips" : [ { 557 | "absolutepath" : "cherokee.aif", 558 | "filename" : "cherokee.aif", 559 | "filekind" : "audiofile", 560 | "id" : "u208000513", 561 | "loop" : 1, 562 | "content_state" : { 563 | "play" : [ 0 ], 564 | "pitchcorrection" : [ 0 ], 565 | "originallengthms" : [ 0.0 ], 566 | "mode" : [ "basic" ], 567 | "basictuning" : [ 440 ], 568 | "timestretch" : [ 0 ], 569 | "followglobaltempo" : [ 0 ], 570 | "speed" : [ 1.0 ], 571 | "pitchshift" : [ 1.0 ], 572 | "originallength" : [ 0.0, "ticks" ], 573 | "formantcorrection" : [ 0 ], 574 | "formant" : [ 1.0 ], 575 | "originaltempo" : [ 120.0 ], 576 | "slurtime" : [ 0.0 ], 577 | "quality" : [ "basic" ], 578 | "loop" : 1 579 | } 580 | 581 | } 582 | ] 583 | } 584 | , 585 | "followglobaltempo" : 0, 586 | "formantcorrection" : 0, 587 | "id" : "obj-15", 588 | "maxclass" : "playlist~", 589 | "mode" : "basic", 590 | "numinlets" : 1, 591 | "numoutlets" : 5, 592 | "originallength" : [ 0.0, "ticks" ], 593 | "originaltempo" : 120.0, 594 | "outlettype" : [ "signal", "signal", "signal", "", "dictionary" ], 595 | "parameter_enable" : 0, 596 | "patching_rect" : [ 116.5, 208.0, 61.0, 30.0 ], 597 | "pitchcorrection" : 0, 598 | "quality" : "basic", 599 | "timestretch" : [ 0 ] 600 | } 601 | 602 | } 603 | , { 604 | "box" : { 605 | "basictuning" : 440, 606 | "data" : { 607 | "clips" : [ { 608 | "absolutepath" : "drumLoop.aif", 609 | "filename" : "drumLoop.aif", 610 | "filekind" : "audiofile", 611 | "id" : "u300000518", 612 | "loop" : 1, 613 | "content_state" : { 614 | "play" : [ 0 ], 615 | "pitchcorrection" : [ 0 ], 616 | "originallengthms" : [ 0.0 ], 617 | "mode" : [ "basic" ], 618 | "basictuning" : [ 440 ], 619 | "timestretch" : [ 0 ], 620 | "followglobaltempo" : [ 0 ], 621 | "speed" : [ 1.0 ], 622 | "pitchshift" : [ 1.0 ], 623 | "originallength" : [ 0.0, "ticks" ], 624 | "formantcorrection" : [ 0 ], 625 | "formant" : [ 1.0 ], 626 | "originaltempo" : [ 120.0 ], 627 | "slurtime" : [ 0.0 ], 628 | "quality" : [ "basic" ], 629 | "loop" : 1 630 | } 631 | 632 | } 633 | ] 634 | } 635 | , 636 | "followglobaltempo" : 0, 637 | "formantcorrection" : 0, 638 | "id" : "obj-13", 639 | "maxclass" : "playlist~", 640 | "mode" : "basic", 641 | "numinlets" : 1, 642 | "numoutlets" : 5, 643 | "originallength" : [ 0.0, "ticks" ], 644 | "originaltempo" : 120.0, 645 | "outlettype" : [ "signal", "signal", "signal", "", "dictionary" ], 646 | "parameter_enable" : 0, 647 | "patching_rect" : [ 33.0, 208.0, 70.0, 30.0 ], 648 | "pitchcorrection" : 0, 649 | "quality" : "basic", 650 | "timestretch" : [ 0 ] 651 | } 652 | 653 | } 654 | , { 655 | "box" : { 656 | "id" : "obj-10", 657 | "maxclass" : "message", 658 | "numinlets" : 2, 659 | "numoutlets" : 1, 660 | "outlettype" : [ "" ], 661 | "patching_rect" : [ 761.0, 347.0, 50.0, 22.0 ] 662 | } 663 | 664 | } 665 | , { 666 | "box" : { 667 | "id" : "obj-8", 668 | "maxclass" : "newobj", 669 | "numinlets" : 1, 670 | "numoutlets" : 1, 671 | "outlettype" : [ "int" ], 672 | "patching_rect" : [ 753.0, 275.0, 106.0, 22.0 ], 673 | "text" : "mc.channelcount~" 674 | } 675 | 676 | } 677 | , { 678 | "box" : { 679 | "id" : "obj-7", 680 | "lastchannelcount" : 2, 681 | "maxclass" : "mc.live.gain~", 682 | "numinlets" : 1, 683 | "numoutlets" : 4, 684 | "outlettype" : [ "multichannelsignal", "", "float", "list" ], 685 | "parameter_enable" : 1, 686 | "patching_rect" : [ 689.0, 275.0, 48.0, 136.0 ], 687 | "saved_attribute_attributes" : { 688 | "valueof" : { 689 | "parameter_longname" : "mc.live.gain~", 690 | "parameter_mmax" : 6.0, 691 | "parameter_mmin" : -70.0, 692 | "parameter_shortname" : "mc.live.gain~", 693 | "parameter_type" : 0, 694 | "parameter_unitstyle" : 4 695 | } 696 | 697 | } 698 | , 699 | "varname" : "mc.live.gain~" 700 | } 701 | 702 | } 703 | , { 704 | "box" : { 705 | "attr" : "channels", 706 | "id" : "obj-6", 707 | "maxclass" : "attrui", 708 | "numinlets" : 1, 709 | "numoutlets" : 1, 710 | "outlettype" : [ "" ], 711 | "patching_rect" : [ 544.0, 422.0, 150.0, 22.0 ] 712 | } 713 | 714 | } 715 | , { 716 | "box" : { 717 | "id" : "obj-4", 718 | "maxclass" : "newobj", 719 | "numinlets" : 1, 720 | "numoutlets" : 2, 721 | "outlettype" : [ "multichannelsignal", "" ], 722 | "patching_rect" : [ 689.0, 189.0, 346.0, 22.0 ], 723 | "text" : "mc.rtpreceive~ @address 127.0.0.1 @channels 2 @port 30000" 724 | } 725 | 726 | } 727 | , { 728 | "box" : { 729 | "id" : "obj-3", 730 | "maxclass" : "newobj", 731 | "numinlets" : 1, 732 | "numoutlets" : 0, 733 | "patching_rect" : [ 33.0, 746.0, 333.0, 22.0 ], 734 | "text" : "mc.rtpsend~ @channels 2 @address 127.0.0.1 @port 30000" 735 | } 736 | 737 | } 738 | ], 739 | "lines" : [ { 740 | "patchline" : { 741 | "destination" : [ "obj-36", 8 ], 742 | "source" : [ "obj-11", 7 ] 743 | } 744 | 745 | } 746 | , { 747 | "patchline" : { 748 | "destination" : [ "obj-36", 7 ], 749 | "source" : [ "obj-11", 6 ] 750 | } 751 | 752 | } 753 | , { 754 | "patchline" : { 755 | "destination" : [ "obj-36", 6 ], 756 | "source" : [ "obj-11", 5 ] 757 | } 758 | 759 | } 760 | , { 761 | "patchline" : { 762 | "destination" : [ "obj-36", 5 ], 763 | "source" : [ "obj-11", 4 ] 764 | } 765 | 766 | } 767 | , { 768 | "patchline" : { 769 | "destination" : [ "obj-36", 4 ], 770 | "source" : [ "obj-11", 3 ] 771 | } 772 | 773 | } 774 | , { 775 | "patchline" : { 776 | "destination" : [ "obj-36", 3 ], 777 | "source" : [ "obj-11", 2 ] 778 | } 779 | 780 | } 781 | , { 782 | "patchline" : { 783 | "destination" : [ "obj-36", 2 ], 784 | "source" : [ "obj-11", 1 ] 785 | } 786 | 787 | } 788 | , { 789 | "patchline" : { 790 | "destination" : [ "obj-36", 1 ], 791 | "source" : [ "obj-11", 0 ] 792 | } 793 | 794 | } 795 | , { 796 | "patchline" : { 797 | "destination" : [ "obj-33", 1 ], 798 | "source" : [ "obj-13", 0 ] 799 | } 800 | 801 | } 802 | , { 803 | "patchline" : { 804 | "destination" : [ "obj-4", 0 ], 805 | "source" : [ "obj-14", 0 ] 806 | } 807 | 808 | } 809 | , { 810 | "patchline" : { 811 | "destination" : [ "obj-13", 0 ], 812 | "order" : 3, 813 | "source" : [ "obj-17", 0 ] 814 | } 815 | 816 | } 817 | , { 818 | "patchline" : { 819 | "destination" : [ "obj-15", 0 ], 820 | "order" : 2, 821 | "source" : [ "obj-17", 0 ] 822 | } 823 | 824 | } 825 | , { 826 | "patchline" : { 827 | "destination" : [ "obj-16", 0 ], 828 | "order" : 1, 829 | "source" : [ "obj-17", 0 ] 830 | } 831 | 832 | } 833 | , { 834 | "patchline" : { 835 | "destination" : [ "obj-25", 0 ], 836 | "order" : 0, 837 | "source" : [ "obj-17", 0 ] 838 | } 839 | 840 | } 841 | , { 842 | "patchline" : { 843 | "destination" : [ "obj-17", 0 ], 844 | "source" : [ "obj-18", 0 ] 845 | } 846 | 847 | } 848 | , { 849 | "patchline" : { 850 | "destination" : [ "obj-9", 0 ], 851 | "source" : [ "obj-19", 0 ] 852 | } 853 | 854 | } 855 | , { 856 | "patchline" : { 857 | "destination" : [ "obj-4", 0 ], 858 | "source" : [ "obj-2", 0 ] 859 | } 860 | 861 | } 862 | , { 863 | "patchline" : { 864 | "destination" : [ "obj-14", 0 ], 865 | "order" : 0, 866 | "source" : [ "obj-20", 0 ] 867 | } 868 | 869 | } 870 | , { 871 | "patchline" : { 872 | "destination" : [ "obj-24", 0 ], 873 | "order" : 1, 874 | "source" : [ "obj-20", 0 ] 875 | } 876 | 877 | } 878 | , { 879 | "patchline" : { 880 | "destination" : [ "obj-6", 0 ], 881 | "order" : 2, 882 | "source" : [ "obj-20", 0 ] 883 | } 884 | 885 | } 886 | , { 887 | "patchline" : { 888 | "destination" : [ "obj-13", 0 ], 889 | "order" : 3, 890 | "source" : [ "obj-21", 0 ] 891 | } 892 | 893 | } 894 | , { 895 | "patchline" : { 896 | "destination" : [ "obj-15", 0 ], 897 | "order" : 2, 898 | "source" : [ "obj-21", 0 ] 899 | } 900 | 901 | } 902 | , { 903 | "patchline" : { 904 | "destination" : [ "obj-16", 0 ], 905 | "order" : 1, 906 | "source" : [ "obj-21", 0 ] 907 | } 908 | 909 | } 910 | , { 911 | "patchline" : { 912 | "destination" : [ "obj-25", 0 ], 913 | "order" : 0, 914 | "source" : [ "obj-21", 0 ] 915 | } 916 | 917 | } 918 | , { 919 | "patchline" : { 920 | "destination" : [ "obj-5", 0 ], 921 | "source" : [ "obj-22", 0 ] 922 | } 923 | 924 | } 925 | , { 926 | "patchline" : { 927 | "destination" : [ "obj-4", 0 ], 928 | "source" : [ "obj-23", 0 ] 929 | } 930 | 931 | } 932 | , { 933 | "patchline" : { 934 | "destination" : [ "obj-19", 0 ], 935 | "source" : [ "obj-24", 0 ] 936 | } 937 | 938 | } 939 | , { 940 | "patchline" : { 941 | "destination" : [ "obj-3", 0 ], 942 | "source" : [ "obj-26", 0 ] 943 | } 944 | 945 | } 946 | , { 947 | "patchline" : { 948 | "destination" : [ "obj-4", 0 ], 949 | "source" : [ "obj-27", 0 ] 950 | } 951 | 952 | } 953 | , { 954 | "patchline" : { 955 | "destination" : [ "obj-31", 0 ], 956 | "source" : [ "obj-28", 1 ] 957 | } 958 | 959 | } 960 | , { 961 | "patchline" : { 962 | "destination" : [ "obj-4", 0 ], 963 | "source" : [ "obj-29", 0 ] 964 | } 965 | 966 | } 967 | , { 968 | "patchline" : { 969 | "destination" : [ "obj-3", 0 ], 970 | "source" : [ "obj-30", 0 ] 971 | } 972 | 973 | } 974 | , { 975 | "patchline" : { 976 | "destination" : [ "obj-23", 0 ], 977 | "order" : 0, 978 | "source" : [ "obj-31", 0 ] 979 | } 980 | 981 | } 982 | , { 983 | "patchline" : { 984 | "destination" : [ "obj-26", 0 ], 985 | "midpoints" : [ 409.0, 417.0, 106.5, 417.0 ], 986 | "order" : 1, 987 | "source" : [ "obj-31", 0 ] 988 | } 989 | 990 | } 991 | , { 992 | "patchline" : { 993 | "destination" : [ "obj-19", 7 ], 994 | "source" : [ "obj-33", 7 ] 995 | } 996 | 997 | } 998 | , { 999 | "patchline" : { 1000 | "destination" : [ "obj-19", 6 ], 1001 | "source" : [ "obj-33", 6 ] 1002 | } 1003 | 1004 | } 1005 | , { 1006 | "patchline" : { 1007 | "destination" : [ "obj-19", 5 ], 1008 | "source" : [ "obj-33", 5 ] 1009 | } 1010 | 1011 | } 1012 | , { 1013 | "patchline" : { 1014 | "destination" : [ "obj-19", 4 ], 1015 | "source" : [ "obj-33", 4 ] 1016 | } 1017 | 1018 | } 1019 | , { 1020 | "patchline" : { 1021 | "destination" : [ "obj-19", 3 ], 1022 | "source" : [ "obj-33", 3 ] 1023 | } 1024 | 1025 | } 1026 | , { 1027 | "patchline" : { 1028 | "destination" : [ "obj-19", 2 ], 1029 | "source" : [ "obj-33", 2 ] 1030 | } 1031 | 1032 | } 1033 | , { 1034 | "patchline" : { 1035 | "destination" : [ "obj-19", 1 ], 1036 | "source" : [ "obj-33", 1 ] 1037 | } 1038 | 1039 | } 1040 | , { 1041 | "patchline" : { 1042 | "destination" : [ "obj-19", 0 ], 1043 | "source" : [ "obj-33", 0 ] 1044 | } 1045 | 1046 | } 1047 | , { 1048 | "patchline" : { 1049 | "destination" : [ "obj-33", 0 ], 1050 | "order" : 2, 1051 | "source" : [ "obj-34", 0 ] 1052 | } 1053 | 1054 | } 1055 | , { 1056 | "patchline" : { 1057 | "destination" : [ "obj-36", 0 ], 1058 | "order" : 0, 1059 | "source" : [ "obj-34", 0 ] 1060 | } 1061 | 1062 | } 1063 | , { 1064 | "patchline" : { 1065 | "destination" : [ "obj-43", 0 ], 1066 | "order" : 1, 1067 | "source" : [ "obj-34", 0 ] 1068 | } 1069 | 1070 | } 1071 | , { 1072 | "patchline" : { 1073 | "destination" : [ "obj-34", 0 ], 1074 | "source" : [ "obj-35", 0 ] 1075 | } 1076 | 1077 | } 1078 | , { 1079 | "patchline" : { 1080 | "destination" : [ "obj-1", 1 ], 1081 | "source" : [ "obj-36", 0 ] 1082 | } 1083 | 1084 | } 1085 | , { 1086 | "patchline" : { 1087 | "destination" : [ "obj-4", 0 ], 1088 | "source" : [ "obj-37", 0 ] 1089 | } 1090 | 1091 | } 1092 | , { 1093 | "patchline" : { 1094 | "destination" : [ "obj-33", 0 ], 1095 | "source" : [ "obj-38", 0 ] 1096 | } 1097 | 1098 | } 1099 | , { 1100 | "patchline" : { 1101 | "destination" : [ "obj-35", 0 ], 1102 | "source" : [ "obj-39", 0 ] 1103 | } 1104 | 1105 | } 1106 | , { 1107 | "patchline" : { 1108 | "destination" : [ "obj-12", 0 ], 1109 | "order" : 0, 1110 | "source" : [ "obj-4", 0 ] 1111 | } 1112 | 1113 | } 1114 | , { 1115 | "patchline" : { 1116 | "destination" : [ "obj-46", 1 ], 1117 | "source" : [ "obj-4", 1 ] 1118 | } 1119 | 1120 | } 1121 | , { 1122 | "patchline" : { 1123 | "destination" : [ "obj-7", 0 ], 1124 | "order" : 2, 1125 | "source" : [ "obj-4", 0 ] 1126 | } 1127 | 1128 | } 1129 | , { 1130 | "patchline" : { 1131 | "destination" : [ "obj-8", 0 ], 1132 | "order" : 1, 1133 | "source" : [ "obj-4", 0 ] 1134 | } 1135 | 1136 | } 1137 | , { 1138 | "patchline" : { 1139 | "destination" : [ "obj-4", 0 ], 1140 | "source" : [ "obj-41", 0 ] 1141 | } 1142 | 1143 | } 1144 | , { 1145 | "patchline" : { 1146 | "destination" : [ "obj-1", 0 ], 1147 | "source" : [ "obj-43", 0 ] 1148 | } 1149 | 1150 | } 1151 | , { 1152 | "patchline" : { 1153 | "destination" : [ "obj-43", 8 ], 1154 | "source" : [ "obj-44", 7 ] 1155 | } 1156 | 1157 | } 1158 | , { 1159 | "patchline" : { 1160 | "destination" : [ "obj-43", 7 ], 1161 | "source" : [ "obj-44", 6 ] 1162 | } 1163 | 1164 | } 1165 | , { 1166 | "patchline" : { 1167 | "destination" : [ "obj-43", 6 ], 1168 | "source" : [ "obj-44", 5 ] 1169 | } 1170 | 1171 | } 1172 | , { 1173 | "patchline" : { 1174 | "destination" : [ "obj-43", 5 ], 1175 | "source" : [ "obj-44", 4 ] 1176 | } 1177 | 1178 | } 1179 | , { 1180 | "patchline" : { 1181 | "destination" : [ "obj-43", 4 ], 1182 | "source" : [ "obj-44", 3 ] 1183 | } 1184 | 1185 | } 1186 | , { 1187 | "patchline" : { 1188 | "destination" : [ "obj-43", 3 ], 1189 | "source" : [ "obj-44", 2 ] 1190 | } 1191 | 1192 | } 1193 | , { 1194 | "patchline" : { 1195 | "destination" : [ "obj-43", 2 ], 1196 | "source" : [ "obj-44", 1 ] 1197 | } 1198 | 1199 | } 1200 | , { 1201 | "patchline" : { 1202 | "destination" : [ "obj-43", 1 ], 1203 | "source" : [ "obj-44", 0 ] 1204 | } 1205 | 1206 | } 1207 | , { 1208 | "patchline" : { 1209 | "destination" : [ "obj-2", 0 ], 1210 | "source" : [ "obj-5", 1 ] 1211 | } 1212 | 1213 | } 1214 | , { 1215 | "patchline" : { 1216 | "destination" : [ "obj-21", 0 ], 1217 | "source" : [ "obj-5", 0 ] 1218 | } 1219 | 1220 | } 1221 | , { 1222 | "patchline" : { 1223 | "destination" : [ "obj-3", 0 ], 1224 | "source" : [ "obj-6", 0 ] 1225 | } 1226 | 1227 | } 1228 | , { 1229 | "patchline" : { 1230 | "destination" : [ "obj-11", 0 ], 1231 | "source" : [ "obj-7", 0 ] 1232 | } 1233 | 1234 | } 1235 | , { 1236 | "patchline" : { 1237 | "destination" : [ "obj-10", 1 ], 1238 | "source" : [ "obj-8", 0 ] 1239 | } 1240 | 1241 | } 1242 | , { 1243 | "patchline" : { 1244 | "destination" : [ "obj-3", 0 ], 1245 | "order" : 1, 1246 | "source" : [ "obj-9", 0 ] 1247 | } 1248 | 1249 | } 1250 | , { 1251 | "patchline" : { 1252 | "destination" : [ "obj-44", 0 ], 1253 | "order" : 0, 1254 | "source" : [ "obj-9", 0 ] 1255 | } 1256 | 1257 | } 1258 | ], 1259 | "parameters" : { 1260 | "obj-7" : [ "mc.live.gain~", "mc.live.gain~", 0 ], 1261 | "obj-9" : [ "mc.live.gain~[1]", "mc.live.gain~", 0 ], 1262 | "parameterbanks" : { 1263 | 1264 | } 1265 | , 1266 | "inherited_shortname" : 1 1267 | } 1268 | , 1269 | "dependency_cache" : [ { 1270 | "name" : "drumLoop.aif", 1271 | "bootpath" : "C74:/media/msp", 1272 | "type" : "AIFF", 1273 | "implicit" : 1 1274 | } 1275 | , { 1276 | "name" : "cherokee.aif", 1277 | "bootpath" : "C74:/media/msp", 1278 | "type" : "AIFF", 1279 | "implicit" : 1 1280 | } 1281 | , { 1282 | "name" : "eroica.aiff", 1283 | "bootpath" : "C74:/docs/tutorial-patchers/msp-tut", 1284 | "type" : "AIFF", 1285 | "implicit" : 1 1286 | } 1287 | , { 1288 | "name" : "cello-f2.aif", 1289 | "bootpath" : "C74:/media/msp", 1290 | "type" : "AIFF", 1291 | "implicit" : 1 1292 | } 1293 | , { 1294 | "name" : "mc.rtpsend~.mxo", 1295 | "type" : "iLaX" 1296 | } 1297 | , { 1298 | "name" : "mc.rtpreceive~.mxo", 1299 | "type" : "iLaX" 1300 | } 1301 | ], 1302 | "autosave" : 0 1303 | } 1304 | 1305 | } 1306 | -------------------------------------------------------------------------------- /test_patch/sender_test.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 8, 6 | "minor" : 0, 7 | "revision" : 5, 8 | "architecture" : "x64", 9 | "modernui" : 1 10 | } 11 | , 12 | "classnamespace" : "box", 13 | "rect" : [ 161.0, 165.0, 805.0, 629.0 ], 14 | "bglocked" : 0, 15 | "openinpresentation" : 0, 16 | "default_fontsize" : 12.0, 17 | "default_fontface" : 0, 18 | "default_fontname" : "Arial", 19 | "gridonopen" : 1, 20 | "gridsize" : [ 15.0, 15.0 ], 21 | "gridsnaponopen" : 1, 22 | "objectsnaponopen" : 1, 23 | "statusbarvisible" : 2, 24 | "toolbarvisible" : 1, 25 | "lefttoolbarpinned" : 0, 26 | "toptoolbarpinned" : 0, 27 | "righttoolbarpinned" : 0, 28 | "bottomtoolbarpinned" : 0, 29 | "toolbars_unpinned_last_save" : 0, 30 | "tallnewobj" : 0, 31 | "boxanimatetime" : 200, 32 | "enablehscroll" : 1, 33 | "enablevscroll" : 1, 34 | "devicewidth" : 0.0, 35 | "description" : "", 36 | "digest" : "", 37 | "tags" : "", 38 | "style" : "", 39 | "subpatcher_template" : "", 40 | "boxes" : [ { 41 | "box" : { 42 | "id" : "obj-2", 43 | "maxclass" : "newobj", 44 | "numinlets" : 1, 45 | "numoutlets" : 1, 46 | "outlettype" : [ "" ], 47 | "patching_rect" : [ 169.0, 208.0, 100.0, 22.0 ] 48 | } 49 | 50 | } 51 | , { 52 | "box" : { 53 | "data" : { 54 | "clips" : [ { 55 | "absolutepath" : "drumLoop.aif", 56 | "filename" : "drumLoop.aif", 57 | "filekind" : "audiofile", 58 | "loop" : 0, 59 | "content_state" : { 60 | "originaltempo" : [ 120.0 ], 61 | "pitchshift" : [ 1.0 ], 62 | "quality" : [ "basic" ], 63 | "mode" : [ "basic" ], 64 | "originallength" : [ 0.0, "ticks" ], 65 | "pitchcorrection" : [ 0 ], 66 | "originallengthms" : [ 0.0 ], 67 | "basictuning" : [ 440 ], 68 | "timestretch" : [ 0 ], 69 | "formant" : [ 1.0 ], 70 | "followglobaltempo" : [ 0 ], 71 | "formantcorrection" : [ 0 ], 72 | "play" : [ 0 ], 73 | "slurtime" : [ 0.0 ], 74 | "speed" : [ 1.0 ] 75 | } 76 | 77 | } 78 | ] 79 | } 80 | , 81 | "id" : "obj-6", 82 | "maxclass" : "playlist~", 83 | "numinlets" : 1, 84 | "numoutlets" : 5, 85 | "outlettype" : [ "signal", "signal", "signal", "", "dictionary" ], 86 | "patching_rect" : [ 69.0, 79.0, 150.0, 30.0 ] 87 | } 88 | 89 | } 90 | , { 91 | "box" : { 92 | "id" : "obj-5", 93 | "lastchannelcount" : 4, 94 | "maxclass" : "mc.live.gain~", 95 | "numinlets" : 1, 96 | "numoutlets" : 4, 97 | "outlettype" : [ "multichannelsignal", "", "float", "list" ], 98 | "parameter_enable" : 1, 99 | "patching_rect" : [ 69.0, 290.0, 48.0, 136.0 ], 100 | "saved_attribute_attributes" : { 101 | "valueof" : { 102 | "parameter_mmax" : 6.0, 103 | "parameter_shortname" : "mc.live.gain~", 104 | "parameter_type" : 0, 105 | "parameter_unitstyle" : 4, 106 | "parameter_mmin" : -70.0, 107 | "parameter_longname" : "mc.live.gain~" 108 | } 109 | 110 | } 111 | , 112 | "varname" : "mc.live.gain~" 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "id" : "obj-4", 119 | "maxclass" : "newobj", 120 | "numinlets" : 4, 121 | "numoutlets" : 1, 122 | "outlettype" : [ "multichannelsignal" ], 123 | "patching_rect" : [ 69.0, 202.0, 70.0, 22.0 ], 124 | "text" : "mc.pack~ 4" 125 | } 126 | 127 | } 128 | , { 129 | "box" : { 130 | "id" : "obj-3", 131 | "maxclass" : "attrui", 132 | "numinlets" : 1, 133 | "numoutlets" : 1, 134 | "outlettype" : [ "" ], 135 | "patching_rect" : [ 296.0, 277.0, 150.0, 22.0 ] 136 | } 137 | 138 | } 139 | , { 140 | "box" : { 141 | "id" : "obj-1", 142 | "maxclass" : "newobj", 143 | "numinlets" : 1, 144 | "numoutlets" : 0, 145 | "patching_rect" : [ 69.0, 476.0, 359.0, 22.0 ], 146 | "text" : "mc.rtpsend~.mxo @channels 2 @address 127.0.0.1 @port 30000" 147 | } 148 | 149 | } 150 | ], 151 | "lines" : [ { 152 | "patchline" : { 153 | "destination" : [ "obj-1", 0 ], 154 | "source" : [ "obj-3", 0 ] 155 | } 156 | 157 | } 158 | , { 159 | "patchline" : { 160 | "destination" : [ "obj-5", 0 ], 161 | "source" : [ "obj-4", 0 ] 162 | } 163 | 164 | } 165 | , { 166 | "patchline" : { 167 | "destination" : [ "obj-1", 0 ], 168 | "source" : [ "obj-5", 0 ] 169 | } 170 | 171 | } 172 | , { 173 | "patchline" : { 174 | "destination" : [ "obj-4", 0 ], 175 | "source" : [ "obj-6", 0 ] 176 | } 177 | 178 | } 179 | ], 180 | "parameters" : { 181 | "obj-5" : [ "mc.live.gain~", "mc.live.gain~", 0 ], 182 | "parameterbanks" : { 183 | 184 | } 185 | 186 | } 187 | , 188 | "dependency_cache" : [ { 189 | "name" : "drumLoop.aif", 190 | "bootpath" : "C74:/media/msp", 191 | "type" : "AIFF", 192 | "implicit" : 1 193 | } 194 | , { 195 | "name" : "mc.rtpsend~.mxo", 196 | "type" : "iLaX" 197 | } 198 | ], 199 | "autosave" : 0 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /vcpkg-helper/x64-osx-rel.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_CRT_LINKAGE dynamic) 3 | set(VCPKG_LIBRARY_LINKAGE static) 4 | 5 | set(VCPKG_CMAKE_SYSTEM_NAME Darwin) 6 | set(VCPKG_BUILD_TYPE release) 7 | -------------------------------------------------------------------------------- /vcpkg-helper/x64-windows-rel.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_CRT_LINKAGE dynamic) 3 | set(VCPKG_LIBRARY_LINKAGE dynamic) 4 | set(VCPKG_BUILD_TYPE release) 5 | 6 | -------------------------------------------------------------------------------- /vcpkg-helper/x64-windows-static-md.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_CRT_LINKAGE dynamic) 3 | set(VCPKG_LIBRARY_LINKAGE static) 4 | set(VCPKG_BUILD_TYPE release) 5 | 6 | -------------------------------------------------------------------------------- /vcpkg-helper/x64-windows-static-rel.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_TARGET_ARCHITECTURE x64) 2 | set(VCPKG_CRT_LINKAGE static) 3 | set(VCPKG_LIBRARY_LINKAGE static) 4 | set(VCPKG_BUILD_TYPE release) 5 | 6 | --------------------------------------------------------------------------------