Repository: iamscottxu/obs-rtspserver Branch: master Commit: 1a1924a6ce48 Files: 180 Total size: 621.2 KB Directory structure: gitextract_rjgx6hb3/ ├── .github/ │ └── workflows/ │ ├── changelogConfig.json │ ├── main.yml │ ├── main.yml.bak │ ├── test.yml │ └── text.yml.bak ├── .gitignore ├── .gitmodules ├── CI/ │ ├── build-linux.sh │ ├── build-macos.sh │ ├── build-windows.ps1 │ ├── include/ │ │ ├── Brewfile │ │ ├── Xcnotary │ │ ├── build_environment.ps1 │ │ ├── build_environment.ps1.in │ │ ├── build_environment.sh │ │ ├── build_environment.sh.in │ │ ├── build_support.sh │ │ ├── build_support_linux.sh │ │ ├── build_support_macos.sh │ │ └── build_support_windows.ps1 │ ├── linux/ │ │ ├── 01_install_dependencies.sh │ │ ├── 02_build_obs_libs.sh │ │ ├── 03_build_plugin.sh │ │ └── 04_package_plugin.sh │ ├── macos/ │ │ ├── 01_install_dependencies.sh │ │ ├── 02_build_obs_libs.sh │ │ ├── 03_build_plugin.sh │ │ └── 04_package_plugin.sh │ ├── utility/ │ │ ├── check-format.sh │ │ └── formatcode.sh │ └── windows/ │ ├── 01_install_dependencies.ps1 │ ├── 02_build_obs_libs.ps1 │ ├── 03_build_plugin.ps1 │ └── 04_package_plugin.ps1 ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README_de-DE.md ├── README_es-ES.md ├── README_fr-FR.md ├── README_it-IT.md ├── README_ja-JP.md ├── README_ko-KR.md ├── README_nl-NL.md ├── README_ru-RU.md ├── README_zh-CN.md ├── README_zh-TW.md ├── bundle/ │ ├── installer-macos.pkgproj.in │ └── macOS/ │ ├── Plugin-Info.plist.in │ └── entitlements.plist ├── data/ │ └── locale/ │ ├── de-DE.ini │ ├── en-US.ini │ ├── es-ES.ini │ ├── fr-FR.ini │ ├── it-IT.ini │ ├── ja-JP.ini │ ├── ko-KR.ini │ ├── nl-NL.ini │ ├── ru-RU.ini │ ├── zh-CN.ini │ └── zh-TW.ini ├── external/ │ ├── BuildHelper.cmake │ ├── Findlibobs.cmake │ ├── Findobs-frontend-api.cmake │ ├── GitInfoHelper.cmake │ ├── ObsPluginHelpers.cmake │ ├── ObsPluginHelpers.cmake.bak │ └── ObsPluginHelpers.cmake.bak.bak ├── helper.h ├── installer/ │ ├── function/ │ │ └── unprevious.nsi │ ├── installer.nsi │ ├── locale/ │ │ ├── de-DE.nsi │ │ ├── en-US.nsi │ │ ├── es-ES.nsi │ │ ├── fr-FR.nsi │ │ ├── it-IT.nsi │ │ ├── ja-JP.nsi │ │ ├── ko-KR.nsi │ │ ├── nl-NL.nsi │ │ ├── zh-CN.nsi │ │ └── zh-TW.nsi │ └── locale.nsi ├── rtsp-server/ │ ├── 3rdpart/ │ │ ├── libb64/ │ │ │ └── CMakeLists.txt │ │ └── md5/ │ │ ├── COPYING │ │ └── md5.hpp │ ├── CMakeLists.txt │ ├── net/ │ │ ├── Acceptor.cpp │ │ ├── Acceptor.h │ │ ├── BufferReader.cpp │ │ ├── BufferReader.h │ │ ├── BufferWriter.cpp │ │ ├── BufferWriter.h │ │ ├── Channel.h │ │ ├── EpollTaskScheduler.cpp │ │ ├── EpollTaskScheduler.h │ │ ├── EventLoop.cpp │ │ ├── EventLoop.h │ │ ├── KqueueTaskScheduler.cpp │ │ ├── KqueueTaskScheduler.h │ │ ├── Logger.cpp │ │ ├── Logger.h │ │ ├── MemoryManager.cpp │ │ ├── MemoryManager.h │ │ ├── Pipe.cpp │ │ ├── Pipe.h │ │ ├── RingBuffer.h │ │ ├── SelectTaskScheduler.cpp │ │ ├── SelectTaskScheduler.h │ │ ├── Socket.h │ │ ├── SocketUtil.cpp │ │ ├── SocketUtil.h │ │ ├── TaskScheduler.cpp │ │ ├── TaskScheduler.h │ │ ├── TcpConnection.cpp │ │ ├── TcpConnection.h │ │ ├── TcpServer.cpp │ │ ├── TcpServer.h │ │ ├── TcpSocket.cpp │ │ ├── TcpSocket.h │ │ ├── ThreadSafeQueue.h.bak │ │ ├── Timer.cpp │ │ ├── Timer.h │ │ ├── Timestamp.cpp │ │ └── Timestamp.h │ └── xop/ │ ├── AACSource.cpp │ ├── AACSource.h │ ├── Base64Encode.cpp │ ├── Base64Encode.h │ ├── BaseMd5.cpp │ ├── BaseMd5.h │ ├── CngMd5.cpp │ ├── CngMd5.h │ ├── DigestAuthentication.cpp │ ├── DigestAuthentication.h │ ├── G711ASource.cpp │ ├── G711ASource.h │ ├── H264NalUnit.cpp │ ├── H264NalUnit.h │ ├── H264Source.cpp │ ├── H264Source.h │ ├── H265NalUnit.cpp │ ├── H265NalUnit.h │ ├── H265Source.cpp │ ├── H265Source.h │ ├── MacMd5.cpp │ ├── MacMd5.h │ ├── Md5.cpp │ ├── Md5.h │ ├── MediaSession.cpp │ ├── MediaSession.h │ ├── MediaSource.h │ ├── Nal.cpp │ ├── Nal.h │ ├── NalUnit.cpp │ ├── NalUnit.h │ ├── RtpConnection.cpp │ ├── RtpConnection.h │ ├── RtspConnection.cpp │ ├── RtspConnection.h │ ├── RtspMessage.cpp │ ├── RtspMessage.h │ ├── RtspPusher.cpp │ ├── RtspPusher.h │ ├── RtspServer.cpp │ ├── RtspServer.h │ ├── VP8Source.cpp │ ├── VP8Source.h │ ├── media.h │ ├── rtp.h │ └── rtsp.h ├── rtsp_main.cpp ├── rtsp_output.cpp ├── rtsp_output.h ├── rtsp_output_helper.cpp ├── rtsp_output_helper.h ├── rtspoutput.rc.in ├── threadsafe_queue.h └── ui/ ├── rtsp_properties.cpp ├── rtsp_properties.hpp └── rtsp_properties.ui ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/changelogConfig.json ================================================ { "typeLabels": [ { "types": ["breaking"], "title": "⚡️ Breaking Changes" }, { "types": ["feat", "feature"], "title": "🚀 New Features" }, { "types": ["fix", "bugfix"], "title": "💊 Bugfixes" }, { "types": ["improvements", "enhancement"], "title": "🔨 Improvements" }, { "types": ["perf"], "title": "🏎️ Performance Improvements" }, { "types": ["build", "ci"], "title": "🏗️ Build System" }, { "types": ["refactor"], "title": "🪚 Refactors" }, { "types": ["doc", "docs"], "title": "📚 Documentation Changes" }, { "types": ["test", "tests"], "title": "🔍 Tests" }, { "types": ["style"], "title": "💅 Code Style Changes" }, { "types": ["chore"], "title": "🧹 Chores" }, { "types": ["other"], "title": "Other Changes" } ], "bumpLabels": [ { "title": "major", "types": ["breaking"] }, { "title": "minor", "types": ["feat", "feature"] }, { "title": "patch", "types": [] } ], "issuesUrl": "", "sortOrder": "desc", "emptyMessage": "No changes" } ================================================ FILE: .github/workflows/main.yml ================================================ name: 'CI Release' on: #release: # types: [published] push: tags: - v[1-9].[0-9]+.[0-9]+ - v[1-9][0-9]+.[0-9]+.[0-9]+ - v[1-9].[0-9]+.[0-9]+-[a-z0-9]+ - v[1-9][0-9]+.[0-9]+.[0-9]+-[a-z0-9]+ env: PRODUCT_NAME: 'obs-rtspserver' DEPS_VERSION_MAC: '2023-11-03' DEPS_HASH_MAC: '90c2fc069847ec2768dcc867c1c63b112c615ed845a907dc44acab7a97181974' #QT_VERSION_MAC: '2022-02-13' QT_HASH_MAC: 'ba4a7152848da0053f63427a2a2cb0a199af3992997c0db08564df6f48c9db98' DEPS_VERSION_WIN: '2023-11-03' DEPS_X64_HASH_WIN: 'd0825a6fb65822c993a3059edfba70d72d2e632ef74893588cf12b1f0d329ce6' DEPS_X86_HASH_WIN: 'b69c864275189464483c240ef3f25ea16fba3050b136fe6c3db6e9ee63036683' QT_X64_HASH_WIN: 'bc57dedf76b47119a6dce0435a2f21b35b08c8f2948b1cb34a157320f77732d1' QT_X86_HASH_WIN: '50129f9836ef987c23db2e0535085fa2d52474ef0de44bc11c9df6cfa602b785' #QT_VERSION_WIN: '5.15.2' NSIS_VERSION_WIN: '3.09' jobs: get_obs_info: name: '01 - Get obs-studio last release info' runs-on: [ubuntu-latest] outputs: latest_id: ${{ steps.latest_release.outputs.id }} latest_url: ${{ steps.latest_release.outputs.url }} latest_html_url: ${{ steps.latest_release.outputs.html_url }} latest_upload_url: ${{ steps.latest_release.outputs.upload_url }} latest_name: ${{ steps.latest_release.outputs.name }} latest_tag_name: ${{ steps.latest_release.outputs.tag_name }} latest_target_commitish: ${{ steps.latest_release.outputs.target_commitish }} latest_created_at: ${{ steps.latest_release.outputs.created_at }} latest_published_at: ${{ steps.latest_release.outputs.published_at }} steps: - name: Get latest release info id: latest_release uses: cardinalby/git-get-release-action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: repo: 'obsproject/obs-studio' latest: true get_plugin_info: name: '01 - Get plugin git info' runs-on: [ubuntu-latest] outputs: git_tag_name: ${{ steps.tag_name.outputs.tag }} steps: - name: 'Get plugin git tag' uses: devops-actions/action-get-tag@v1.0.2 id: tag_name clang_check: name: '02 - Code format check' runs-on: [ubuntu-latest] needs: [get_plugin_info] steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Install clang-format' run: sudo apt-get install -y clang-format-12 - name: 'Run clang-format' working-directory: 'plugin' run: | source CI/utility/formatcode.sh source CI/utility/check-format.sh windows_build: name: '03 - Windows (Latest)' runs-on: [windows-2022] needs: [get_obs_info, get_plugin_info, clang_check] strategy: matrix: arch: [64, 32] env: #CMAKE_GENERATOR: "Visual Studio 17 2022" CMAKE_SYSTEM_VERSION: "10.0.22000.0" OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" steps: - name: 'Add msbuild to PATH' uses: microsoft/setup-msbuild@v1 - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}' fetch-depth: 0 submodules: 'recursive' - name: "Install Dependencies" working-directory: 'plugin' run: CI/windows/01_install_dependencies.ps1 -BuildArch ${{ matrix.arch }}-bit -NoChoco -InstallList "obs-deps","qt-deps","obs-studio" - name: 'Build libobs and obs-frontend-api' working-directory: 'plugin' run: CI/windows/02_build_obs_libs.ps1 -BuildArch "${{ matrix.arch }}-bit" - name: 'Build plugin' working-directory: 'plugin' run: CI/windows/03_build_plugin.ps1 -BuildArch "${{ matrix.arch }}-bit" - name: 'Create build artifact' working-directory: 'plugin' run: CI/windows/04_package_plugin.ps1 -BuildArch "${{ matrix.arch }}-bit" - name: 'Upload build Artifact' uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-${{ matrix.arch }}' path: '${{ github.workspace }}/plugin/*-windows-*.zip' windows_package: name: '04 - Windows Installer' runs-on: [windows-2022] needs: [get_plugin_info, windows_build] env: #CMAKE_GENERATOR: "Visual Studio 17 2022" CMAKE_SYSTEM_VERSION: "10.0.22000.0" OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Download 64-bit artifact' uses: actions/download-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-64' path: 'plugin' - name: 'Download 32-bit artifact' uses: actions/download-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-32' path: 'plugin' - name: "Install Dependencies" working-directory: 'plugin' run: CI/windows/01_install_dependencies.ps1 -BuildArch 64-bit -NoChoco -InstallList "nsis" - name: 'Build NSIS installer' working-directory: 'plugin' run: | Get-ChildItem -Filter "*-windows-x86.zip" -File | Expand-Archive -Force -DestinationPath ./release/ Get-ChildItem -Filter "*-windows-x64.zip" -File | Expand-Archive -Force -DestinationPath ./release/ CI/windows/04_package_plugin.ps1 -BuildArch 64-bit -BuildInstaller -CombinedArchs - name: 'Upload build Artifact' uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-release' path: '${{ github.workspace }}/plugin/${{ env.PRODUCT_NAME }}-*-windows-all*.*' linux_build: name: '03 - Linux (Ubuntu)' runs-on: ${{ matrix.ubuntu }} needs: [get_obs_info, get_plugin_info, clang_check] strategy: matrix: #ubuntu: ['ubuntu-20.04', 'ubuntu-22.04'] ubuntu: ['ubuntu-22.04'] env: OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" defaults: run: shell: bash steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}' fetch-depth: 0 submodules: 'recursive' - name: "Install Dependencies" working-directory: 'plugin' run: source CI/linux/01_install_dependencies.sh --disable-pipewire - name: 'Build libobs and obs-frontend-api' working-directory: 'plugin' run: source CI/linux/02_build_obs_libs.sh --disable-pipewire - name: 'Build plugin' working-directory: 'plugin' run: source CI/linux/03_build_plugin.sh - name: 'Create build artifact' working-directory: 'plugin' run: source CI/linux/04_package_plugin.sh - name: 'Upload build Artifact' uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-linux-${{ matrix.ubuntu }}' path: '${{ github.workspace }}/plugin/build/${{ env.PRODUCT_NAME }}-*-linux.*' macos_build: name: '03 - macOS (Latest)' runs-on: [macos-latest] strategy: matrix: arch: ['universal', 'x86_64', 'arm64'] needs: [get_obs_info, get_plugin_info, clang_check] env: MACOSX_DEPLOYMENT_TARGET: '10.13' BLOCKED_FORMULAS: 'speexdsp' OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" defaults: run: shell: bash steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}' fetch-depth: 0 submodules: 'recursive' - name: 'Setup build environment' run: | REMOVE_FORMULAS="" for FORMULA in ${{ env.BLOCKED_FORMULAS }}; do if [ -d "/usr/local/opt/${FORMULA}" ]; then REMOVE_FORMULAS="${REMOVE_FORMULAS}${FORMULA} " fi done if [ -n "${REMOVE_FORMULAS}" ]; then brew uninstall ${REMOVE_FORMULAS} fi - name: 'Install dependencies' working-directory: 'plugin' run: source CI/macos/01_install_dependencies.sh --architecture "${{ matrix.arch }}" - name: 'Build libobs and obs-frontend-api' working-directory: 'plugin' run: source CI/macos/02_build_obs_libs.sh --architecture "${{ matrix.arch }}" - name: 'Build plugin' working-directory: 'plugin' run: source CI/macos/03_build_plugin.sh --architecture "${{ matrix.arch }}" - name: 'Create build artifact' working-directory: 'plugin' run: source CI/macos/04_package_plugin.sh --architecture "${{ matrix.arch }}" - name: 'Upload build Artifact' uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-macos-${{ matrix.arch }}' path: '${{ github.workspace }}/plugin/build/*-macos-${{ matrix.arch }}.*' create_changelog: name: '02 - Create Changelog' runs-on: [ubuntu-latest] needs: [get_plugin_info] outputs: changelog: "${{ steps.create_changelog_text.outputs.body }}\n\n**Full Changelog**: https://github.com/iamscottxu/obs-rtspserver/compare/${{ steps.get_last_release.outputs.tag_name }}...${{ needs.get_plugin_info.outputs.git_tag_name }}" steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: actions/checkout@v4 with: ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' fetch-depth: 0 - name: 'Get last release info' id: get_last_release uses: cardinalby/git-get-release-action@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: latest: true - name: 'Create changelog text' id: create_changelog_text uses: dlavrenuek/conventional-changelog-action@v1.2.3 with: from: "${{ steps.get_last_release.outputs.tag_name }}" to: "${{ needs.get_plugin_info.outputs.git_tag_name }}" config-file: "${{ github.workspace }}/.github/workflows/changelogConfig.json" create_release: name: '05 - Create release' runs-on: [ubuntu-latest] needs: [get_plugin_info, windows_package, linux_build, macos_build, create_changelog] steps: - name: 'Check whether the version is prerelease' uses: MeilCli/regex-match@v1 id: prerelease_test with: search_string: ${{ needs.get_plugin_info.outputs.git_tag_name }} regex_pattern: "^v[1-9][0-9]*.[0-9]+.[0-9]+-[a-z0-9]+$" - name: 'Create release ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: ncipollo/release-action@v1 id: create_release with: #bodyFile: "body.md" body: "${{ needs.create_changelog.outputs.changelog }}" draft: true prerelease: ${{ steps.prerelease_test.outputs.matched }} name: "${{ env.PRODUCT_NAME }} ${{ needs.get_plugin_info.outputs.git_tag_name }}" token: ${{ secrets.GITHUB_TOKEN }} - name: 'Download release artifacts' uses: actions/download-artifact@v4 - name: 'Upload Windows .zip artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-windows-release/${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-all.zip asset_name: ${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows.zip asset_content_type: application/zip github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload Windows .exe artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-windows-release/${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-all-installer.exe asset_name: ${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-installer.exe asset_content_type: application/x-msdownload github_token: ${{ secrets.GITHUB_TOKEN }} #- name: 'Upload linux qt5 .tar.gz artifact to release' # uses: shogo82148/actions-upload-release-asset@v1 # with: # upload_url: ${{ steps.create_release.outputs.upload_url }} # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.tar.gz # asset_content_type: application/x-gzip # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .tar.gz artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.tar.gz asset_content_type: application/x-gzip github_token: ${{ secrets.GITHUB_TOKEN }} # - name: 'Upload linux qt5 .deb artifact to release' # uses: shogo82148/actions-upload-release-asset@v1 # with: # upload_url: ${{ steps.create_release.outputs.upload_url }} # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.deb # asset_content_type: application/vnd.debian.binary-package # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .deb artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.deb asset_content_type: application/vnd.debian.binary-package github_token: ${{ secrets.GITHUB_TOKEN }} # - name: 'Upload linux qt5 .rpm artifact to release' # uses: shogo82148/actions-upload-release-asset@v1 # with: # upload_url: ${{ steps.create_release.outputs.upload_url }} # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.rpm # asset_content_type: application/vnd.debian.binary-package # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .rpm artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.rpm asset_content_type: application/vnd.debian.binary-package github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos universal .zip artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-universal/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.zip asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.zip asset_content_type: application/zip github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos universal .pkg artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-universal/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.pkg asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.pkg asset_content_type: application/vnd.apple.installer+xml github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos x86_64 .zip artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-x86_64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.zip asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.zip asset_content_type: application/zip github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos x86_64 .pkg artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-x86_64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.pkg asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.pkg asset_content_type: application/vnd.apple.installer+xml github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos arm64 .zip artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-arm64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.zip asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.zip asset_content_type: application/zip github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload macos arm64 .pkg artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-arm64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.pkg asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.pkg asset_content_type: application/vnd.apple.installer+xml github_token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/main.yml.bak ================================================ name: 'CI Windows Release' on: release: types: [published] # push: # paths-ignore: # - 'docs/**' # tags: # - '[0-9]+.[0-9]+.[0-9]+' jobs: windows: name: 'Windows 32+64bit' runs-on: [windows-latest] env: QT_VERSION: '5.10.1' WINDOWS_DEPS_VERSION: '2017' CMAKE_GENERATOR: "Visual Studio 16 2019" CMAKE_SYSTEM_VERSION: "10.0" steps: - name: 'Add msbuild to PATH' uses: microsoft/setup-msbuild@v1.0.0 - name: 'Install prerequisite: QT' run: | curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C - 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" - name: 'Install prerequisite: Pre-built OBS dependencies' run: | curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C - 7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps" - name: 'Install prerequisite: NSIS' working-directory: ${{ github.workspace }} run: | Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') scoop bucket add extras scoop install nsis - name: 'Checkout OBS' uses: actions/checkout@v2 with: repository: obsproject/obs-studio path: ${{ github.workspace }}/obs-studio submodules: 'recursive' - name: 'Checkout obs-rtspserver' uses: actions/checkout@v2 with: path: ${{ github.workspace }}/obs-studio/plugins/obs-rtspserver submodules: 'recursive' - name: 'Get OBS-Studio git info' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | git fetch --prune --unshallow echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | git checkout ${{ env.OBS_GIT_TAG }} git submodule update - name: 'Get obs-rtspserver git info' shell: bash working-directory: ${{ github.workspace }}/obs-studio/plugins/obs-rtspserver run: | git fetch --prune --unshallow echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) # - name: 'Restore OBS 32-bit build v${{ env.OBS_GIT_TAG }} from cache' # id: build-cache-obs-32 # uses: actions/cache@v1 # env: # CACHE_NAME: 'build-cache-obs-32' # with: # path: ${{ github.workspace }}/obs-studio/build32 # key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} # restore-keys: | # ${{ runner.os }}-${{ env.CACHE_NAME }}- - name: 'Add obs-rtspserver Subdirectory' working-directory: ${{ github.workspace }}/obs-studio/plugins run: echo "add_subdirectory(obs-rtspserver)" >> .\CMakeLists.txt - name: 'Configure OBS 32-bit' # if: steps.build-cache-obs-32.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | mkdir .\build32 cd .\build32 cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DBUILD_CAPTIONS=YES -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. - name: 'Build obs-rtspserver 32-bit' # if: steps.build-cache-obs-32.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | msbuild /m /p:Configuration=RelWithDebInfo .\build32\plugins\obs-rtspserver\obs-rtspserver.vcxproj # - name: 'Restore OBS 64-bit build v${{ env.OBS_GIT_TAG }} from cache' # id: build-cache-obs-64 # uses: actions/cache@v1 # env: # CACHE_NAME: 'build-cache-obs-64' # with: # path: ${{ github.workspace }}/obs-studio/build64 # key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} # restore-keys: | # ${{ runner.os }}-${{ env.CACHE_NAME }}- - name: 'Configure OBS 64-bit' # if: steps.build-cache-obs-64.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | mkdir .\build64 cd .\build64 cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2017_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DBUILD_CAPTIONS=YES -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. - name: 'Build obs-rtspserver 64-bit' # if: steps.build-cache-obs-64.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | msbuild /m /p:Configuration=RelWithDebInfo .\build64\plugins\obs-rtspserver\obs-rtspserver.vcxproj - name: 'Set release filename' shell: bash run: | FILENAME="obs-rtspserver-${{ env.GIT_TAG }}-windows" echo "::set-env name=WIN_FILENAME::$FILENAME" - name: 'Package obs-rtspserver' working-directory: ${{ github.workspace }} run: | mkdir build-package\obs-plugins\64bit mkdir build-package\obs-plugins\32bit mkdir build-package\data\obs-plugins\obs-rtspserver\locale\ robocopy .\obs-studio\build64\plugins\obs-rtspserver\RelWithDebInfo\ .\build-package\obs-plugins\64bit\ obs-rtspserver.dll obs-rtspserver.pdb robocopy .\obs-studio\build32\plugins\obs-rtspserver\RelWithDebInfo\ .\build-package\obs-plugins\32bit\ obs-rtspserver.dll obs-rtspserver.pdb robocopy /E .\obs-studio\plugins\obs-rtspserver\data\ .\build-package\data\obs-plugins\obs-rtspserver\ * robocopy .\obs-studio\plugins\obs-rtspserver\ .\installer\ LICENSE robocopy .\obs-studio\plugins\obs-rtspserver\installer\ .\installer\ installer.nsi obs.ico mkdir release 7z a ".\release\${{ env.WIN_FILENAME }}.zip" ".\build-package\*" - name: 'Publish ${{ env.WIN_FILENAME }}.zip' id: create_release if: success() uses: actions/upload-artifact@v2-preview with: name: '${{ env.GIT_TAG }}-windows' path: ${{ github.workspace }}\release\*.zip - name: Build obs-rtspserver installer working-directory: ${{ github.workspace }}\installer run: makensis /DVERSION=${{ env.GIT_TAG }} .\installer.nsi - name: 'Publish ${{ env.WIN_FILENAME }}-windows-installer.exe' if: success() uses: actions/upload-artifact@v2-preview with: name: '${{ env.GIT_TAG }}-windows-installer' path: ${{ github.workspace }}\build-package\*.exe make-release: name: 'Upload release' runs-on: [ubuntu-latest] needs: [windows] steps: - name: 'Get the version' shell: bash id: get_version run: | echo ::set-env name=TAG_VERSION::${GITHUB_REF/refs\/tags\//} # - name: 'Create Release' # id: create_release # uses: actions/create-release@v1 # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # with: # tag_name: ${{ env.TAG_VERSION }} # release_name: obs-rtspserver ${{ env.TAG_VERSION }} # draft: false # prerelease: false - name: 'Download release artifacts' uses: actions/download-artifact@v2-preview - name: 'Upload Windows .zip artifact to release' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-windows/obs-rtspserver-${{ env.TAG_VERSION }}-windows.zip asset_name: obs-rtspserver-${{ env.TAG_VERSION }}-windows.zip asset_content_type: application/zip - name: 'Upload Windows .exe artifact to release' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-windows-installer/obs-rtspserver-${{ env.TAG_VERSION }}-windows-installer.exe asset_name: obs-rtspserver-${{ env.TAG_VERSION }}-windows-installer.exe asset_content_type: application/zip ================================================ FILE: .github/workflows/test.yml ================================================ name: 'test' on: #release: # types: [published] push: tags: - test[0-9]+ jobs: create-release: name: '05 - Create release' runs-on: [ubuntu-latest] steps: - name: 'Checkout plugin' uses: actions/checkout@v2.3.3 - name: 'Create release ${{ needs.get_plugin_info.outputs.git_tag_name }}' uses: ncipollo/release-action@v1 id: create_release with: #bodyFile: "body.md" #omitBody: true #omitBodyDuringUpdate: true draft: true prerelease: true token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/text.yml.bak ================================================ on: release: types: [published] jobs: one: runs-on: ubuntu-16.04 steps: - name: Dump GitHub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - name: Dump job context env: JOB_CONTEXT: ${{ toJson(job) }} run: echo "$JOB_CONTEXT" - name: Dump steps context env: STEPS_CONTEXT: ${{ toJson(steps) }} run: echo "$STEPS_CONTEXT" - name: Dump runner context env: RUNNER_CONTEXT: ${{ toJson(runner) }} run: echo "$RUNNER_CONTEXT" - name: Dump strategy context env: STRATEGY_CONTEXT: ${{ toJson(strategy) }} run: echo "$STRATEGY_CONTEXT" - name: Dump matrix context env: MATRIX_CONTEXT: ${{ toJson(matrix) }} run: echo "$MATRIX_CONTEXT" ================================================ FILE: .gitignore ================================================ *.aps rtspoutput.rc resource.h bundle/installer-macos.generated.pkgproj bundle/installer-macos.pkgproj bundle/LICENSE.txt bundle/macOS/Plugin-Info.plist installer/LICENSE build*/ release/ .vs/ .vscode/ ================================================ FILE: .gitmodules ================================================ [submodule "rtsp-server/3rdpart/libb64/libb64"] path = rtsp-server/3rdpart/libb64/libb64 url = https://github.com/libb64/libb64 ================================================ FILE: CI/build-linux.sh ================================================ #!/bin/bash ############################################################################## # Linux plugin build script ############################################################################## # # This script contains all steps necessary to: # # * Build libobs and obs-frontend-api with all required dependencies # * Build your plugin # * Create debian package # # Parameters: # -h, --help : Print usage help # -q, --quiet : Suppress most build process output # -v, --verbose : Enable more verbose build process output # -p, --package : Create installer for plugin # -b, --build-dir : Specify alternative build directory # (default: build) # # Environment Variables (optional): # OBS_VERSION : OBS Version # ############################################################################## # Halt on errors set -eE ## SET UP ENVIRONMENT ## _RUN_OBS_BUILD_SCRIPT=TRUE CHECKOUT_DIR="$(git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh" ## DEPENDENCY INSTALLATION ## source "${CHECKOUT_DIR}/CI/linux/01_install_dependencies.sh" ## OBS LIBRARY BUILD ## source "${CHECKOUT_DIR}/CI/linux/02_build_obs_libs.sh" ## PLUGIN BUILD ## source "${CHECKOUT_DIR}/CI/linux/03_build_plugin.sh" ## PLUGIN PACKAGE AND NOTARIZE ## source "${CHECKOUT_DIR}/CI/linux/04_package_plugin.sh" ## MAIN SCRIPT FUNCTIONS ## print_usage() { echo -e "build_linux.sh - Build script for ${PRODUCT_NAME}\n" echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-d, --skip-dependency-checks : Skip dependency checks\n" \ "-p, --package : Create installer for plugin\n" \ "-b, --build-dir : Specify alternative build directory (default: build)\n" } obs-build-main() { while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -p | --package ) PACKAGE=TRUE; shift ;; -b | --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done ensure_dir "${CHECKOUT_DIR}" step "Fetching version tags..." git fetch origin --tags GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) GIT_HASH=$(git rev-parse --short HEAD) GIT_TAG=$(git describe --tags --abbrev=0 2&>/dev/null || true) FILE_NAME="${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-linux" if [ -z "${SKIP_DEP_CHECKS}" ]; then install_dependencies fi build_obs_libs build_obs_plugin if [ -n "${PACKAGE}" ]; then package_obs_plugin fi cleanup } obs-build-main $* ================================================ FILE: CI/build-macos.sh ================================================ #!/bin/bash ############################################################################## # macOS plugin build script ############################################################################## # # This script contains all steps necessary to: # # * Build libobs and obs-frontend-api with all required dependencies # * Build your plugin # * Create macOS module bundle # * Create macOS plugin installer package # * Notarize macOS plugin installer package # # Parameters: # -h, --help : Print usage help # -q, --quiet : Suppress most build process output # -v, --verbose : Enable more verbose build process output # -d, --skip-dependency-checks : Skip dependency checks # -p, --package : Create installer for plugin # -c, --codesign : Codesign plugin and installer # -n, --notarize : Notarize plugin installer # (implies --codesign) # -b, --build-dir : Specify alternative build directory # (default: build) # # Environment Variables (optional): # MACOS_DEPS_VERSION : Pre-compiled macOS dependencies version # QT_VERSION : Pre-compiled Qt version # OBS_VERSION : OBS version # ############################################################################## # Halt on errors set -eE ## SET UP ENVIRONMENT ## _RUN_OBS_BUILD_SCRIPT=TRUE CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh" ## DEPENDENCY INSTALLATION ## source "${CHECKOUT_DIR}/CI/macos/01_install_dependencies.sh" ## OBS LIBRARY BUILD ## source "${CHECKOUT_DIR}/CI/macos/02_build_obs_libs.sh" ## PLUGIN BUILD ## source "${CHECKOUT_DIR}/CI/macos/03_build_plugin.sh" ## PLUGIN PACKAGE AND NOTARIZE ## source "${CHECKOUT_DIR}/CI/macos/04_package_plugin.sh" ## MAIN SCRIPT FUNCTIONS ## print_usage() { echo -e "build_macos.sh - Build script for ${PRODUCT_NAME}\n" echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-a, --architecture : Specify build architecture (default: universal, alternative: x86_64, arm64)\n" \ "-d, --skip-dependency-checks : Skip dependency checks\n" \ "-p, --package : Create installer for plugin\n" \ "-c, --codesign : Codesign plugin and installer\n" \ "-n, --notarize : Notarize plugin installer (implies --codesign)\n" \ "-b, --build-dir : Specify alternative build directory (default: build)\n" } obs-build-main() { while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -a | --architecture ) ARCH="${2}"; shift 2 ;; -d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;; -p | --package ) PACKAGE=TRUE; shift ;; -c | --codesign ) CODESIGN=TRUE; shift ;; -n | --notarize ) NOTARIZE=TRUE; PACKAGE=TRUE CODESIGN=TRUE; shift ;; -b | --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done ensure_dir "${CHECKOUT_DIR}" check_macos_version check_archs step "Fetching version tags..." /usr/bin/git fetch origin --tags GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD) GIT_HASH=$(/usr/bin/git rev-parse --short HEAD) GIT_TAG=$(/usr/bin/git describe --tags --abbrev=0 2&>/dev/null || true) if [ "${ARCH}" = "arm64" ]; then FILE_NAME="${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Apple.pkg" elif [ "${ARCH}" = "x86_64" ]; then FILE_NAME="${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Intel.pkg" else FILE_NAME="${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Universal.pkg" fi if [ -z "${SKIP_DEP_CHECKS}" ]; then install_dependencies fi build_obs_libs build_obs_plugin if [ -n "${PACKAGE}" ]; then package_obs_plugin fi if [ -n "${NOTARIZE}" ]; then notarize_obs_plugin fi cleanup } obs-build-main $* ================================================ FILE: CI/build-windows.ps1 ================================================ Param( [Switch]$Help, [Switch]$Quiet, [Switch]$Verbose, [Switch]$NoChoco, [Switch]$SkipDependencyChecks, [Switch]$BuildInstaller, [Switch]$CombinedArchs, [String]$BuildDirectory = "build", [String]$BuildArch = (Get-CimInstance CIM_OperatingSystem).OSArchitecture, [String]$BuildConfiguration = "RelWithDebInfo" ) ############################################################################## # Windows plugin build script ############################################################################## # # This script contains all steps necessary to: # # * Build libobs and obs-frontend-api with all required dependencies # * Build your plugin # * Create 64-bit or 32-bit packages # * Create 64-bit or 32-bit installation packages # * Create combined installation packages # # Parameters: # -Help : Print usage help # -NoChco : Skip automatic dependency installation # via Chocolatey # -SkipDependencyChecks : Skips dependency checks # -BuildDirectory : Directory to use for builds # Default: Win64 on 64-bit systems # Win32 on 32-bit systems # -BuildArch : Build architecture to use (32bit or 64bit) # -BuildConfiguration : Build configuration to use # Default: RelWithDebInfo # -BuildInstaller : Build InnoSetup installer - Default: off" # -CombinedArchs : Create combined packages and installer # (64-bit and 32-bit) - Default: off" # # Environment Variables (optional): # WindowsDepsVersion : Pre-compiled Windows dependencies version # WindowsQtVersion : Pre-compiled Qt version # ObsVersion : OBS Version # ############################################################################## $ErrorActionPreference = "Stop" $_RunObsBuildScript = $true $CheckoutDir = git rev-parse --show-toplevel $DepsBuildDir = "${CheckoutDir}/../obs-build-dependencies" $ObsBuildDir = "${CheckoutDir}/../obs-studio" if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) { . ${CheckoutDir}/CI/include/build_environment.ps1 } . ${CheckoutDir}/CI/include/build_support_windows.ps1 ## DEPENDENCY INSTALLATION ## . ${CheckoutDir}/CI/windows/01_install_dependencies.ps1 ## OBS LIBRARY BUILD ## . ${CheckoutDir}/CI/windows/02_build_obs_libs.ps1 ## PLUGIN BUILD ## . ${CheckoutDir}/CI/windows/03_build_plugin.ps1 ## PLUGIN PACKAGE AND NOTARIZE ## . ${CheckoutDir}/CI/windows/04_package_plugin.ps1 ## MAIN SCRIPT FUNCTIONS ## function Build-Obs-Plugin-Main { Ensure-Directory ${CheckoutDir} Write-Step "Fetching version tags..." & git fetch origin --tags $GitBranch = git rev-parse --abbrev-ref HEAD $GitHash = git rev-parse --short HEAD $ErrorActionPreference = "SilentlyContiue" $GitTag = git describe --tags --abbrev=0 $ErrorActionPreference = "Stop" if ($GitTag -eq $null) { $GitTag=$ProductVersion } $FileName = "${ProductName}-${GitTag}-${GitHash}" if(!($SkipDependencyChecks.isPresent)) { Install-Dependencies -NoChoco:$NoChoco } if($CombinedArchs.isPresent) { Build-OBS-Libs -BuildArch 64-bit Build-OBS-Libs -BuildArch 32-bit Build-OBS-Plugin -BuildArch 64-bit Build-OBS-Plugin -BuildArch 32-bit } else { Build-OBS-Libs Build-OBS-Plugin } Package-OBS-Plugin } function Print-Usage { Write-Host "build-windows.ps1 - Build script for ${ProductName}" $Lines = @( "Usage: ${MyInvocation.MyCommand.Name}", "-Help : Print this help", "-Quiet : Suppress most build process output" "-Verbose : Enable more verbose build process output" "-NoChoco : Skip automatic dependency installation via Chocolatey - Default: on", "-SkipDependencyChecks : Skips dependency checks - Default: off", "-BuildDirectory : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems", "-BuildArch : Build architecture to use (32bit or 64bit) - Default: local architecture", "-BuildConfiguration : Build configuration to use - Default: RelWithDebInfo", "-BuildInstaller : Build InnoSetup installer - Default: off", "-CombinedArchs : Create combined packages and installer (64-bit and 32-bit) - Default: off" ) $Lines | Write-Host } if($Help.isPresent) { Print-Usage exit 0 } Build-Obs-Plugin-Main ================================================ FILE: CI/include/Brewfile ================================================ brew "cmake" brew "ninja" brew "coreutils" ================================================ FILE: CI/include/Xcnotary ================================================ brew "akeru-inc/tap/xcnotary" ================================================ FILE: CI/include/build_environment.ps1 ================================================ $ProductName = "obs-rtspserver" #$ProductVersion = "@CMAKE_PROJECT_VERSION@" ================================================ FILE: CI/include/build_environment.ps1.in ================================================ $ProductName = "@CMAKE_PROJECT_NAME@" $ProductVersion = "@CMAKE_PROJECT_VERSION@" ================================================ FILE: CI/include/build_environment.sh ================================================ PRODUCT_NAME="obs-rtspserver" #PRODUCT_VERSION="@CMAKE_PROJECT_VERSION@" #LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" ================================================ FILE: CI/include/build_environment.sh.in ================================================ PRODUCT_NAME="@CMAKE_PROJECT_NAME@" PRODUCT_VERSION="@CMAKE_PROJECT_VERSION@" LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" ================================================ FILE: CI/include/build_support.sh ================================================ #!/bin/bash ############################################################################## # Unix support functions ############################################################################## # # This script file can be included in build scripts for UNIX-compatible # shells to compose build scripts. # ############################################################################## ## DEFINE UTILITIES ## if [ -z "${QUIET}" ]; then status() { echo -e "${COLOR_BLUE}[${PRODUCT_NAME}] ${1}${COLOR_RESET}" } step() { echo -e "${COLOR_GREEN} + ${1}${COLOR_RESET}" } info() { echo -e "${COLOR_ORANGE} + ${1}${COLOR_RESET}" } error() { echo -e "${COLOR_RED} + ${1}${COLOR_RESET}" } else status() { : } step() { : } info() { : } error() { echo -e "${COLOR_RED} + ${1}${COLOR_RESET}" } fi exists() { /usr/bin/command -v "$1" >/dev/null 2>&1 } ensure_dir() { [[ -n "${1}" ]] && /bin/mkdir -p "${1}" && builtin cd "${1}" } cleanup() { : } caught_error() { error "ERROR during build step: ${1}" cleanup exit 1 } # Setup build environment BUILD_DIR="${BUILD_DIR:-build}" BUILD_CONFIG="${BUILD_CONFIG:-RelWithDebInfo}" #CI_WORKFLOW="${CHECKOUT_DIR}/.github/workflows/main.yml" CURRENT_ARCH=$(uname -m) CURRENT_DATE="$(date +"%Y-%m-%d")" if [ "${GITHUB_ACTIONS}" = "true" ]; then CI="true" fi ## Utility functions ## check_ccache() { if ccache -V >/dev/null 2>&1; then info "CCache available" CMAKE_CCACHE_OPTIONS="-DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache" if [ "${CI}" ]; then ccache --set-config=cache_dir=${GITHUB_WORKSPACE:-${HOME}}/.ccache ccache --set-config=max_size=${CCACHE_SIZE:-500M} ccache --set-config=compression=true ccache -z fi else info "CCache not available" fi } _add_ccache_to_path() { if [ "${CMAKE_CCACHE_OPTIONS}" ]; then PATH="/usr/local/opt/ccache/libexec:${PATH}" status "Compiler Info:" local IFS=$'\n' for COMPILER_INFO in $(type cc c++ gcc g++ clang clang++ || true); do info "${COMPILER_INFO}" done fi } safe_fetch() { if [ $# -lt 2 ]; then error "Usage: safe_fetch URL HASH" return 1 fi while true; do case "${1}" in -n | --nocontinue ) NOCONTINUE=TRUE; shift ;; -- ) shift; break ;; * ) break ;; esac done DOWNLOAD_URL="${1}" DOWNLOAD_HASH="${2}" DOWNLOAD_FILE="$(basename ${DOWNLOAD_URL})" CURLCMD=${CURLCMD:-curl} if [ "${NOCONTINUE}" ]; then ${CURLCMD/--continue-at -/} "${DOWNLOAD_URL}" else ${CURLCMD} "${DOWNLOAD_URL}" fi if [ "${DOWNLOAD_HASH}" = "$(sha256sum "${DOWNLOAD_FILE}" | cut -d " " -f 1)" ]; then info "${DOWNLOAD_FILE} downloaded successfully and passed hash check" return 0 else error "${DOWNLOAD_FILE} downloaded successfully and failed hash check" return 1 fi } check_and_fetch() { if [ $# -lt 2 ]; then caught_error "Usage: check_and_fetch URL HASH" fi while true; do case "${1}" in -n | --nocontinue ) NOCONTINUE=TRUE; shift ;; -- ) shift; break ;; * ) break ;; esac done DOWNLOAD_URL="${1}" DOWNLOAD_HASH="${2}" DOWNLOAD_FILE="$(basename "${DOWNLOAD_URL}")" if [ -f "${DOWNLOAD_FILE}" ] && [ "${DOWNLOAD_HASH}" = "$(sha256sum "${DOWNLOAD_FILE}" | cut -d " " -f 1)" ]; then info "${DOWNLOAD_FILE} exists and passed hash check" return 0 else safe_fetch "${DOWNLOAD_URL}" "${DOWNLOAD_HASH}" fi } github_fetch() { if [ $# -ne 3 ]; then error "Usage: github_fetch GITHUB_USER GITHUB_REPOSITORY GITHUB_COMMIT_HASH" return 1 fi GH_USER="${1}" GH_REPO="${2}" GH_REF="${3}" if [ -d "./.git" ]; then info "Repository ${GH_USER}/${GH_REPO} already exists, updating..." git config advice.detachedHead false git config remote.origin.url "https://github.com/${GH_USER}/${GH_REPO}.git" git config remote.origin.fetch "+refs/heads/master:refs/remotes/origin/master" git config remote.origin.tapOpt --no-tags if ! git rev-parse -q --verify "${GH_COMMIT}^{commit}"; then git fetch origin fi git checkout -f "${GH_REF}" -- git reset --hard "${GH_REF}" -- if [ -d "./.gitmodules" ]; then git submodule foreach --recursive git submodule sync git submodule update --init --recursive fi else git clone "https://github.com/${GH_USER}/${GH_REPO}.git" "$(pwd)" git config advice.detachedHead false info "Checking out commit ${GH_REF}.." git checkout -f "${GH_REF}" -- if [ -d "./.gitmodules" ]; then git submodule foreach --recursive git submodule sync git submodule update --init --recursive fi fi } apply_patch() { if [ $# -ne 2 ]; then error "Usage: apply_patch PATCH_URL PATCH_HASH" return 1 fi COMMIT_URL="${1}" COMMIT_HASH="${2}" PATCH_FILE="$(basename ${COMMIT_URL})" if [ "${COMMIT_URL:0:5}" = "https" ]; then ${CURLCMD:-curl} "${COMMIT_URL}" if [ "${COMMIT_HASH}" = "$(sha256sum ${PATCH_FILE} | cut -d " " -f 1)" ]; then info "${PATCH_FILE} downloaded successfully and passed hash check" else error "${PATCH_FILE} downloaded successfully and failed hash check" return 1 fi info "Applying patch ${COMMIT_URL}" else PATCH_FILE="${COMMIT_URL}" fi patch -g 0 -f -p1 -i "${PATCH_FILE}" } ================================================ FILE: CI/include/build_support_linux.sh ================================================ #!/bin/bash ############################################################################## # Linux support functions ############################################################################## # # This script file can be included in build scripts for Linux. # ############################################################################## # Setup build environment # CI_OBS_VERSION=$(cat "${CI_WORKFLOW}" | sed -En "s/[ ]+OBS_VERSION: '([0-9\.]+)'/\1/p") if [ "${TERM-}" -a -z "${CI}" ]; then COLOR_RED=$(tput setaf 1) COLOR_GREEN=$(tput setaf 2) COLOR_BLUE=$(tput setaf 4) COLOR_ORANGE=$(tput setaf 3) COLOR_RESET=$(tput sgr0) else COLOR_RED=$(echo -e '\033[31m') COLOR_GREEN=$(echo -e '\033[32m') COLOR_BLUE=$(echo -e '\033[34m') COLOR_ORANGE=$(echo -e '\033[33m') COLOR_RESET=$(echo -e '\033[0m') fi if [ "${CI}" -o "${QUIET}" ]; then export CURLCMD="curl --silent --show-error --location -O" else export CURLCMD="curl --progress-bar --location --continue-at - -O" fi ================================================ FILE: CI/include/build_support_macos.sh ================================================ #!/bin/bash ############################################################################## # macOS support functions ############################################################################## # # This script file can be included in build scripts for macOS. # ############################################################################## # Setup build environment #CI_DEPS_VERSION=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+DEPS_VERSION_MAC: '([0-9\-]+)'/\1/p") #CI_DEPS_HASH=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+DEPS_HASH_MAC: '([0-9a-f]+)'/\1/p") #CI_QT_VERSION=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+QT_VERSION_MAC: '([0-9\.]+)'/\1/p" | /usr/bin/head -1) #CI_QT_HASH=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+QT_HASH_MAC: '([0-9a-f]+)'/\1/p") #CI_MACOSX_DEPLOYMENT_TARGET=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+MACOSX_DEPLOYMENT_TARGET: '([0-9\.]+)'/\1/p") #CI_OBS_VERSION=$(/bin/cat "${CI_WORKFLOW}" | /usr/bin/sed -En "s/[ ]+OBS_VERSION: '([0-9\.]+)'/\1/p") MACOS_DEPS_VERSION=${MACOS_DEPS_VERSION:-${DEPS_VERSION_MAC}} MACOS_DEPS_HASH=${MACOS_DEPS_HASH:-${DEPS_HASH_MAC}} QT_HASH=${QT_HASH:-${QT_HASH_MAC}} MACOS_VERSION="$(/usr/bin/sw_vers -productVersion)" MACOS_MAJOR="$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 1)" MACOS_MINOR="$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 2)" if [ "${TERM-}" -a -z "${CI}" ]; then COLOR_RED=$(/usr/bin/tput setaf 1) COLOR_GREEN=$(/usr/bin/tput setaf 2) COLOR_BLUE=$(/usr/bin/tput setaf 4) COLOR_ORANGE=$(/usr/bin/tput setaf 3) COLOR_RESET=$(/usr/bin/tput sgr0) else COLOR_RED=$(echo -e '\033[31m') COLOR_GREEN=$(echo -e '\033[32m') COLOR_BLUE=$(echo -e '\033[34m') COLOR_ORANGE=$(echo -e '\033[33m') COLOR_RESET=$(echo -e '\033[0m') fi ## DEFINE UTILITIES ## check_macos_version() { step "Check macOS version..." #MIN_VERSION=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}} MIN_VERSION=${MACOSX_DEPLOYMENT_TARGET} MIN_MAJOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 1) MIN_MINOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 2) if [ "${MACOS_MAJOR}" -lt "11" ] && [ "${MACOS_MINOR}" -lt "${MIN_MINOR}" ]; then error "WARNING: Minimum required macOS version is ${MIN_VERSION}, but running on ${MACOS_VERSION}" fi if [ "${MACOS_MAJOR}" -ge "11" ]; then export CODESIGN_LINKER="ON" fi } install_homebrew_deps() { if ! exists brew; then error "Homebrew not found - please install homebrew (https://brew.sh)" exit 1 fi brew bundle --file "${CHECKOUT_DIR}/CI/include/Brewfile" ${QUIET:+--quiet} check_curl } check_curl() { if [ "${MACOS_MAJOR}" -lt "11" ] && [ "${MACOS_MINOR}" -lt "15" ]; then if [ ! -d /usr/local/opt/curl ]; then step "Installing Homebrew curl.." brew install curl fi CURLCMD="/usr/local/opt/curl/bin/curl" else CURLCMD="curl" fi if [ "${CI}" -o "${QUIET}" ]; then export CURLCMD="${CURLCMD} --silent --show-error --location -O" else export CURLCMD="${CURLCMD} --progress-bar --location --continue-at - -O" fi } check_archs() { step "Check Architecture..." ARCH="${ARCH:-universal}" if [ "${ARCH}" = "universal" ]; then CMAKE_ARCHS="x86_64;arm64" elif [ "${ARCH}" != "x86_64" -a "${ARCH}" != "arm64" ]; then caught_error "Unsupported architecture '${ARCH}' provided" else CMAKE_ARCHS="${ARCH}" fi } ## SET UP CODE SIGNING AND NOTARIZATION CREDENTIALS ## ############################################################################## # Apple Developer Identity needed: # # + Signing the code requires a developer identity in the system's keychain # + codesign will look up and find the identity automatically # ############################################################################## read_codesign_ident() { if [ ! -n "${CODESIGN_IDENT}" ]; then step "Code-signing Setup" read -p "${COLOR_ORANGE} + Apple developer application identity: ${COLOR_RESET}" CODESIGN_IDENT fi } read_codesign_ident_installer() { if [ ! -n "${CODESIGN_IDENT_INSTALLER}" ]; then step "Code-signing Setup for Installer" read -p "${COLOR_ORANGE} + Apple developer installer identity: ${COLOR_RESET}" CODESIGN_IDENT_INSTALLER fi } ############################################################################## # Apple Developer credentials necessary: # # + Signing for distribution and notarization require an active Apple # Developer membership # + An Apple Development identity is needed for code signing # (i.e. 'Apple Development: YOUR APPLE ID (PROVIDER)') # + Your Apple developer ID is needed for notarization # + An app-specific password is necessary for notarization from CLI # + This password will be stored in your macOS keychain under the identifier # 'OBS-Codesign-Password'with access Apple's 'altool' only. ############################################################################## read_codesign_pass() { step "Notarization Setup" if [ -z "${CODESIGN_IDENT_USER}" ]; then read -p "${COLOR_ORANGE} + Apple account id: ${COLOR_RESET}" CODESIGN_IDENT_USER fi if [ -z "${CODESIGN_IDENT_PASS}" ]; then CODESIGN_IDENT_PASS=$(stty -echo; read -p "${COLOR_ORANGE} + Apple developer password: ${COLOR_RESET}" secret; stty echo; echo $secret) echo "" fi step "Updating notarization keychain" echo -n "${COLOR_ORANGE}" /usr/bin/xcrun altool --store-password-in-keychain-item "OBS-Codesign-Password" -u "${CODESIGN_IDENT_USER}" -p "${CODESIGN_IDENT_PASS}" echo -n "${COLOR_RESET}" CODESIGN_IDENT_SHORT=$(echo "${CODESIGN_IDENT}" | /usr/bin/sed -En "s/.+\((.+)\)/\1/p") } ================================================ FILE: CI/include/build_support_windows.ps1 ================================================ function Write-Status { param( [parameter(Mandatory=$true)] [string] $output ) if (!($Quiet.isPresent)) { if (Test-Path env:CI) { Write-Output "`e[33;34m[${ProductName}] ${output}`e[33;0m" } else { Write-Host -ForegroundColor blue "[${ProductName}] ${output}" } } } function Write-Info { param( [parameter(Mandatory=$true)] [string] $output ) if (!($Quiet.isPresent)) { if (Test-Path env:CI) { Write-Output "`e[33;33m + ${output}`e[33;0m" } else { Write-Host -ForegroundColor DarkYellow " + ${output}" } } } function Write-Step { param( [parameter(Mandatory=$true)] [string] $output ) if (!($Quiet.isPresent)) { if (Test-Path env:CI) { Write-Output "`e[33;32m + ${output}`e[33;0m" } else { Write-Host -ForegroundColor green " + ${output}" } } } function Write-Error { param( [parameter(Mandatory=$true)] [string] $output ) if (Test-Path env:CI) { Write-Output "e[33;31m + ${output}`e[33;0m" } else { Write-Host -ForegroundColor red " + ${output}" } } function Test-CommandExists { param( [parameter(Mandatory=$true)] [string] $Command ) $CommandExists = $false $OldActionPref = $ErrorActionPreference $ErrorActionPreference = "stop" try { if (Get-Command $Command) { $CommandExists = $true } } Catch { $CommandExists = $false } Finally { $ErrorActionPreference = $OldActionPref } return $CommandExists } function Ensure-Directory { param( [parameter(Mandatory=$true)] [string] $Directory ) if (!(Test-Path $Directory)) { $null = New-Item -ItemType Directory -Force -Path $Directory } Set-Location -Path $Directory } function TestFileHash { param ( [string]$Path, [string]$Hash ) $FileHash = Get-FileHash -Path $Path -Algorithm SHA256 if (-not ($FileHash.Hash -ieq $Hash)) { Write-Error "${Path} failed hash check." return $false } return $true } $BuildDirectory = "$(if (Test-Path Env:BuildDirectory) { $env:BuildDirectory } else { $BuildDirectory })" $BuildConfiguration = "$(if (Test-Path Env:BuildConfiguration) { $env:BuildConfiguration } else { $BuildConfiguration })" $BuildArch = "$(if (Test-Path Env:BuildArch) { $env:BuildArch } else { $BuildArch })" $OBSBranch = "$(if (Test-Path Env:OBSBranch) { $env:OBSBranch } else { $OBSBranch })" #$WindowsDepsVersion = "$(if (Test-Path Env:WindowsDepsVersion ) { $env:WindowsDepsVersion } else { "2022-02-13" })" $WindowsDepsVersion = "$(if (Test-Path Env:DEPS_VERSION_WIN ) { $env:DEPS_VERSION_WIN } else { "2022-08-02" })" $CmakeSystemVersion = "$(if (Test-Path Env:CMAKE_SYSTEM_VERSION) { $env:CMAKE_SYSTEM_VERSION } else { "10.0.22000.0" })" #$OBSVersion = "$(if ( Test-Path Env:OBSVersion ) { $env:ObsVersion } else { "27.2.3" })" $OBSVersion = "$(if ( Test-Path Env:OBS_VERSION ) { $env:OBS_VERSION } else { "28.0.1" })" #$NSISVersion = "$(if ( Test-Path Env:NSISVersion ) { $env:NSISVersion } else { "3.08" })" $NSISVersion = "$(if ( Test-Path Env:NSIS_VERSION_WIN ) { $env:NSIS_VERSION_WIN } else { "3.08" })" $WindowsDepsX64Hash = "$(if (Test-Path Env:DEPS_X64_HASH_WIN ) { $env:DEPS_X64_HASH_WIN } else { "-" })" $WindowsDepsX86Hash = "$(if (Test-Path Env:DEPS_X86_HASH_WIN ) { $env:DEPS_X86_HASH_WIN } else { "-" })" $WindowsQtX64Hash = "$(if (Test-Path Env:QT_X64_HASH_WIN ) { $env:QT_X64_HASH_WIN } else { "-" })" $WindowsQtX86Hash = "$(if (Test-Path Env:QT_X86_HASH_WIN ) { $env:QT_X86_HASH_WIN } else { "-" })" if ($env:GITHUB_ACTIONS -eq "true") { $env:CI = "true" } function Install-Windows-Dependencies { Write-Status "Checking Windows build dependencies" $ObsBuildDependencies = @( @("7z", "7zip"), @("cmake", "cmake --install-arguments 'ADD_CMAKE_TO_PATH=System'") ) if(!(Test-CommandExists "choco")) { Set-ExecutionPolicy AllSigned Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) } Foreach($Dependency in $ObsBuildDependencies) { if($Dependency -is [system.array]) { $Command = $Dependency[0] $ChocoName = $Dependency[1] } else { $Command = $Dependency $ChocoName = $Dependency } if(!(Test-CommandExists "${Command}")) { Invoke-Expression "choco install -y ${ChocoName}" } } $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") } function Find-Msvc-Vcvarsall-Path { param( [string]$Version = "" ) $vswherePath = "${env:ProgramFiles}\Microsoft Visual Studio\Installer\vswhere.exe" if (-not (Test-Path $vswherePath)) { $vswherePath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" } $argumentList = "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath", "-latest" if ($Version -ne "") { $argumentList += "-version", $Version } $output = (&$vswherePath $argumentList) $VcvarsallPath = "$output\VC\Auxiliary\Build\vcvarsall.bat" return $VcvarsallPath } function Set-Msvc-Environment-And-Run-Cmake { param( [string]$VsVersion = "", [ValidateSet("x86", "amd64", "x86_amd64", "x86_arm", "x86_arm64", "amd64_x86", "amd64_arm", "amd64_arm64")] [String]$Arch, [ValidateSet("", "store", "uwp")] [String]$PlatformType = "", [string]$WinsdkVersion = "", [string]$VcvarsVer = "", [string]$VcvarsSpectreLibs = "", [string[]]$CmakeArgumentList ) $VcvarsallPath = Find-Msvc-Vcvarsall-Path($VsVersion) $argumentList = "/C", "call", """$VcvarSallPath""", $Arch, $PlatformType, $WinsdkVersion if (-not $VcvarsVer -eq '') { $argumentList += "-vcvars_ver=$VcvarsVer" } if (-not $VcvarsSpectreLibs -eq '') { $argumentList += "-vcvars_spectre_libs=$VcvarsSpectreLibs" } $argumentList += "&&call", "cmake.exe" $argumentList += $CmakeArgumentList &"$env:windir\System32\cmd.exe" $argumentList } ================================================ FILE: CI/linux/01_install_dependencies.sh ================================================ #!/bin/bash ############################################################################## # Linux dependency management function ############################################################################## # # This script file can be included in build scripts for Linux or run directly # ############################################################################## # Halt on errors set -eE install_obs-studio() { if [ -n "${OBS_BRANCH}" ]; then CHECKOUT_REF="${OBS_BRANCH}" else #CHECKOUT_REF="tags/${OBS_VERSION:-${CI_OBS_VERSION}}" CHECKOUT_REF="tags/${OBS_VERSION}" fi ensure_dir "${OBS_BUILD_DIR}" if [ ! -d "${OBS_BUILD_DIR}/.git" ]; then git clone --recursive https://github.com/obsproject/obs-studio "$(pwd)" git fetch origin --tags git checkout ${CHECKOUT_REF} -b obs-plugin-build else if ! git show-ref --verify --quiet refs/heads/obs-plugin-build; then git checkout ${CHECKOUT_REF} -b obs-plugin-build else git checkout obs-plugin-build fi fi } install_build-deps() { shift status "Install OBS build dependencies" trap "caught_error 'install_build-deps'" ERR sudo apt-get install -y $@ } install_obs-deps() { shift status "Install OBS dependencies" trap "caught_error 'install_obs-deps'" ERR #if [ -z "${DISABLE_PIPEWIRE}" ]; then # sudo apt-get install -y $@ libpipewire-0.3-dev #else sudo apt-get install -y $@ #fi } install_qt5-deps() { shift status "Install Qt5 dependencies" trap "caught_error 'install_qt5-deps'" ERR sudo apt-get install -y $@ } install_qt6-deps() { shift status "Install Qt6 dependencies" trap "caught_error 'install_qt6-deps'" ERR _QT6_AVAILABLE="$(sudo apt-cache madison ${1})" if [ "${_QT6_AVAILABLE}" ]; then sudo apt-get install -y $@ fi } install_dependencies() { status "Set up apt" trap "caught_error 'install_dependencies'" ERR BUILD_DEPS=( "build-deps cmake ninja-build pkg-config clang clang-format build-essential curl ccache" "obs-deps libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev \ libswscale-dev libx264-dev libcurl4-openssl-dev libmbedtls-dev libgl1-mesa-dev libjansson-dev \ libluajit-5.1-dev python3-dev libx11-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \ libxcb-composite0-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev swig libcmocka-dev \ libpci-dev libxss-dev libglvnd-dev libgles2-mesa libgles2-mesa-dev libwayland-dev libxkbcommon-dev \ libpulse-dev" "qt5-deps qtbase5-dev qtbase5-private-dev libqt5svg5-dev qtwayland5" "qt6-deps qt6-base-dev qt6-base-private-dev libqt6svg6-dev qt6-wayland" ) sudo dpkg --add-architecture amd64 sudo apt-get -qq update for DEPENDENCY in "${BUILD_DEPS[@]}"; do set -- ${DEPENDENCY} trap "caught_error ${DEPENDENCY}" ERR FUNC_NAME="install_${1}" ${FUNC_NAME} ${@} done } install-dependencies-standalone() { CHECKOUT_DIR="$(git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh" status "Setting up plugin build dependencies" install_dependencies } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" } install-dependencies-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -- ) shift; break ;; * ) break ;; esac done install-dependencies-standalone fi } install-dependencies-main $* ================================================ FILE: CI/linux/02_build_obs_libs.sh ================================================ #!/bin/bash ############################################################################## # Linux libobs library build function ############################################################################## # # This script file can be included in build scripts for Linux or run directly # ############################################################################## # Halt on errors set -eE build_obs_libs() { status "Build libobs and obs-frontend-api" trap "caught_error 'build_obs_libs'" ERR check_ccache ensure_dir "${OBS_BUILD_DIR}" step "Configuring OBS build system" check_ccache cmake -S . -B plugin_${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DENABLE_PLUGINS=OFF \ -DENABLE_UI=ON \ -DENABLE_SCRIPTING=OFF \ -DENABLE_PIPEWIRE=OFF \ -DBUILD_BROWSER=OFF \ ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR} step "Building libobs and obs-frontend-api" cmake --build plugin_${BUILD_DIR} -t obs-frontend-api } build-obs-libs-standalone() { CHECKOUT_DIR="$(git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh" build_obs_libs } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } build-obs-libs-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done build-obs-libs-standalone fi } build-obs-libs-main $* ================================================ FILE: CI/linux/03_build_plugin.sh ================================================ #!/bin/bash ############################################################################## # Linux libobs plugin build function ############################################################################## # # This script file can be included in build scripts for Linux or run directly # ############################################################################## # Halt on errors set -eE build_obs_plugin() { status "Build plugin ${PRODUCT_NAME}" trap "caught_error 'builds_obs_plugin'" ERR ensure_dir "${CHECKOUT_DIR}" step "Configuring OBS plugin build system" check_ccache cmake -S . -B ${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \ -DOBS_SOURCE_DIR="${OBS_BUILD_DIR}" \ ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR} step "Building OBS plugin" cmake --build ${BUILD_DIR} } build-plugin-standalone() { CHECKOUT_DIR="$(git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh" build_obs_plugin } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } build-plugin-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done build-plugin-standalone fi } build-plugin-main $* ================================================ FILE: CI/linux/04_package_plugin.sh ================================================ #!/bin/bash ############################################################################## # Linux libobs plugin build function ############################################################################## # # This script file can be included in build scripts for Linux or run directly # ############################################################################## # Halt on errors set -eE package_obs_plugin() { status "Package OBS plugin ${PRODUCT_NAME}" trap "caught_error 'package_obs_plugin'" ERR ensure_dir "${CHECKOUT_DIR}" step "Package ${PRODUCT_NAME}..." cmake --build "${BUILD_DIR}" -t package } package-plugin-standalone() { CHECKOUT_DIR="$(git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_linux.sh" #GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) #GIT_HASH=$(git rev-parse --short HEAD) #GIT_TAG=$(git describe --tags --always --dirty='-dev') #GIT_VERSION=$(echo ${GIT_TAG} | grep -Eos '[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$') #FILE_NAME="${PRODUCT_NAME}-${GIT_TAG}-linux" package_obs_plugin } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } package-plugin-main() { if [ ! -n "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done package-plugin-standalone fi } package-plugin-main $* ================================================ FILE: CI/macos/01_install_dependencies.sh ================================================ #!/bin/bash ############################################################################## # macOS dependency management function ############################################################################## # # This script file can be included in build scripts for macOS or run directly # ############################################################################## # Halt on errors set -eE install_obs-deps() { status "Set up precompiled macOS OBS dependencies v${1}" ensure_dir "${DEPS_BUILD_DIR}" step "Download..." check_and_fetch "https://github.com/obsproject/obs-deps/releases/download/${1}/macos-deps-${1}-universal.tar.xz" "${2}" mkdir -p obs-deps step "Unpack..." /usr/bin/tar -xf "./macos-deps-${1}-universal.tar.xz" -C ./obs-deps /usr/bin/xattr -r -d com.apple.quarantine ./obs-deps } install_qt-deps() { status "Set up precompiled dependency Qt v${1}" ensure_dir "${DEPS_BUILD_DIR}" step "Download..." check_and_fetch "https://github.com/obsproject/obs-deps/releases/download/${1}/macos-deps-qt6-${1}-universal.tar.xz" "${2}" mkdir -p obs-deps step "Unpack..." /usr/bin/tar -xf "./macos-deps-qt6-${1}-universal.tar.xz" -C ./obs-deps /usr/bin/xattr -r -d com.apple.quarantine ./obs-deps } install_obs-studio() { if [ "${OBS_BRANCH}" ]; then CHECKOUT_REF="${OBS_BRANCH}" else #CHECKOUT_REF="tags/${OBS_VERSION:-${CI_OBS_VERSION}}" CHECKOUT_REF="tags/${OBS_VERSION}" fi ensure_dir "${OBS_BUILD_DIR}" if [ ! -d "${OBS_BUILD_DIR}/.git" ]; then /usr/bin/git clone --recursive https://github.com/obsproject/obs-studio "$(pwd)" /usr/bin/git fetch origin --tags /usr/bin/git checkout ${CHECKOUT_REF} -b obs-plugin-build else if ! /usr/bin/git show-ref --verify --quiet refs/heads/obs-plugin-build; then /usr/bin/git checkout ${CHECKOUT_REF} -b obs-plugin-build else /usr/bin/git checkout obs-plugin-build fi fi } install_dependencies() { status "Installing Homebrew dependencies" trap "caught_error 'install_dependencies'" ERR BUILD_DEPS=( #"obs-deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}} ${MACOS_DEPS_HASH:-${CI_DEPS_HASH}}" "obs-deps ${MACOS_DEPS_VERSION} ${MACOS_DEPS_HASH}" #"qt-deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}} ${QT_HASH:-${CI_QT_HASH}}" "qt-deps ${MACOS_DEPS_VERSION} ${QT_HASH}" #"obs-studio ${OBS_VERSION:-${CI_OBS_VERSION}}" "obs-studio ${OBS_VERSION}" ) install_homebrew_deps for DEPENDENCY in "${BUILD_DEPS[@]}"; do set -- ${DEPENDENCY} trap "caught_error ${DEPENDENCY}" ERR FUNC_NAME="install_${1}" ${FUNC_NAME} ${2} ${3} done } install-dependencies-standalone() { CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh" status "Setting up plugin build dependencies" check_macos_version check_archs install_dependencies } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-a, --architecture : Specify build architecture (default: universal, alternative: x86_64, arm64)\n" } install-dependencies-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -a | --architecture ) ARCH="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done install-dependencies-standalone fi } install-dependencies-main $* ================================================ FILE: CI/macos/02_build_obs_libs.sh ================================================ #!/bin/bash ############################################################################## # macOS libobs library build function ############################################################################## # # This script file can be included in build scripts for macOS or run directly # ############################################################################## # Halt on errors set -eE build_obs_libs() { status "Build libobs and obs-frontend-api" trap "caught_error 'build_obs_libs'" ERR ensure_dir "${OBS_BUILD_DIR}" step "Configuring OBS build system" check_ccache #-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}} \ cmake -S . -B plugin_${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \ -DCMAKE_OSX_ARCHITECTURES="${CMAKE_ARCHS}" \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ -DOBS_CODESIGN_LINKER=${CODESIGN_LINKER:-OFF} \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DENABLE_PLUGINS=OFF \ -DENABLE_UI=ON \ -DENABLE_SCRIPTING=OFF \ -DENABLE_SPARKLE_UPDATER=OFF \ -DSPARKLE=OFF \ -DBUILD_BROWSER=OFF \ -DCMAKE_PREFIX_PATH="${DEPS_BUILD_DIR}/obs-deps" \ ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR} step "Building libobs and obs-frontend-api" cmake --build plugin_${BUILD_DIR} -t obs-frontend-api } build-obs-libs-standalone() { CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh" check_macos_version check_archs build_obs_libs } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-a, --architecture : Specify build architecture (default: universal, alternative: x86_64, arm64)\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } build-obs-libs-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -a | --architecture ) ARCH="${2}"; shift 2 ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done build-obs-libs-standalone fi } build-obs-libs-main $* ================================================ FILE: CI/macos/03_build_plugin.sh ================================================ #!/bin/bash ############################################################################## # macOS libobs plugin build function ############################################################################## # # This script file can be included in build scripts for macOS or run directly # ############################################################################## # Halt on errors set -eE build_obs_plugin() { status "Build plugin ${PRODUCT_NAME}" trap "caught_error 'builds_obs_plugin'" ERR if [ "${CODESIGN}" ]; then read_codesign_ident fi ensure_dir "${CHECKOUT_DIR}" step "Configuring OBS plugin build system" check_ccache #-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}} \ cmake -S . -B ${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \ -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \ -DCMAKE_OSX_ARCHITECTURES="${CMAKE_ARCHS}" \ -DOBS_CODESIGN_LINKER=${CODESIGN_LINKER:-OFF} \ -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \ -DOBS_BUNDLE_CODESIGN_IDENTITY="${CODESIGN_IDENT:--}" \ -DCMAKE_PREFIX_PATH="${DEPS_BUILD_DIR}/obs-deps" \ -DOBS_SOURCE_DIR="${OBS_BUILD_DIR}" \ ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR} step "Building OBS plugin" cmake --build ${BUILD_DIR} } build-plugin-standalone() { CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" DEPS_BUILD_DIR="${CHECKOUT_DIR}/../obs-build-dependencies" OBS_BUILD_DIR="${CHECKOUT_DIR}/../obs-studio" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh" check_macos_version check_archs build_obs_plugin } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n" \ "-c, --codesign : Codesign OBS and all libraries (default: ad-hoc only)\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } build-plugin-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -c | --codesign ) CODESIGN=TRUE; shift ;; -a | --architecture ) ARCH="${2}"; shift 2 ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done build-plugin-standalone fi } build-plugin-main $* ================================================ FILE: CI/macos/04_package_plugin.sh ================================================ #!/bin/bash ############################################################################## # macOS libobs plugin package function ############################################################################## # # This script file can be included in build scripts for macOS or run directly # ############################################################################## # Halt on errors set -eE package_obs_plugin() { if [ "${CODESIGN}" ]; then read_codesign_ident fi status "Package OBS plugin ${PRODUCT_NAME}" trap "caught_error 'package_obs_plugin'" ERR ensure_dir "${CHECKOUT_DIR}" # if [ -d "${BUILD_DIR}/rundir/${PRODUCT_NAME}.plugin" ]; then # rm -rf "${BUILD_DIR}/rundir/${PRODUCT_NAME}.plugin" # fi if [ -d "${CHECKOUT_DIR}/release" ]; then rm -rf "${CHECKOUT_DIR}/release" fi cmake --install "${BUILD_DIR}" --prefix "${CHECKOUT_DIR}/release" if ! type packagesbuild &>/dev/null; then status "Setting up dependency Packages.app" step "Download..." check_and_fetch "http://s.sudre.free.fr/Software/files/Packages.dmg" "9d9a73a64317ea6697a380014d2e5c8c8188b59d5fb8ce8872e56cec06cd78e8" step "Mount disk image..." hdiutil attach -noverify Packages.dmg step "Install Packages.app" PACKAGES_VOLUME=$(hdiutil info -plist | grep "/Volumes/Packages" | sed 's/\/Volumes\/\([^<]*\)<\/string>/\1/' | sed -e 's/^[[:space:]]*//') sudo installer -pkg "/Volumes/${PACKAGES_VOLUME}/Install Packages.pkg" -target / hdiutil detach "/Volumes/${PACKAGES_VOLUME}" fi step "Package ${PRODUCT_NAME}..." cp "${CHECKOUT_DIR}/LICENSE" "${CHECKOUT_DIR}/bundle/LICENSE.txt" packagesbuild ./bundle/installer-macos.generated.pkgproj ensure_dir "${CHECKOUT_DIR}/release" step "Creating zip archive..." zip -r9 "${CHECKOUT_DIR}/${BUILD_DIR}/${FILE_NAME}.zip" . ensure_dir "${CHECKOUT_DIR}" if [ "${CODESIGN}" ]; then step "Codesigning installer package..." read_codesign_ident_installer /usr/bin/productsign --sign "${CODESIGN_IDENT_INSTALLER}" "${BUILD_DIR}/${PRODUCT_NAME}.pkg" "${BUILD_DIR}/${FILE_NAME}.pkg" else mv "${BUILD_DIR}/${PRODUCT_NAME}.pkg" "${BUILD_DIR}/${FILE_NAME}.pkg" fi } notarize_obs_plugin() { status "Notarize ${PRODUCT_NAME}" trap "caught_error 'notarize_obs_plugin'" ERR if ! exists brew; then error "Homebrew not found - please install homebrew (https://brew.sh)" exit 1 fi if ! exists xcnotary; then step "Install notarization dependency 'xcnotary'" brew install akeru-inc/tap/xcnotary fi ensure_dir "${CHECKOUT_DIR}" if [ -f "${FILE_NAME}" ]; then xcnotary precheck "${FILE_NAME}" else error "No notarization package installer ('${FILE_NAME}') found" return fi if [ "$?" -eq 0 ]; then read_codesign_ident_installer read_codesign_pass step "Run xcnotary with ${FILE_NAME}..." xcnotary notarize "${FILE_NAME}" --developer-account "${CODESIGN_IDENT_USER}" --developer-password-keychain-item "OBS-Codesign-Password" --provider "${CODESIGN_IDENT_SHORT}" fi } package-plugin-standalone() { CHECKOUT_DIR="$(/usr/bin/git rev-parse --show-toplevel)" if [ -f "${CHECKOUT_DIR}/CI/include/build_environment.sh" ]; then source "${CHECKOUT_DIR}/CI/include/build_environment.sh" fi PRODUCT_NAME="${PRODUCT_NAME:-obs-plugin}" source "${CHECKOUT_DIR}/CI/include/build_support.sh" source "${CHECKOUT_DIR}/CI/include/build_support_macos.sh" GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD) GIT_HASH=$(/usr/bin/git rev-parse --short HEAD) GIT_TAG=$(/usr/bin/git describe --tags --always --dirty='-dev') GIT_VERSION=$(echo ${GIT_TAG} | grep -Eos '[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$') check_macos_version check_archs FILE_NAME="${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-macos-${ARCH}" check_curl package_obs_plugin if [ "${NOTARIZE}" ]; then notarize_obs_plugin fi } print_usage() { echo -e "Usage: ${0}\n" \ "-h, --help : Print this help\n" \ "-q, --quiet : Suppress most build process output\n" \ "-v, --verbose : Enable more verbose build process output\n" \ "-a, --architecture : Specify build architecture (default: x86_64, alternative: arm64)\n" \ "-c, --codesign : Codesign OBS and all libraries (default: ad-hoc only)\n" \ "-n, --notarize : Notarize OBS (default: off)\n" \ "--build-dir : Specify alternative build directory (default: build)\n" } package-plugin-main() { if [ -z "${_RUN_OBS_BUILD_SCRIPT}" ]; then while true; do case "${1}" in -h | --help ) print_usage; exit 0 ;; -q | --quiet ) export QUIET=TRUE; shift ;; -v | --verbose ) export VERBOSE=TRUE; shift ;; -a | --architecture ) ARCH="${2}"; shift 2 ;; -c | --codesign ) CODESIGN=TRUE; shift ;; -n | --notarize ) NOTARIZE=TRUE; CODESIGN=TRUE; shift ;; -s | --standalone ) STANDALONE=TRUE; shift ;; --build-dir ) BUILD_DIR="${2}"; shift 2 ;; -- ) shift; break ;; * ) break ;; esac done package-plugin-standalone fi } package-plugin-main $* ================================================ FILE: CI/utility/check-format.sh ================================================ #!/bin/bash dirty=$(git ls-files --modified) set +x if [[ $dirty ]]; then echo "=================================" echo "Files were not formatted properly" echo "$dirty" echo "=================================" exit 1 fi ================================================ FILE: CI/utility/formatcode.sh ================================================ #!/usr/bin/env bash # Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh set -o errexit set -o pipefail set -o nounset if [ ${#} -eq 1 ]; then VERBOSITY="--verbose" else VERBOSITY="" fi # Runs the Clang Formatter in parallel on the code base. # Return codes: # - 1 there are files to be formatted # - 0 everything looks fine # Get CPU count OS=$(uname) NPROC=1 if [[ ${OS} = "Linux" ]] ; then NPROC=$(nproc) elif [[ ${OS} = "Darwin" ]] ; then NPROC=$(sysctl -n hw.physicalcpu) fi # Discover clang-format if type clang-format-12 2> /dev/null ; then CLANG_FORMAT=clang-format-12 elif type clang-format 2> /dev/null ; then # Clang format found, but need to check version CLANG_FORMAT=clang-format V=$(clang-format --version) if [[ $V != *"version 12.0"* ]]; then echo "clang-format is not 12.0 (returned ${V})" exit 1 fi else echo "No appropriate clang-format found (expected clang-format-12.0.0, or clang-format)" exit 1 fi find . -type d \( \ -path ./\*build -o \ -path ./cmake -o \ -path ./deps -o \ -path ./plugins/decklink/\*/decklink-sdk -o \ -path ./plugins/enc-amf -o \ -path ./plugins/mac-syphon/syphon-framework -o \ -path ./plugins/obs-outputs/ftl-sdk -o \ -path ./plugins/obs-vst \ \) -prune -false -type f -o \ -name '*.h' -or \ -name '*.hpp' -or \ -name '*.m' -or \ -name '*.m,' -or \ -name '*.c' -or \ -name '*.cpp' \ | xargs -L100 -P ${NPROC} ${CLANG_FORMAT} ${VERBOSITY} -i -style=file -fallback-style=none ================================================ FILE: CI/windows/01_install_dependencies.ps1 ================================================ Param( [Switch]$Help = $(if (Test-Path variable:Help) { $Help }), [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }), [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }), [Switch]$NoChoco = $(if (Test-Path variable:NoChoco) { $true } else { $false }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" } else { (Get-CimInstance CIM_OperatingSystem).OSArchitecture }), [String]$ProductName = $(if (Test-Path variable:ProductName) { "${ProductName}" } else { "obs-plugin" }), [string[]]$InstallList = $(if (Test-Path variable:InstallList) { "${InstallList}" } else { $null }) ) ############################################################################## # Windows dependency management function ############################################################################## # # This script file can be included in build scripts for Windows or run # directly # ############################################################################## $ErrorActionPreference = "Stop" Function Install-obs-deps { Param( [Parameter(Mandatory=$true)] [String]$Version ) $Arch = "x86" if ($BuildArch -eq "64-bit") { $Arch = "x64" } Write-Status "Setup for pre-built Windows OBS dependencies v${Version} ${Arch}" Ensure-Directory $DepsBuildDir if (!(Test-Path $DepsBuildDir/windows-deps-${Version}-${Arch})) { Write-Status "Setting up pre-built Windows OBS dependencies v${Version} ${Arch}" Write-Step "Download..." $ProgressPreference = $(if ($Quiet.isPresent) { "SilentlyContinue" } else { "Continue" }) Invoke-WebRequest -Uri "https://github.com/obsproject/obs-deps/releases/download/${Version}/windows-deps-${Version}-${Arch}.zip" -UseBasicParsing -OutFile "windows-deps-${Version}-${Arch}.zip" $ProgressPreference = 'Continue' Write-Step "Unpack..." Expand-Archive -Path "windows-deps-${Version}-${Arch}.zip" } else { Write-Step "Found existing pre-built dependencies..." } } function Install-qt-deps { Param( [Parameter(Mandatory=$true)] [String]$Version ) $Arch = "x86" if ($BuildArch -eq "64-bit") { $Arch = "x64" } Write-Status "Setup for pre-built dependency Qt v${Version} ${Arch}" Ensure-Directory $DepsBuildDir if (!(Test-Path $DepsBuildDir/windows-deps-qt6-${Version}-${Arch})) { Write-Status "Setting up OBS dependency Qt v${Version} ${Arch}" Write-Step "Download..." $ProgressPreference = $(if ($Quiet.isPresent) { 'SilentlyContinue' } else { 'Continue' }) Invoke-WebRequest -Uri "https://github.com/obsproject/obs-deps/releases/download/${Version}/windows-deps-qt6-${Version}-${Arch}.zip" -UseBasicParsing -OutFile "windows-deps-qt6-${Version}-${Arch}.zip" $ProgressPreference = 'Continue' Write-Step "Unpack..." Expand-Archive -Path "windows-deps-qt6-${Version}-${Arch}.zip" } else { Write-Step "Found existing pre-built Qt..." } } function Install-obs-studio { Param( [parameter(Mandatory=$true)] [string]$Version ) $CheckoutRef = "$(if (!(Test-Path variable:OBSBranch)) { ${OBSBranch} } else { "tags/${OBSVersion}" })" Write-Status "Setup for OBS Studio v${OBSVersion}" Ensure-Directory ${ObsBuildDir} if (!(Test-Path "${ObsBuildDir}/.git")) { & git clone --recursive https://github.com/obsproject/obs-studio "${pwd}" & git fetch origin --tags & git checkout ${CheckoutRef} -b obs-plugin-build } else { & git show-ref --verify --quiet refs/heads/obs-plugin-build $BranchExists = $? if ($BranchExists -Eq $false) { & git checkout ${CheckoutRef} -b obs-plugin-build } else { & git checkout obs-plugin-build } } } function Install-nsis { Param( [parameter(Mandatory=$true)] [string]$Version ) Write-Status "Setup for NSIS v${Version}" Ensure-Directory $DepsBuildDir if (!(Test-Path $DepsBuildDir/nsis-${Version})) { Write-Status "Setting up NSIS v${Version}" Write-Step "Download..." $ProgressPreference = $(if ($Quiet.isPresent) { 'SilentlyContinue' } else { 'Continue' }) Invoke-WebRequest -Uri "https://sourceforge.net/projects/nsis/files/NSIS 3/${Version}/nsis-${Version}.zip/download" -UseBasicParsing -OutFile "nsis-${Version}.zip" -UserAgent "Wget/1.10.2" $ProgressPreference = 'Continue' Write-Step "Unpack..." Expand-Archive -Path "nsis-${Version}.zip" -DestinationPath "./" } else { Write-Step "Found existing NSIS..." } } function Install-Dependencies { if(!($NoChoco.isPresent)) { Install-Windows-Dependencies } $BuildDependencies = @( @('obs-deps', $WindowsDepsVersion), @('qt-deps', $WindowsDepsVersion), @('obs-studio', $OBSVersion), @('nsis', $NSISVersion) ) Foreach($Dependency in ${BuildDependencies}) { $DependencyName = $Dependency[0] $DependencyVersion = $Dependency[1] if ($null -ne $InstallList -and $InstallList -notcontains $DependencyName) { continue } $FunctionName = "Install-${DependencyName}" & $FunctionName -Version $DependencyVersion } Ensure-Directory ${CheckoutDir} } function Install-Dependencies-Standalone { $CheckoutDir = git rev-parse --show-toplevel if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) { . ${CheckoutDir}/CI/include/build_environment.ps1 } $DepsBuildDir = "${CheckoutDir}/../obs-build-dependencies" $ObsBuildDir = "${CheckoutDir}/../obs-studio" . ${CheckoutDir}/CI/include/build_support_windows.ps1 Write-Status "Setting up plugin build dependencies" Install-Dependencies } function Print-Usage { $Lines = @( "Usage: ${MyInvocation.MyCommand.Name}", "-Help : Print this help", "-Quiet : Suppress most build process output", "-Verbose : Enable more verbose build process output", "-NoChoco : Skip automatic dependency installation via Chocolatey - Default: on" "-BuildArch : Build architecture to use (32bit or 64bit) - Default: local architecture" ) $Lines | Write-Host } if(!(Test-Path variable:_RunObsBuildScript)) { if ($Help.isPresent) { Print-Usage exit 0 } Install-Dependencies-Standalone } ================================================ FILE: CI/windows/02_build_obs_libs.ps1 ================================================ Param( [Switch]$Help = $(if (Test-Path variable:Help) { $Help }), [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }), [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }), [String]$ProductName = $(if (Test-Path variable:ProductName) { "${ProductName}" } else { "obs-plugin" }), [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" } else { "build" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" } else { "RelWithDebInfo" }) ) ############################################################################## # Windows libobs library build function ############################################################################## # # This script file can be included in build scripts for Windows or run # directly # ############################################################################## $ErrorActionPreference = "Stop" function Build-OBS-Libs { Param( [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" }), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" }) ) Write-Status "Build libobs and obs-frontend-api" Ensure-Directory ${ObsBuildDir} if ($BuildArch -eq "64-bit") { $QtDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x64" $DepsDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x64" Set-Msvc-Environment-And-Run-Cmake -Arch "amd64" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "-S", ".", "-B", """plugin_${BuildDirectory}64""", "-G", "Ninja", "-DCMAKE_SYSTEM_VERSION=""${CmakeSystemVersion}""", "-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=""${CmakeSystemVersion}""", "-DQTDIR=""${QtDirectory}""", "-DDepsPath=""${DepsDirectory}""", "-DCMAKE_C_COMPILER=cl.exe", "-DCMAKE_CXX_COMPILER=cl.exe", "-DENABLE_PLUGINS=OFF", "-DENABLE_UI=ON", "-DENABLE_SCRIPTING=OFF", "-DBUILD_BROWSER=OFF", "-DCMAKE_BUILD_TYPE=Release", "$(if (Test-Path Variable:$Quiet) { "-Wno-deprecated -Wno-dev --log-level=ERROR" })") Set-Msvc-Environment-And-Run-Cmake -Arch "amd64" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "--build", """plugin_${BuildDirectory}64""", "-t", "obs-frontend-api", "--config ${BuildConfiguration}") } else { $QtDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x86" $DepsDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x86" Set-Msvc-Environment-And-Run-Cmake -Arch "x86" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "-S", ".", "-B", """plugin_${BuildDirectory}32""", "-G", "Ninja", "-DCMAKE_SYSTEM_VERSION=""${CmakeSystemVersion}""", "-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=""${CmakeSystemVersion}""", "-DQTDIR=""${QtDirectory}""", "-DDepsPath=""${DepsDirectory}""", "-DCMAKE_C_COMPILER=cl.exe", "-DCMAKE_CXX_COMPILER=cl.exe", "-DENABLE_PLUGINS=OFF", "-DENABLE_UI=ON", "-DENABLE_SCRIPTING=OFF", "-DBUILD_BROWSER=OFF", "-DCMAKE_BUILD_TYPE=Release", "$(if (Test-Path Variable:$Quiet) { "-Wno-deprecated -Wno-dev --log-level=ERROR" })") Set-Msvc-Environment-And-Run-Cmake -Arch "x86" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "--build", """plugin_${BuildDirectory}32""", "-t", "obs-frontend-api", "--config ${BuildConfiguration}") } Ensure-Directory ${CheckoutDir} } function Build-OBS-Libs-Standalone { $CheckoutDir = git rev-parse --show-toplevel if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) { . ${CheckoutDir}/CI/include/build_environment.ps1 } $ObsBuildDir = "${CheckoutDir}/../obs-studio" . ${CheckoutDir}/CI/include/build_support_windows.ps1 Build-OBS-Libs } function Print-Usage { $Lines = @( "Usage: ${MyInvocation.MyCommand.Name}", "-Help : Print this help", "-Quiet : Suppress most build process output", "-Verbose : Enable more verbose build process output", "-BuildDirectory : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems", "-BuildArch : Build architecture to use (32bit or 64bit) - Default: local architecture", "-BuildConfiguration : Build configuration to use - Default: RelWithDebInfo" ) $Lines | Write-Host } if(!(Test-Path variable:_RunObsBuildScript)) { if($Help.isPresent) { Print-Usage exit 0 } Build-OBS-Libs-Standalone } ================================================ FILE: CI/windows/03_build_plugin.ps1 ================================================ Param( [Switch]$Help = $(if (Test-Path variable:Help) { $Help }), [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }), [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }), [String]$ProductName = $(if (Test-Path variable:ProductName) { "${ProductName}" } else { "obs-plugin" }), [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" } else { "build" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" } else { "RelWithDebInfo" }) ) ############################################################################## # Windows libobs plugin build function ############################################################################## # # This script file can be included in build scripts for Windows or run # directly # ############################################################################## $ErrorActionPreference = "Stop" function Build-OBS-Plugin { Param( [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" }), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" }) ) Write-Status "Build plugin ${ProductName}" Ensure-Directory ${CheckoutDir} if ($BuildArch -eq "64-bit") { $QtDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x64" $DepsDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x64" Set-Msvc-Environment-And-Run-Cmake -Arch "amd64" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "-S", ".", "-B", """${BuildDirectory}64""", "-G", "Ninja", "-DCMAKE_SYSTEM_VERSION=""${CmakeSystemVersion}""", "-DQTDIR=""${QtDirectory}""", "-DDepsPath=""${DepsDirectory}""", "-DCMAKE_C_COMPILER=cl.exe", "-DCMAKE_CXX_COMPILER=cl.exe", "-DOBS_SOURCE_DIR=""${ObsBuildDir}""", "-DCMAKE_BUILD_TYPE=""${BuildConfiguration}""", "$(if (Test-Path Variable:$Quiet) { "-Wno-deprecated -Wno-dev --log-level=ERROR" })") Set-Msvc-Environment-And-Run-Cmake -Arch "amd64" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "--build", """${BuildDirectory}64""", "--config", "${BuildConfiguration}") } else { $QtDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x86" $DepsDirectory = "${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x86" Set-Msvc-Environment-And-Run-Cmake -Arch "x86" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "-S", ".", "-B", """${BuildDirectory}32""", "-G", "Ninja", "-DCMAKE_SYSTEM_VERSION=""${CmakeSystemVersion}""", "-DQTDIR=""${QtDirectory}""", "-DDepsPath=""${DepsDirectory}""", "-DCMAKE_C_COMPILER=cl.exe", "-DCMAKE_CXX_COMPILER=cl.exe", "-DOBS_SOURCE_DIR=""${ObsBuildDir}""", "-DCMAKE_BUILD_TYPE=""${BuildConfiguration}""", "$(if (Test-Path Variable:$Quiet) { "-Wno-deprecated -Wno-dev --log-level=ERROR" })") Set-Msvc-Environment-And-Run-Cmake -Arch "x86" -WinsdkVersion "${CmakeSystemVersion}" -CmakeArgumentList @( "--build", """${BuildDirectory}32""", "--config", "${BuildConfiguration}") } Ensure-Directory ${CheckoutDir} } function Build-Plugin-Standalone { $CheckoutDir = git rev-parse --show-toplevel if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) { . ${CheckoutDir}/CI/include/build_environment.ps1 } $ObsBuildDir = "${CheckoutDir}/../obs-studio" . ${CheckoutDir}/CI/include/build_support_windows.ps1 Build-OBS-Plugin } function Print-Usage { $Lines = @( "Usage: ${MyInvocation.MyCommand.Name}", "-Help : Print this help", "-Quiet : Suppress most build process output", "-Verbose : Enable more verbose build process output", "-BuildDirectory : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems", "-BuildArch : Build architecture to use (32bit or 64bit) - Default: local architecture", "-BuildConfiguration : Build configuration to use - Default: RelWithDebInfo" ) $Lines | Write-Host } if(!(Test-Path variable:_RunObsBuildScript)) { if($Help.isPresent) { Print-Usage exit 0 } Build-Plugin-Standalone } ================================================ FILE: CI/windows/04_package_plugin.ps1 ================================================ Param( [Switch]$Help = $(if (Test-Path variable:Help) { $Help }), [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }), [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }), [Switch]$BuildInstaller = $(if ($BuildInstaller.isPresent) { $true }), [String]$ProductName = $(if (Test-Path variable:ProductName) { "${ProductName}" } else { "obs-plugin" }), [Switch]$CombinedArchs = $(if ($CombinedArchs.isPresent) { $true }), [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" } else { "build" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" } else { "RelWithDebInfo" }) ) ############################################################################## # Windows libobs plugin package function ############################################################################## # # This script file can be included in build scripts for Windows or run # directly with the -Standalone switch # ############################################################################## $ErrorActionPreference = "Stop" function Package-OBS-Plugin { Param( [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { "${BuildDirectory}" }), [String]$BuildArch = $(if (Test-Path variable:BuildArch) { "${BuildArch}" }), [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { "${BuildConfiguration}" }) ) Write-Status "Package plugin ${ProductName}" Ensure-Directory ${CheckoutDir} $makensis_path = "${CheckoutDir}/../obs-build-dependencies/nsis-$NSISVersion/makensis.exe" if ($CombinedArchs.isPresent) { if (!(Test-Path ${CheckoutDir}/release/obs-plugins/64bit)) { cmake --build ${BuildDirectory}64 --config ${BuildConfiguration} -t install } if (!(Test-Path ${CheckoutDir}/release/obs-plugins/32bit)) { cmake --build ${BuildDirectory}32 --config ${BuildConfiguration} -t install } $CompressVars = @{ Path = "${CheckoutDir}/release/*" CompressionLevel = "Optimal" DestinationPath = "${FileName}-windows-all.zip" } Write-Step "Creating zip archive..." Compress-Archive -Force @CompressVars if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) { Write-Step "Creating installer..." Set-Location "${CheckoutDir}/installer" Copy-Item "${CheckoutDir}/LICENSE" "${CheckoutDir}/installer/LICENSE" & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=all .\installer.nsi Ensure-Directory ${CheckoutDir} } } elseif ($BuildArch -eq "64-bit") { cmake --build ${BuildDirectory}64 --config ${BuildConfiguration} -t install $CompressVars = @{ Path = "${CheckoutDir}/release/*" CompressionLevel = "Optimal" DestinationPath = "${FileName}-windows-x64.zip" } Write-Step "Creating zip archive..." Compress-Archive -Force @CompressVars if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) { Write-Step "Creating installer..." Set-Location "${CheckoutDir}/installer" Copy-Item "${CheckoutDir}/LICENSE" "${CheckoutDir}/installer/LICENSE" & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=x64 .\installer.nsi Ensure-Directory ${CheckoutDir} } } elseif ($BuildArch -eq "32-bit") { cmake --build ${BuildDirectory}32 --config ${BuildConfiguration} -t install $CompressVars = @{ Path = "${CheckoutDir}/release/*" CompressionLevel = "Optimal" DestinationPath = "${FileName}-windows-x86.zip" } Write-Step "Creating zip archive..." Compress-Archive -Force @CompressVars if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) { Write-Step "Creating installer..." Set-Location "${CheckoutDir}/installer" Copy-Item "${CheckoutDir}/LICENSE" "${CheckoutDir}/installer/LICENSE" & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=x86 .\installer.nsi Ensure-Directory ${CheckoutDir} } } } function Package-Plugin-Standalone { $CheckoutDir = git rev-parse --show-toplevel if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) { . ${CheckoutDir}/CI/include/build_environment.ps1 } . ${CheckoutDir}/CI/include/build_support_windows.ps1 Ensure-Directory ${CheckoutDir} $GitBranch = git rev-parse --abbrev-ref HEAD $GitHash = git rev-parse --short HEAD $ErrorActionPreference = "SilentlyContiue" $GitTag = git describe --tags --always --dirty='-dev' $ErrorActionPreference = "Stop" if ($null -eq $GitTag) { $GitTag="v$ProductVersion" } elseif ($GitTag -match "[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$") { $ProductVersion = $Matches[0] } if ($ProductVersion -match "^[0-9]+.[0-9]+.[0-9]+") { $ProductFileVersion = $Matches[0] + ".0" } $FileName = "${ProductName}-v${ProductVersion}" Package-OBS-Plugin } function Print-Usage { $Lines = @( "Usage: ${MyInvocation.MyCommand.Name}", "-Help : Print this help", "-Quiet : Suppress most build process output", "-Verbose : Enable more verbose build process output", "-CombinedArchs : Create combined architecture package", "-BuildDirectory : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems", "-BuildArch : Build architecture to use (32bit or 64bit) - Default: local architecture", "-BuildConfiguration : Build configuration to use - Default: RelWithDebInfo" ) $Lines | Write-Host } if(!(Test-Path variable:_RunObsBuildScript)) { if($Help.isPresent) { Print-Usage exit 0 } Package-Plugin-Standalone } ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) include(external/GitInfoHelper.cmake) get_git_version(OBS_PLUGUN_GIT_TAG OBS_PLUGUIN_VERSION OBS_PLUGUIN_SHORT_VERSION OBS_PLUGUIN_LONG_VERSION) project(obs-rtspserver VERSION ${OBS_PLUGUIN_LONG_VERSION} HOMEPAGE_URL "https://obsproject.com/forum/resources/obs-rtspserver.1037" DESCRIPTION "RTSP server plugin for obs-studio") set(LINUX_MAINTAINER_EMAIL "scottxu@scottxublog.com") set(LINUX_MAINTAINER "Scott Xu") set(MACOS_BUNDLEID "com.scottxu.obs-rtspserver") set(OBS_PLUGIN_OBS_SOURCE_DIR ${CMAKE_SOURCE_DIR}) set(OBS_FRONTEND_API_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/UI/obs-frontend-api") add_library(${CMAKE_PROJECT_NAME} MODULE) if (NOT COMMAND setup_plugin_target) include(external/BuildHelper.cmake) endif() add_subdirectory(rtsp-server) if(CMAKE_VERSION VERSION_LESS "3.7.0") set(CMAKE_INCLUDE_CURRENT_DIR ON) endif() #set(CMAKE_AUTOMOC ON) #set(CMAKE_AUTOUIC ON) set(OBS_RTSPSERVER_SOURCES rtsp_main.cpp rtsp_output.cpp rtsp_properties.cpp rtsp_output_helper.cpp ) set(OBS_RTSPSERVER_HEADERS rtsp_output.h rtsp_properties.h rtsp_output_helper.h ) file(GLOB OBS_RTSPSERVER_UI_SOURCES ui/*.cpp) file(GLOB OBS_RTSPSERVER_MAIN_SOURCES *.cpp) set(OBS_RTSPSERVER_SOURCES ${OBS_RTSPSERVER_UI_SOURCES} ${OBS_RTSPSERVER_MAIN_SOURCES} ) file(GLOB OBS_RTSPSERVER_UI_HEADERS ui/*.hpp) file(GLOB OBS_RTSPSERVER_MAIN_HEADERS *.h) set(OBS_RTSPSERVER_HEADERS ${OBS_RTSPSERVER_UI_HEADERS} ${OBS_RTSPSERVER_MAIN_HEADERS} ) set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY CXX_STANDARD 17) set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY C_STANDARD 17) find_qt(COMPONENTS Widgets Core) set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON) target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${OBS_RTSPSERVER_SOURCES} ${OBS_RTSPSERVER_HEADERS}) if(WIN32) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc.in ${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc) target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc) endif() target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${OBS_FRONTEND_API_INCLUDE_DIRS} ${LIBOBS_INCLUDE_DIRS} "rtsp-server") target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE rtsp-server obs-frontend-api libobs Qt::Core Qt::Widgets) add_definitions(-DVERSION_STRING="${OBS_PLUGUIN_VERSION}") if(APPLE) set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES FOLDER "plugins" PRODUCTNAME "OBS RTSP Server Plugin") else() set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES FOLDER "plugins" VERSION "${OBS_PLUGUIN_VERSION}" PRODUCTNAME "OBS RTSP Server Plugin") endif() setup_plugin_target(${CMAKE_PROJECT_NAME}) ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ================================================ FILE: README.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer This is a plugin for obs-studio, encode and publish to a RTSP stream. **Supported Platforms** : Windows 10, Windows 11, Linux and macOS **Supported OBS Studio version** : 30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Install ## Windows The installer can be found in [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases). If you want to use compressed file to install manually, you can unzip it (e.g.: obs-rtspserver-v2.0.5-windows.zip) and put it to your obs-studio install folder. ### winget Package If the operating system version is Windows 10 1709 or later and [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) has been installed, just run this to install it: ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS You can use the .pkg installer to install and the installer can be found in [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) if use macOS. ## Linux (Only x64) ### Ubuntu/Debian DEB Package Download the deb package from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and install it. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Replace {version} with last release version, e.g.: v2.2.0 ### Red-Hat RPM Package Download the rpm package from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and install it. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Replace {version} with last release version, e.g.: v2.2.0 ### ArchLinux AUR Package obs-rtspserver is also available as an [AUR Package](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) If you use [yay](https://github.com/Jguer/yay) just run this to install it: ```bash yay -S obs-rtspserver ``` ### Other Download the tar.gz archive from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and unpack to "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Replace {version} with last release version, e.g.: v2.2.0 # Build * Install cmake, visual studio(only windows) and qt. * Download and configure the source code of obs-studio. * Copy source code to (obs-studio source code)/plugins/obs-rtspserver/ * Add `add_subdirectory(obs-rtspserver)` to (obs-studio source code)/plugins/CMakeLists.txt * Build obs-rtspserver. # FAQ * [Can't find the plugin in the menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # License * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_de-DE.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Helfen Sie bei der Übersetzung von obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Dies ist ein Plugin für obs-studio, das die Ausgabe codiert und ein RTSP-Stream veröffentlicht. **Unterstützte Betriebssysteme** : Windows 10, Windows 11, Linux und macOS **Unterstützte OBS Studio Version**: 30.0.0+ [![Paketstatus](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Installation ## Windows Der Installer kann auf der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) gefunden werden. Wenn Sie eine komprimierte Datei verwenden möchten, um manuell zu installieren, können Sie sie entpacken (z.B.: obs-rtspserver-v2.0.5-windows.zip) und in den Installationsordner von obs-studio legen. ### winget Paket Wenn die Betriebssystemversion Windows 10 1709 oder neuer ist und [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) installiert wurde, führen Sie einfach Folgendes aus, um es zu installieren: ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS Sie können den .pkg-Installer verwenden, um zu installieren, und der Installer kann unter [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) gefunden werden, wenn macOS verwendet wird. ## Linux (Nur x64) ### Ubuntu/Debian DEB-Paket Laden Sie das deb-Paket von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und installieren Sie es. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0 ### Red-Hat RPM-Paket Laden Sie das RPM-Paket von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und installieren Sie es. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0 ### ArchLinux AUR-Paket obs-rtspserver ist auch als [AUR-Paket](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) verfügbar. Wenn Sie [yay](https://github.com/Jguer/yay) verwenden, führen Sie einfach Folgendes aus, um es zu installieren: ```bash yay -S obs-rtspserver ``` ### Andere Laden Sie das tar.gz-Archiv von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und entpacken Sie es in "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0 # Bauen * Install cmake, visual studio(only windows) and qt. * Laden Sie den Quellcode von obs-studio herunter und konfigurieren Sie ihn. * Kopieren Sie den Quellcode in (obs-studio Quellcode)/plugins/obs-rtspserver/ * Fügen Sie `add_subdirectory(obs-rtspserver)` zu (obs-studio Quellcode)/plugins/CMakeLists.txt hinzu. * Build obs-rtspserver. # FAQ * [Kann das Plugin im Menü nicht finden](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Lizenz * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_es-ES.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Ayuda a traducir obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Este es un complemento para obs-studio que codificará la salida y publicará un flujo RTSP. **Sistemas operativos compatibles** : Windows 10, Windows 11, Linux y macOS **Versión de OBS Studio compatible**: 30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Instalar ## Windows El instalador se puede encontrar en [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases). Si quieres usar un archivo comprimido para instalar manualmente, puedes descomprimirlo (por ejemplo: obs-rtspserver-v2.0.5-windows.zip) y colocarlo en la carpeta de instalación de tu obs-studio. ### winget Paquete Si la versión del sistema operativo es Windows 10 1709 o posterior y se ha instalado [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1), simplemente ejecuta esto para instalarlo: ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS Puede utilizar el instalador .pkg para instalar y el instalador se puede encontrar en [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) si utiliza macOS. ## Linux (Solo x64) ### Paquete DEB de Ubuntu/Debian Descarga el paquete deb desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) e instálalo. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0 ### Paquete Red-Hat RPM Descargue el paquete rpm desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) e instálelo. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0 ### ArchLinux AUR Paquete obs-rtspserver también está disponible como un [Paquete AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) Si usas [yay](https://github.com/Jguer/yay) simplemente ejecuta esto para instalarlo: ```bash yay -S obs-rtspserver ``` ### Otros Descargue el archivo tar.gz desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) y descomprímalo en "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0 # Construir * Instala cmake, visual studio (solo en Windows) y qt. * Descarga y configura el código fuente de obs-studio. * Copiar el código fuente a (código fuente de obs-studio)/plugins/obs-rtspserver/ * Añade `add_subdirectory(obs-rtspserver)` a (código fuente de obs-studio)/plugins/CMakeLists.txt * Construye obs-rtspserver. # Preguntas frecuentes * [No puedo encontrar el complemento en el menú](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Licencia * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_fr-FR.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Ceci est un plugin pour obs-studio, encoder et publier dans un flux RTSP. **Systèmes d'exploitation pris en charge** : Windows 10, Windows 11, Linux and macOS **Version OBS Studio prise en charge** : 30.0.0+ [![Statut de l'emballage](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Installer ## Windows Vous pouvez utiliser le programme d'installation pour l'installation et le programme d'installation se trouve dans [Page de version](https://github.com/iamscottxu/obs-rtspserver/releases) si vous utilisez Windows. Si vous souhaitez utiliser un fichier compressé pour l'installer manuellement, vous pouvez le décompresser (par exemple : obs-rtspserver-v2.0.5-windows.zip) et le placer dans votre dossier d'installation obs-studio. ### winget Package Si la version du système d'exploitation est Windows 10 1709 ou ultérieure et que [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) a été installé, il suffit d'exécuter ceci pour l'installer : ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS Vous pouvez utiliser l'installateur .pkg pour installer et l'installateur peut être trouvé dans [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) si vous utilisez macOS. ## Linux (Seulement x64) ### ArchLinux AUR Paquet Téléchargez le paquet deb depuis la [Page de sortie](https://github.com/iamscottxu/obs-rtspserver/releases) et installez-le. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0 ### Package Red-Hat RPM Téléchargez le package rpm depuis la [Page de sortie](https://github.com/iamscottxu/obs-rtspserver/releases) et installez-le. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0 ### ArchLinux AUR Package obs-rtspserver est également disponible en tant que [paquet AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) Si vous utilisez [yay](https://github.com/Jguer/yay), exécutez simplement ceci pour l'installer : ```bash yay -S obs-rtspserver ``` ### Autre Téléchargez l'archive tar.gz depuis la [Page de publication](https://github.com/iamscottxu/obs-rtspserver/releases) et décompressez-la dans "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0 # Construire * Installer cmake, visual studio (uniquement sur Windows) et qt. * Téléchargez et configurez le code source d'obs-studio. * Copiez le code source vers (code source obs-studio)/plugins/obs-rtspserver/ * Ajoutez `add_subdirectory(obs-rtspserver)` à (obs-studio source code)/plugins/CMakeLists.txt * Construisez obs-rtspserver. # FAQ * [Impossible de trouver le plugin dans le menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Licence * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_it-IT.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Questo è un plugin per obs-studio che codificherà l'output e pubblicherà un flusso RTSP. **Sistemi operativi supportati** : Windows 10, Windows 11, Linux e macOS **Versione di OBS Studio supportata**: 30.0.0+ [![Stato del pacchetto](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Installazione ## Windows È possibile utilizzare il programma di installazione per l'installazione. Puoi trovare il programma di installazione qui [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases). Se desideri utilizzare un file compresso per l'installazione manuale, puoi decomprimerlo (ad esempio obs-rtspserver-v2.0.5-windows.zip) e salvarlo nella cartella di installazione di obs-studio. ### winget Pacchetto Se il sistema operativo è Windows 10 1709 o successivo e [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) è stato installato, esegui semplicemente questo per installarlo: ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS È possibile utilizzare il programma di installazione .pkg per l'installazione. Puoi trovare il programma di installazione qui [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases). ## Linux (Solo x64) ### Ubuntu/Debian Pacchetto DEB Scarica il pacchetto deb dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e installalo. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0 ### Red-Hat RPM Package Scarica il pacchetto rpm dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e installalo. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0 ### ArchLinux Pacchetto AUR obs-rtspserver è disponibile anche come [Pacchetto AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver). Se stai usando [yay](https://github.com/Jguer/yay) puoi installarlo con il seguente comando: ```bash yay -S obs-rtspserver ``` ### Altro Scarica l'archivio tar.gz dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e decomprimilo in "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0 # Costruisci * Installa cmake, visual studio (solo su Windows) e qt. * Scarica e configura il codice sorgente da obs-studio. * Copia il codice sorgente in (codice sorgente obs-studio)/plugins/obs-rtspserver/ * Aggiungi `add_subdirectory (obs-rtspserver)` a (codice sorgente obs-studio)/plugins/CMakeLists.txt. * Avvia la build obs-rtspserver. # FAQ * [Il plugin nel menu non è stato trovato](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Licenze software * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_ja-JP.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [obs-rtspserverを翻訳するのを助けてください!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer これはobs-studioのプラグインであり、RTSPストリームにエンコードして公開するものです。 **サポートされているプラットフォーム**:Windows 10、Windows 11、Linux、およびmacOS **サポートされているOBS Studioのバージョン** : 30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # インストール ## Windows [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) でインストーラーを見つけることができます。 手動でインストールするために圧縮ファイルを使用したい場合は、それを解凍して(例:obs-rtspserver-v2.0.5-windows.zip)、obs-studioのインストールフォルダに配置してください。 ### winget パッケージ Windows 10 1709以降のオペレーティングシステムバージョンで、[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)がインストールされている場合は、次のコマンドを実行してインストールしてください。 ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS .macOSを使用する場合、[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)で.pkgインストーラを使用してインストールできます。 ## Linux (Only x64) ### Ubuntu/Debian DEB パッケージ debパッケージを[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からダウンロードしてインストールしてください。 ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * 「{version}」を最新のリリースバージョンで置き換えてください。例:v2.2.0 ### Red-Hat RPM パッケージ rpmパッケージを[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からダウンロードしてインストールしてください。 ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * {version}を最新のリリースバージョンであるv2.2.0などで置き換えてください。 ### ArchLinux AUR パッケージ obs-rtspserverは[AURパッケージ](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)としても利用可能です。 [yay](https://github.com/Jguer/yay)を使用している場合は、次のコマンドを実行してインストールしてください。 ```bash yay -S obs-rtspserver ``` ### 他の [リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からtar.gzアーカイブをダウンロードし、"/"に展開してください。 ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * {version}を最新のリリースバージョンであるv2.2.0などで置き換えてください。 # ビルド * cmake、visual studio(Windowsのみ)およびqtをインストールしてください。 * ダウンロードして、obs-studioのソースコードを設定してください。 * ソースコードを(obs-studioソースコード)/plugins/obs-rtspserver/にコピーしてください。 * (obs-studioのソースコード)/plugins/CMakeLists.txtに`add_subdirectory(obs-rtspserver)`を追加してください。 * Build obs-rtspserver. # FAQ * [メニューでプラグインが見つかりません](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # ライセンス * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_ko-KR.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [obs-rtspserver를 번역하는 데 도움을 주세요!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer 이 플러그인은 obs-studio에서 RTSP스트림을 인코딩 및 배포하는 플러그인입니다. **지원하는 운영체제** : Windows 10, Windows 11, Linux and macOS **지원되는 OBS Studio 버전** : 30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # 설치 ## Windows 설치 파일은 [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases)에서 찾을 수 있습니다. 직접 설치하려면 압축파일을 다운로드 받은 후 (예: obs-rtspserver-v2.0.5-windows.zip) obs-studio가 설치된 폴더에 압축을 푸십시오. ### winget 패키지 Windows 10 1709 이상 버전을 사용하고 있고 [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)가 설치되어 있다면 아래 명령어를 실행하여 바로 설치할 수 있습니다: ```powershell winget install iamscottxu.obs-rtspserver ``` ## macOS macOS에서는 [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases)에서 .pkg 설치파일을 찾아 설치할 수 있습니다. ## Linux (x64만 지원) ### 우분투/데비안 DEB 패키지 deb 패키지를 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 다운로드하여 설치하십시오. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0 ### 레드햇 RPM 패키지 rpm 패키지를 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 다운로드하여 설치하십시오. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0 ### ArchLinux AUR 패키지 obs-rtspserver는 [AUR 패키지](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)로도 사용할 수 있습니다. 만약 [yay](https://github.com/Jguer/yay)를 사용한다면 다음 명령을 실행하여 설치하세요. ```bash yay -S obs-rtspserver ``` ### 다른 "/"에서 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 tar.gz 아카이브를 다운로드하고 압축을 해제하세요. ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0 # 빌드 * cmake, visual studio(only windows) 및 qt를 설치하십시오. * 다운로드하고 obs-studio의 소스 코드를 구성하세요. * 소스 코드를 (obs-studio 소스 코드)/plugins/obs-rtspserver/에 복사하세요. * (obs-studio 소스 코드)/plugins/CMakeLists.txt에 `add_subdirectory(obs-rtspserver)`를 추가하세요. * obs-rtspserver를 빌드하세요. # 자주 묻는 질문 * [메뉴에서 플러그인을 찾을 수 없습니다](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # 라이선스 * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_nl-NL.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Dit is een plug-in voor obs-studio die de uitvoer codeert en een RTSP-stream publiceert. **Ondersteunde besturingssystemen** : Windows 10, Windows 11, Linux and macOS **Ondersteunde OBS Studio-versie**: 30.0.0+ [![Verpakkingsstatus](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Installatie ## Windows U kunt het installatieprogramma gebruiken om het te installeren. Je vindt het installatieprogramma hier [release-pagina](https://github.com/iamscottxu/obs-rtspserver/releases). Als je een gecomprimeerd bestand wilt gebruiken voor handmatige installatie, kun je het uitpakken (bijv. obs-rtspserver-v2.0.5-windows.zip) en het in je obs-studio installatiemap plaatsen. ### winget Pakket Als de versie van het besturingssysteem Windows 10 1709 of later is en [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) is geïnstalleerd, voer dan gewoon dit uit om het te installeren: ```powershell winget install iamscottxu.obs-rtspserver ``` ## macOS U kunt de .pkg-installateur gebruiken om te installeren en de installateur kan worden gevonden op [Release-pagina](https://github.com/iamscottxu/obs-rtspserver/releases) als u macOS gebruikt. ## Linux (Alleen x64) ### Ubuntu/Debian DEB-pakket Download het deb-pakket van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en installeer het. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0 ### Red-Hat RPM-pakket Download het rpm-pakket van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en installeer het. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0 ### ArchLinux AUR-pakket obs-rtspserver is ook beschikbaar als een [AUR-pakket](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) Als je [yay](https://github.com/Jguer/yay) gebruikt, voer dan gewoon dit uit om het te installeren: ```bash yay -S obs-rtspserver ``` ### Andere Download het tar.gz-archief van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en pak het uit naar "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0 # Bouwen * Installeer cmake, visual studio (alleen Windows) en qt. * Download en configureer de broncode van obs-studio. * Kopieer de broncode naar (obs-studio broncode)/plugins/obs-rtspserver/ * Voeg `add_subdirectory(obs-rtspserver)` toe aan (obs-studio broncode)/plugins/CMakeLists.txt * Bouw obs-rtspserver. # Veelgestelde vragen * [Kan de plugin niet vinden in het menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Licentie * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_ru-RU.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [Помогите перевести obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer Это плагин для obs-studio, кодирующий и публикующий поток RTSP. **Поддерживаемые платформы**: Windows 10, Windows 11, Linux и macOS **Поддерживаемая версия OBS Studio**: 30.0.0+ [![Статус упаковки](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # Установить ## Windows Установщик можно найти на [странице релизов](https://github.com/iamscottxu/obs-rtspserver/releases). Если вы хотите использовать сжатый файл для ручной установки, вы можете распаковать его (например: obs-rtspserver-v2.0.5-windows.zip) и поместить его в папку установки вашей программы obs-studio. ### winget Package Если версия операционной системы Windows 10 1709 или более поздняя и [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) был установлен, просто запустите это, чтобы установить его. ```powershell winget install iamscottxu.obs-rtspserver ``` ## MacOS Вы можете использовать установщик .pkg для установки, и установщик можно найти на [странице релизов](https://github.com/iamscottxu/obs-rtspserver/releases), если вы используете macOS. ## Linux (Только x64) ### Ubuntu/Debian DEB Package Скачайте deb-пакет с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и установите его. ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * Замените {version} на последнюю версию релиза, например: v2.2.0 ### Red-Hat RPM Package Скачайте пакет rpm с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и установите его. ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * Замените {version} на последнюю версию релиза, например: v2.2.0 ### ArchLinux AUR Package obs-rtspserver также доступен в виде [AUR Package](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver) Если вы используете [yay](https://github.com/Jguer/yay), просто выполните следующую команду для его установки: ```bash yay -S obs-rtspserver ``` ### Другое Скачайте архив tar.gz с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и распакуйте его в корневой каталог "/". ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * Замените {version} на последнюю версию релиза, например: v2.2.0 # Создать * Установите cmake, visual studio (только для Windows) и qt. * Скачайте и настройте исходный код obs-studio. * Скопируйте исходный код в (исходный код obs-studio)/plugins/obs-rtspserver/ * Добавьте `add_subdirectory(obs-rtspserver)` в (исходный код obs-studio)/plugins/CMakeLists.txt * Соберите obs-rtspserver. # ЧАВО * [Не удается найти плагин в меню](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu) # Лицензия * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_zh-CN.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [帮助翻译 obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer 这是一个 OBS Studio 插件,此插件可对输出进行编码并发布 RTSP 流。 **支持的平台** : Windows 10、Windows 11、Linux和macOS **支持的 OBS Studio 版本**:30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # 安装 ## Windows 可以使用安装程序进行安装,安装程序可以在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。 如果要使用压缩文件手动安装,可以解压缩压缩文件(例如:obs-rtspserver-v2.0.5-windows.zip)把它放到你的 OBS Studio 安装文件夹里。 ### winget 软件包 如果你使用 Windows 10 1709 和以后的版本,且已经安装了[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)后,可以运行以下命令进行安装: ```powershell winget install iamscottxu.obs-rtspserver ``` ## macOS 如果你使用的是 macOS 操作系统,您可以使用安装程序进行安装,安装程序 .pkg 可以在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。 ## Linux(仅 x64) ### Ubuntu/Debian DEB 软件包 在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 deb 软件包并安装。 ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * 将 {version} 替换成最新发布版本号,例如:v2.2.0 ### Red-Hat RPM 软件包 在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 rpm 软件包并安装。 ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * 将 {version} 替换成最新发布版本号,例如:v2.2.0 ### ArchLinux AUR 软件包 obs-rtspserver也可以作为[AUR软件包](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)提供。如果您使用[yay](https://github.com/Jguer/yay),请运行以下命令进行安装: ```bash yay -S obs-rtspserver ``` ### 其他 在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 tar.gz 压缩包并解压到 "/"。 ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * 将 {version} 替换成最新发布版本号,例如:v2.2.0 # 生成 * 安装 cmake 、 visual studio (仅 Windows )和 qt; * 下载并配置 OBS Studio 的源代码; * 将源代码复制到 (OBS Studio 源代码目录)/plugins/obs-rtspserver/ 中; * 添加 `add_subdirectory(obs-rtspserver)` 到 (OBS Studio 源代码目录)/plugins/CMakeLists.txt 中; * 生成 obs-rtspserver 。 # 常见问题 * [在菜单中找不到插件](https://github.com/iamscottxu/obs-rtspserver/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98#%E5%9C%A8%E8%8F%9C%E5%8D%95%E4%B8%AD%E6%89%BE%E4%B8%8D%E5%88%B0%E6%8F%92%E4%BB%B6) # 许可信息 * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: README_zh-TW.md ================================================ ![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg) ![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg) ![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg) ![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg) ![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg) 🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md) 🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md) 🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md) 🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md) 🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md) 🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md) 🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md) 🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md) 🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md) 🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md) [幫助翻譯 obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver) # OBS-RTSPServer 這是一個 OBS Studio 插件,此外掛程式可對輸出進行編碼並發布 RTSP 流。 **支援的平台** : Windows 10、Windows 11、Linux和macOS **支援的 OBS Studio 版本**:30.0.0+ [![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions) # 安裝 ## Windows 可以使用安裝程式進行安裝,安裝程式可以在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。 如果要使用壓縮檔案手動安裝,可以解壓縮壓縮檔案(例如:obs-rtspserver-v2.0.5-windows.zip)把它放到你的 OBS Studio 安裝資料夾裡。 ### winget 軟體包 如果你使用 Windows 10 1709 和以後的版本,且已經安裝了[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)後,可以執行下列指令安裝: ```powershell winget install iamscottxu.obs-rtspserver ``` ## macOS 如果你使用的是 macOS 作業系統,您可以使用安裝程式進行安裝,安裝程式 .pkg 可以在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。 ## Linux(僅 x64) ### Ubuntu/Debian DEB 軟體包 在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 deb 軟體包並安裝。 ```bash wget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb apt install -y obs-rtspserver-linux.deb ``` * 将 {version} 替换成最新发布版本号,例如:v2.2.0 ### Red-Hat RPM 軟體包 在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 rpm 軟體包並安裝。 ```bash wget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm rpm -ivh obs-rtspserver-linux.rpm ``` * 將 {version} 替換成最新發布版本號,例如:v2.2.0 ### ArchLinux AUR 軟體包 obs-rtspserver也可以作為[AUR軟體包](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)提供。 如果您使用[yay](https://github.com/Jguer/yay),請執行下列命令進行安裝: ```bash yay -S obs-rtspserver ``` ### 其他 在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 tar.gz 壓縮套件並解壓縮到 "/"。 ```bash wget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz #For all user tar -xzvf obs-rtspserver-linux.tar.gz -C / #For local user mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/ mkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/ mkdir -p ~/obs-rtspserver-linux tar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/ mv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so mv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale rm -rf ~/obs-rtspserver-linux ``` * 將 {version} 替換成最新發布版本號,例如:v2.2.0 # 產生 * 安裝 cmake 、 visual studio (僅 Windows )和 qt; * 下載並配置 OBS Studio 的源代碼; * 將原始碼複製到 (OBS Studio 原始碼目錄)/plugins/obs-rtspserver/ 中; * 加入 `add_subdirectory(obs-rtspserver)` 到 (OBS Studio 原始碼目錄)/plugins/CMakeLists.txt 中; * 產生 obs-rtspserver 。 # 常見問題 * [在選單中找不到外掛程式](https://github.com/iamscottxu/obs-rtspserver/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98#%E5%9C%A8%E8%8F%9C%E5%8D%95%E4%B8%AD%E6%89%BE%E4%B8%8D%E5%88%B0%E6%8F%92%E4%BB%B6) # 許可資訊 * [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE) * [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html) * [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE) ================================================ FILE: bundle/installer-macos.pkgproj.in ================================================ PACKAGES MUST-CLOSE-APPLICATION-ITEMS MUST-CLOSE-APPLICATIONS PACKAGE_FILES DEFAULT_INSTALL_LOCATION /Library/Application Support/obs-studio/plugins HIERARCHY CHILDREN CHILDREN GID 80 PATH Applications PATH_TYPE 0 PERMISSIONS 509 TYPE 1 UID 0 CHILDREN CHILDREN CHILDREN CHILDREN CHILDREN GID 80 PATH ../@RELATIVE_INSTALL_PATH@/@CMAKE_PROJECT_NAME@ PATH_TYPE 1 PERMISSIONS 493 TYPE 3 UID 0 GID 80 PATH plugins PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 80 PATH obs-studio PATH_TYPE 2 PERMISSIONS 509 TYPE 2 UID 0 GID 80 PATH Application Support PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Automator PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Documentation PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Extensions PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Filesystems PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Frameworks PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Input Methods PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Internet Plug-Ins PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchAgents PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH LaunchDaemons PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PreferencePanes PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Preferences PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 80 PATH Printers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH PrivilegedHelperTools PATH_TYPE 0 PERMISSIONS 1005 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickLook PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH QuickTime PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Screen Savers PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Scripts PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Services PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN GID 0 PATH Widgets PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH Library PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 CHILDREN CHILDREN GID 0 PATH Shared PATH_TYPE 0 PERMISSIONS 1023 TYPE 1 UID 0 GID 80 PATH Users PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 GID 0 PATH / PATH_TYPE 0 PERMISSIONS 493 TYPE 1 UID 0 PAYLOAD_TYPE 0 PRESERVE_EXTENDED_ATTRIBUTES SHOW_INVISIBLE SPLIT_FORKS TREAT_MISSING_FILES_AS_WARNING VERSION 5 PACKAGE_SCRIPTS POSTINSTALL_PATH PATH_TYPE 0 PREINSTALL_PATH PATH_TYPE 0 RESOURCES PACKAGE_SETTINGS AUTHENTICATION 0 CONCLUSION_ACTION 0 FOLLOW_SYMBOLIC_LINKS IDENTIFIER @MACOS_BUNDLEID@ LOCATION 0 NAME @CMAKE_PROJECT_NAME@ OVERWRITE_PERMISSIONS PAYLOAD_SIZE -1 REFERENCE_PATH RELOCATABLE USE_HFS+_COMPRESSION VERSION @PROJECT_VERSION@ TYPE 0 UUID 7C03585F-BD40-434A-9436-FBFD8700B437 PROJECT PROJECT_COMMENTS NOTES PROJECT_PRESENTATION BACKGROUND APPAREANCES DARK_AQUA LIGHT_AQUA SHARED_SETTINGS_FOR_ALL_APPAREANCES INSTALLATION TYPE HIERARCHIES INSTALLER LIST CHILDREN DESCRIPTION OPTIONS HIDDEN STATE 0 PACKAGE_UUID 7C03585F-BD40-434A-9436-FBFD8700B437 TITLE TYPE 0 UUID 4F4C1C62-FC2C-4E46-8FBD-CD875E187DD2 REMOVED MODE 1 INSTALLATION_STEPS ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewIntroductionController INSTALLER_PLUGIN Introduction LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewReadMeController INSTALLER_PLUGIN ReadMe LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewLicenseController INSTALLER_PLUGIN License LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewDestinationSelectController INSTALLER_PLUGIN TargetSelect LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationTypeController INSTALLER_PLUGIN PackageSelection LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewInstallationController INSTALLER_PLUGIN Install LIST_TITLE_KEY InstallerSectionTitle ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS ICPresentationViewSummaryController INSTALLER_PLUGIN Summary LIST_TITLE_KEY InstallerSectionTitle INTRODUCTION LOCALIZATIONS LICENSE LOCALIZATIONS LANGUAGE English VALUE PATH LICENSE.txt PATH_TYPE 1 MODE 0 README LOCALIZATIONS SUMMARY LOCALIZATIONS TITLE LOCALIZATIONS LANGUAGE English VALUE OBS RTSP Server Plugin LANGUAGE Dutch VALUE OBS RTSP Server Plugin LANGUAGE Simplified Chinese VALUE OBS RTSP 服务器插件 LANGUAGE Traditional Chinese VALUE OBS RTSP 伺服器外掛程式 LANGUAGE Japanese VALUE OBS RTSPサーバープラグイン LANGUAGE Spanish VALUE Complemento servidor RTSP para OBS LANGUAGE German VALUE OBS RTSP Server Plugin LANGUAGE French VALUE Module d'extension de serveur RTSP OBS LANGUAGE Italian VALUE OBS RTSP Server Plugin LANGUAGE Korean VALUE OBS RTSP 서버 플러그인 PROJECT_REQUIREMENTS LIST BEHAVIOR 3 DICTIONARY IC_REQUIREMENT_FILES_CONDITION 0 IC_REQUIREMENT_FILES_DISK_TYPE 0 IC_REQUIREMENT_FILES_LIST /Applications/OBS.app IC_REQUIREMENT_FILES_SELECTOR 0 IC_REQUIREMENT_CHECK_TYPE 1 IDENTIFIER fr.whitebox.Packages.requirement.files MESSAGE NAME Files STATE BEHAVIOR 3 DICTIONARY IC_REQUIREMENT_OS_DISK_TYPE 1 IC_REQUIREMENT_OS_DISTRIBUTION_TYPE 0 IC_REQUIREMENT_OS_MINIMUM_VERSION 101300 IC_REQUIREMENT_CHECK_TYPE 0 IDENTIFIER fr.whitebox.Packages.requirement.os MESSAGE NAME Operating System STATE RESOURCES ROOT_VOLUME_ONLY PROJECT_SETTINGS ADVANCED_OPTIONS installer-script.domains:enable_currentUserHome 1 installer-script.domains:enable_localSystem 1 BUILD_FORMAT 0 BUILD_PATH PATH ../@RELATIVE_BUILD_PATH@ PATH_TYPE 1 EXCLUDED_FILES PATTERNS_ARRAY REGULAR_EXPRESSION STRING .DS_Store TYPE 0 PROTECTED PROXY_NAME Remove .DS_Store files PROXY_TOOLTIP Remove ".DS_Store" files created by the Finder. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING .pbdevelopment TYPE 0 PROTECTED PROXY_NAME Remove .pbdevelopment files PROXY_TOOLTIP Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING CVS TYPE 1 REGULAR_EXPRESSION STRING .cvsignore TYPE 0 REGULAR_EXPRESSION STRING .cvspass TYPE 0 REGULAR_EXPRESSION STRING .svn TYPE 1 REGULAR_EXPRESSION STRING .git TYPE 1 REGULAR_EXPRESSION STRING .gitignore TYPE 0 PROTECTED PROXY_NAME Remove SCM metadata PROXY_TOOLTIP Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING classes.nib TYPE 0 REGULAR_EXPRESSION STRING designable.db TYPE 0 REGULAR_EXPRESSION STRING info.nib TYPE 0 PROTECTED PROXY_NAME Optimize nib files PROXY_TOOLTIP Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. STATE PATTERNS_ARRAY REGULAR_EXPRESSION STRING Resources Disabled TYPE 1 PROTECTED PROXY_NAME Remove Resources Disabled folders PROXY_TOOLTIP Remove "Resources Disabled" folders. STATE SEPARATOR NAME @CMAKE_PROJECT_NAME@ PAYLOAD_ONLY TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING TYPE 0 VERSION 2 ================================================ FILE: bundle/macOS/Plugin-Info.plist.in ================================================ CFBundleName ${MACOSX_PLUGIN_BUNDLE_NAME} CFBundleIdentifier ${MACOSX_PLUGIN_GUI_IDENTIFIER} CFBundleVersion ${MACOSX_PLUGIN_BUNDLE_VERSION} CFBundleShortVersionString ${MACOSX_PLUGIN_SHORT_VERSION_STRING} CFBundleInfoDictionaryVersion 6.0 CFBundleExecutable ${MACOSX_PLUGIN_EXECUTABLE_NAME} CFBundlePackageType ${MACOSX_PLUGIN_BUNDLE_TYPE} CFBundleSupportedPlatforms MacOSX LSMinimumSystemVersion 10.13 ================================================ FILE: bundle/macOS/entitlements.plist ================================================ com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.device.camera com.apple.security.device.audio-input com.apple.security.cs.disable-library-validation com.apple.security.cs.allow-dyld-environment-variables ================================================ FILE: data/locale/de-DE.ini ================================================ RtspServer="RTSP Server" RstpServer.Description="OBS RTSP Server Plugin" RtspServer.Properties="RTSP Server" RtspServer.Properties.StartOutput="Starten" RtspServer.Properties.StopOutput="Stoppen" RtspServer.Properties.Options="Optionen" RtspServer.Properties.Options.AutoStart="AutoStart" RtspServer.Properties.Options.EnabledAudioTracks="Audiospuren: " RtspServer.Properties.Target="Ziel" RtspServer.Properties.Target.Multicast="Aktiviertes Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Kopieren" RtspServer.Properties.Options.Output="Ausgabe-Optionen: " RtspServer.Properties.Options.Output.Tip="Wenn Sie die Ausgabe-Optionen ändern wollen öffnen Sie: Datei->Einstellungen->Ausgabe->Streaming." RtspServer.Properties.Authentication="Authentifizierung" RtspServer.Properties.Authentication.Enabled="Aktiviert" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Benutzername: " RtspServer.Properties.Authentication.Password="Passwort: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Optional)" RtspServer.Properties.Status="Status" RtspServer.Properties.Status.TotalDataSent="Gesamtdatenausgabe: " RtspServer.Properties.Status.Bitrate="Bitrate: " RtspServer.Properties.Status.DroppedFrames="Dropped Frames:" RtspServer.Properties.Version="Version: " RtspOutput="RTSP Ausgabe" RtspOutput.Error.BeginDataCapture="Datenerfassung konnte nicht gestartet werden" RtspOutput.Error.InitEncoders="Kodierungsinitialisierungsfehler" RtspOutput.Error.StartRtspServer="RTSP Server konnte nicht auf Port '%d' gestartet werden" RtspOutput.Error.StartMulticast="Multicast-Start fehlgeschlagen." RtspOutput.Error.Encode="Kodierungsfehler" RtspOutput.Error.Unknown="Unbekannter Fehler" RtspOutput.Hotkey.StartOutput="Starten" RtspOutput.Hotkey.StopOutput="Stoppen" RtspOutput.Properties.Multicast="Aktiviertes Multicast" RtspOutput.Properties.Port="Port" RtspOutput.Properties.UrlSuffix="URL-Suffix" RtspOutput.Properties.OutputAudio="Audioausgabe aktivieren" RtspOutput.Properties.Authentication="Authentifizierung" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Benutzername" RtspOutput.Properties.Authentication.Password="Passwort" Reset="Zurücksetzen" ================================================ FILE: data/locale/en-US.ini ================================================ RtspServer="RTSP Server" RstpServer.Description="OBS RTSP Server Plugin" RtspServer.Properties="RTSP Server" RtspServer.Properties.StartOutput="Start" RtspServer.Properties.StopOutput="Stop" RtspServer.Properties.Options="Options" RtspServer.Properties.Options.AutoStart="Auto Start" RtspServer.Properties.Options.EnabledAudioTracks="Audio Tracks: " RtspServer.Properties.Target="Target" RtspServer.Properties.Target.Multicast="Enabled Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Copy" RtspServer.Properties.Options.Output="Output Options: " RtspServer.Properties.Options.Output.Tip="Open File->Settings->Output->Streaming to change output options." RtspServer.Properties.Authentication="Authentication" RtspServer.Properties.Authentication.Enabled="Enabled" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Username: " RtspServer.Properties.Authentication.Password="Password: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Optional)" RtspServer.Properties.Status="Status" RtspServer.Properties.Status.TotalDataSent="Total Data Output: " RtspServer.Properties.Status.Bitrate="Bitrate: " RtspServer.Properties.Status.DroppedFrames="Dropped Frames: " RtspServer.Properties.Version="Version: " RtspOutput="RTSP Output" RtspOutput.Error.BeginDataCapture="can't begin data capture" RtspOutput.Error.InitEncoders="initialize encoders error" RtspOutput.Error.StartRtspServer="starting RTSP server failed on port '%d'" RtspOutput.Error.StartMulticast="starting multicast failed" RtspOutput.Error.Encode="encode error" RtspOutput.Error.Unknown="unknown error" RtspOutput.Hotkey.StartOutput="Start Output" RtspOutput.Hotkey.StopOutput="Stop Output" RtspOutput.Properties.Multicast="Enabled Multicast" RtspOutput.Properties.Port="Port" RtspOutput.Properties.UrlSuffix="URL Suffix" RtspOutput.Properties.OutputAudio="Enable Audio Output" RtspOutput.Properties.Authentication="Authentication" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Username" RtspOutput.Properties.Authentication.Password="Password" Reset="Reset" ================================================ FILE: data/locale/es-ES.ini ================================================ RtspServer="Servidor RTSP" RstpServer.Description="Complemento servidor RTSP para OBS" RtspServer.Properties="Servidor RTSP" RtspServer.Properties.StartOutput="Iniciar" RtspServer.Properties.StopOutput="Parar" RtspServer.Properties.Options="Opciones" RtspServer.Properties.Options.AutoStart="Autoencendido" RtspServer.Properties.Options.EnabledAudioTracks="Pistas de audio: " RtspServer.Properties.Target="Objetivo" RtspServer.Properties.Target.Multicast="Habilitado Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Copiar" RtspServer.Properties.Options.Output="Opciones de salida: " RtspServer.Properties.Options.Output.Tip="Si desea cambiar las opciones de salida, abra: Archivo->Configuración->Salida->Emisión." RtspServer.Properties.Authentication="Autenticación" RtspServer.Properties.Authentication.Enabled="Activada" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Nombre de usuario: " RtspServer.Properties.Authentication.Password="Contraseña: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Opcional)" RtspServer.Properties.Status="Estatus" RtspServer.Properties.Status.TotalDataSent="Salida de datos total: " RtspServer.Properties.Status.Bitrate="Tasa de bits: " RtspServer.Properties.Status.DroppedFrames="Frames caídos:" RtspServer.Properties.Version="Versión: " RtspOutput="Salida RTSP" RtspOutput.Error.BeginDataCapture="No se pudo iniciar la adquisición de datos" RtspOutput.Error.InitEncoders="Error de inicialización de codificación" RtspOutput.Error.StartRtspServer="El servidor RTSP no se pudo iniciar en el puerto '%d'" RtspOutput.Error.StartMulticast="error al iniciar la multidifusión" RtspOutput.Error.Encode="Error de codificación" RtspOutput.Error.Unknown="Error desconocido" RtspOutput.Hotkey.StartOutput="Iniciar" RtspOutput.Hotkey.StopOutput="Parar" RtspOutput.Properties.Multicast="Habilitado Multicast" RtspOutput.Properties.Port="Puerto de red" RtspOutput.Properties.UrlSuffix="Sufijo de URL" RtspOutput.Properties.OutputAudio="Habilitar salida de audio" RtspOutput.Properties.Authentication="Autenticación" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Nombre de usuario" RtspOutput.Properties.Authentication.Password="Contraseña" Reset="Reiniciar" ================================================ FILE: data/locale/fr-FR.ini ================================================ RtspServer="Serveur RTSP" RstpServer.Description="Module d'extension de serveur RTSP OBS" RtspServer.Properties="Serveur RTSP" RtspServer.Properties.StartOutput="Démarrer" RtspServer.Properties.StopOutput="Arrêter" RtspServer.Properties.Options="Options" RtspServer.Properties.Options.AutoStart="Démarrage automatique" RtspServer.Properties.Options.EnabledAudioTracks="Pistes audio: " RtspServer.Properties.Target="Cible" RtspServer.Properties.Target.Multicast="Activé Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Copier" RtspServer.Properties.Options.Output="Options de sortie: " RtspServer.Properties.Options.Output.Tip="Ouvrir le Fichier->Paramètres->Sortie->Streaming pour modifier les options de sortie." RtspServer.Properties.Authentication="Authentification" RtspServer.Properties.Authentication.Enabled="Activée" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Nom d'utilisateur: " RtspServer.Properties.Authentication.Password="Mot de passe: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Facultatif)" RtspServer.Properties.Status="Statut" RtspServer.Properties.Status.TotalDataSent="Sortie totale des données: " RtspServer.Properties.Status.Bitrate="Débit binaire: " RtspServer.Properties.Status.DroppedFrames="Images perdues :" RtspServer.Properties.Version="Version: " RtspOutput="Sortie RTSP" RtspOutput.Error.BeginDataCapture="ne peut pas commencer la capture de données" RtspOutput.Error.InitEncoders="Erreur d'initialisation des encodeurs" RtspOutput.Error.StartRtspServer="le démarrage du serveur RTSP a échoué sur le port '%d'" RtspOutput.Error.StartMulticast="échec du démarrage de la multidiffusion" RtspOutput.Error.Encode="erreur d'encodage" RtspOutput.Error.Unknown="erreur inconnue" RtspOutput.Hotkey.StartOutput="Démarrer la sortie" RtspOutput.Hotkey.StopOutput="Arrêter la sortie" RtspOutput.Properties.Multicast="Activé Multicast" RtspOutput.Properties.Port="Port" RtspOutput.Properties.UrlSuffix="Suffixe d'URL" RtspOutput.Properties.OutputAudio="Activer la sortie audio" RtspOutput.Properties.Authentication="Authentification" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Nom d'utilisateur" RtspOutput.Properties.Authentication.Password="Mot de passe" Reset="Réinitialiser" ================================================ FILE: data/locale/it-IT.ini ================================================ RtspServer="RTSP Server" RstpServer.Description="OBS RTSP Server Plugin" RtspServer.Properties="RTSP Server" RtspServer.Properties.StartOutput="Avvia" RtspServer.Properties.StopOutput="Ferma" RtspServer.Properties.Options="Opzioni" RtspServer.Properties.Options.AutoStart="Avvio Automatico" RtspServer.Properties.Options.EnabledAudioTracks="Tracce Audio: " RtspServer.Properties.Target="Target" RtspServer.Properties.Target.Multicast="Abilitato Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Copia" RtspServer.Properties.Options.Output="Output Opzioni: " RtspServer.Properties.Options.Output.Tip="Apri File->Impostazioni->Uscita->Dirette per cambiare le opzioni di uscita" RtspServer.Properties.Authentication="Autenticazione" RtspServer.Properties.Authentication.Enabled="Abilitato" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Nome utente: " RtspServer.Properties.Authentication.Password="Parola d'ordine: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Opzionale)" RtspServer.Properties.Status="Stato" RtspServer.Properties.Status.TotalDataSent="Totale Dati in uscita: " RtspServer.Properties.Status.Bitrate="Bitrate: " RtspServer.Properties.Status.DroppedFrames="Dropped Frames:" RtspServer.Properties.Version="Versione: " RtspOutput="Uscita RTSP" RtspOutput.Error.BeginDataCapture="impossibile iniziare l'acquisizione dei dati" RtspOutput.Error.InitEncoders="inizializzare l'errore degli encoder" RtspOutput.Error.StartRtspServer="avvio del server RTSP non riuscito sulla porta '%d'" RtspOutput.Error.StartMulticast="l'avvio della trasmissione multicast è fallito" RtspOutput.Error.Encode="errore di codifica" RtspOutput.Error.Unknown="errore sconosciuto" RtspOutput.Hotkey.StartOutput="Avvia Uscita" RtspOutput.Hotkey.StopOutput="Ferma Uscita" RtspOutput.Properties.Multicast="Abilitato Multicast" RtspOutput.Properties.Port="Porta" RtspOutput.Properties.UrlSuffix="Suffisso URL" RtspOutput.Properties.OutputAudio="Abilita l'uscita audio" RtspOutput.Properties.Authentication="Autenticazione" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Nome utente" RtspOutput.Properties.Authentication.Password="Parola d'ordine" Reset="Ripristina" ================================================ FILE: data/locale/ja-JP.ini ================================================ RtspServer="RTSPサーバー" RstpServer.Description="OBS RTSPサーバープラグイン" RtspServer.Properties="RTSPサーバー" RtspServer.Properties.StartOutput="出力開始" RtspServer.Properties.StopOutput="出力終了" RtspServer.Properties.Options="オプション" RtspServer.Properties.Options.AutoStart="自動起動" RtspServer.Properties.Options.EnabledAudioTracks="音声トラック: " RtspServer.Properties.Target="サーバー" RtspServer.Properties.Target.Multicast="マルチキャストを有効にする" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="コピー" RtspServer.Properties.Options.Output="出力オプション: " RtspServer.Properties.Options.Output.Tip="出力オプションを変更するには [ファイル]->[設定]->[出力]->[配信] を開きます。" RtspServer.Properties.Authentication="認証" RtspServer.Properties.Authentication.Enabled="認証を有効にする" RtspServer.Properties.Authentication.Realm="レルム: " RtspServer.Properties.Authentication.Username="ユーザー名: " RtspServer.Properties.Authentication.Password="パスワード: " RtspServer.Properties.Authentication.PasswordPlaceholder="(オプション)" RtspServer.Properties.Status="統計" RtspServer.Properties.Status.TotalDataSent="出力データの合計: " RtspServer.Properties.Status.Bitrate="ビットレート: " RtspServer.Properties.Status.DroppedFrames="ドロップされたフレーム:" RtspServer.Properties.Version="Version: " RtspOutput="RTSP出力" RtspOutput.Error.BeginDataCapture="データキャプチャを開始できません" RtspOutput.Error.InitEncoders="エンコーダ初期化エラー" RtspOutput.Error.StartRtspServer="ポート%dでのRTSPサーバーの起動に失敗しました" RtspOutput.Error.StartMulticast="マルチキャストの開始に失敗しました" RtspOutput.Error.Encode="エンコードエラー" RtspOutput.Error.Unknown="不明なエラー" RtspOutput.Hotkey.StartOutput="出力開始" RtspOutput.Hotkey.StopOutput="出力終了" RtspOutput.Properties.Multicast="マルチキャスト" RtspOutput.Properties.Port="ポート" RtspOutput.Properties.UrlSuffix="URLのサフィックス" RtspOutput.Properties.OutputAudio="オーディオ出力を有効にする" RtspOutput.Properties.Authentication="認証" RtspOutput.Properties.Authentication.Realm="レルム" RtspOutput.Properties.Authentication.Username="ユーザー名" RtspOutput.Properties.Authentication.Password="パスワード" Reset="リセット" ================================================ FILE: data/locale/ko-KR.ini ================================================ RtspServer="RTSP 서버" RstpServer.Description="OBS RTSP 서버 플러그인" RtspServer.Properties="RTSP 서버" RtspServer.Properties.StartOutput="스타트" RtspServer.Properties.StopOutput="중지" RtspServer.Properties.Options="옵션" RtspServer.Properties.Options.AutoStart="자동 실행" RtspServer.Properties.Options.EnabledAudioTracks="오디오 트랙: " RtspServer.Properties.Target="표적" RtspServer.Properties.Target.Multicast="활성화된 멀티캐스트" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="복사" RtspServer.Properties.Options.Output="출력 옵션: " RtspServer.Properties.Options.Output.Tip="[파일]->[설정]->[출력]->[방송] 을 열어 출력 옵션을 변경합니다." RtspServer.Properties.Authentication="인증" RtspServer.Properties.Authentication.Enabled="활성화 됨" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="사용자 이름: " RtspServer.Properties.Authentication.Password="비밀번호: " RtspServer.Properties.Authentication.PasswordPlaceholder="(옵션)" RtspServer.Properties.Status="상태" RtspServer.Properties.Status.TotalDataSent="총 데이터 출력: " RtspServer.Properties.Status.Bitrate="비트 레이트: " RtspServer.Properties.Status.DroppedFrames="드롭된 프레임:" RtspServer.Properties.Version="버전: " RtspOutput="RTSP 출력" RtspOutput.Error.BeginDataCapture="데이터 캡처를 시작할 수 없습니다." RtspOutput.Error.InitEncoders="인코더 초기화 오류" RtspOutput.Error.StartRtspServer="포트에서 RTSP 서버 시작 실패 '%d'" RtspOutput.Error.StartMulticast="멀티캐스트 시작 실패" RtspOutput.Error.Encode="인코딩 오류" RtspOutput.Error.Unknown="알수없는 오류" RtspOutput.Hotkey.StartOutput="시작 출력" RtspOutput.Hotkey.StopOutput="출력 중지" RtspOutput.Properties.Multicast="활성화된 멀티캐스트" RtspOutput.Properties.Port="포트" RtspOutput.Properties.UrlSuffix="URL 접미사" RtspOutput.Properties.OutputAudio="오디오 출력 활성화" RtspOutput.Properties.Authentication="인증" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="사용자 이름" RtspOutput.Properties.Authentication.Password="비밀번호" Reset="초기화" ================================================ FILE: data/locale/nl-NL.ini ================================================ RtspServer="RTSP Server" RstpServer.Description="OBS RTSP Server Plugin" RtspServer.Properties="RTSP Server" RtspServer.Properties.StartOutput="Starten" RtspServer.Properties.StopOutput="Stoppen" RtspServer.Properties.Options="Opties" RtspServer.Properties.Options.AutoStart="AutoStart" RtspServer.Properties.Options.EnabledAudioTracks="Audiotracks: " RtspServer.Properties.Target="Doelwit" RtspServer.Properties.Target.Multicast="Ingeschakelde Multicast" RtspServer.Properties.Target.Address="URL: " RtspServer.Properties.Target.Address.Copy="Kopiëren" RtspServer.Properties.Options.Output="Uitvoeropties: " RtspServer.Properties.Options.Output.Tip="Als u de uitvoeropties wilt wijzigen, opent u: Bestand->Instellingen->Uitvoer->Streamen." RtspServer.Properties.Authentication="Authenticatie" RtspServer.Properties.Authentication.Enabled="Enabled" RtspServer.Properties.Authentication.Realm="Realm: " RtspServer.Properties.Authentication.Username="Gebruikersnaam: " RtspServer.Properties.Authentication.Password="Wachtwoord: " RtspServer.Properties.Authentication.PasswordPlaceholder="(Optioneel)" RtspServer.Properties.Status="Toestand" RtspServer.Properties.Status.TotalDataSent="Totale gegevensoutput: " RtspServer.Properties.Status.Bitrate="Bitsnelheid: " RtspServer.Properties.Status.DroppedFrames="Dropped Frames:" RtspServer.Properties.Version="Versie: " RtspOutput="RTSP Uitvoer" RtspOutput.Error.BeginDataCapture="Data-acquisitie kan niet worden gestart" RtspOutput.Error.InitEncoders="initialiseer encoderfout" RtspOutput.Error.StartRtspServer="RTSP-server kan niet worden gestart op poort '%d'" RtspOutput.Error.StartMulticast="starten van multicast mislukt" RtspOutput.Error.Encode="Coderingsfout" RtspOutput.Error.Unknown="Onbekende fout" RtspOutput.Hotkey.StartOutput="Starten" RtspOutput.Hotkey.StopOutput="Stoppen" RtspOutput.Properties.Multicast="Ingeschakelde Multicast" RtspOutput.Properties.Port="Poort" RtspOutput.Properties.UrlSuffix="Url-achtervoegsel" RtspOutput.Properties.OutputAudio="Audio-uitvoer inschakelen" RtspOutput.Properties.Authentication="Authenticatie" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Gebruikersnaam" RtspOutput.Properties.Authentication.Password="Wachtwoord" Reset="Terugzetten" ================================================ FILE: data/locale/ru-RU.ini ================================================ RtspServer="RTSP Сервер" RstpServer.Description="OBS RTSP Сервер Плагин" RtspServer.Properties="RTSP Сервер" RtspServer.Properties.StartOutput="Начать" RtspServer.Properties.StopOutput="Остановить" RtspServer.Properties.Options="Опции" RtspServer.Properties.Options.AutoStart="Автоматический запуск" RtspServer.Properties.Options.EnabledAudioTracks="Аудио дорожки:" RtspServer.Properties.Target="Цель" RtspServer.Properties.Target.Multicast="Включен многоадресный режим" RtspServer.Properties.Target.Address="URL:" RtspServer.Properties.Target.Address.Copy="Копировать" RtspServer.Properties.Options.Output="Варианты вывода:" RtspServer.Properties.Options.Output.Tip="Откройте Файл->Настройки->Вывод->Потоковая передача, чтобы изменить параметры вывода." RtspServer.Properties.Authentication="Аутентификация" RtspServer.Properties.Authentication.Enabled="Включено" RtspServer.Properties.Authentication.Realm="Царство:" RtspServer.Properties.Authentication.Username="Имя пользователя:" RtspServer.Properties.Authentication.Password="Пароль:" RtspServer.Properties.Authentication.PasswordPlaceholder="(Опционально)" RtspServer.Properties.Status="Статус" RtspServer.Properties.Status.TotalDataSent="Общий объем данных:" RtspServer.Properties.Status.Bitrate="Битрейт:" RtspServer.Properties.Status.DroppedFrames="Сброшенные кадры:" RtspServer.Properties.Version="Версия:" RtspOutput="RTSP Выход" RtspOutput.Error.BeginDataCapture="не могу начать захват данных" RtspOutput.Error.InitEncoders="Ошибка инициализации кодировщиков" RtspOutput.Error.StartRtspServer="запуск сервера RTSP не удался на порту '%d'" RtspOutput.Error.StartMulticast="начало многоадресной передачи не удалось" RtspOutput.Error.Encode="ошибка кодирования" RtspOutput.Error.Unknown="неизвестная ошибка" RtspOutput.Hotkey.StartOutput="Начало вывода" RtspOutput.Hotkey.StopOutput="Остановить вывод" RtspOutput.Properties.Multicast="Включен многоадресный режим" RtspOutput.Properties.Port="Порт" RtspOutput.Properties.UrlSuffix="URL суффикс" RtspOutput.Properties.OutputAudio="Включить аудиовыход" RtspOutput.Properties.Authentication="Аутентификация" RtspOutput.Properties.Authentication.Realm="Царство" RtspOutput.Properties.Authentication.Username="Имя пользователя" RtspOutput.Properties.Authentication.Password="Пароль" Reset="Сбросить" ================================================ FILE: data/locale/zh-CN.ini ================================================ RtspServer="RTSP 服务器" RstpServer.Description="OBS RTSP 服务器插件" RtspServer.Properties="RTSP 服务器" RtspServer.Properties.StartOutput="启动" RtspServer.Properties.StopOutput="停止" RtspServer.Properties.Options="选项" RtspServer.Properties.Options.AutoStart="自动启动" RtspServer.Properties.Options.EnabledAudioTracks="音轨:" RtspServer.Properties.Target="目标" RtspServer.Properties.Target.Multicast="启用组播" RtspServer.Properties.Target.Address="URL:" RtspServer.Properties.Target.Address.Copy="复制" RtspServer.Properties.Options.Output="输出选项:" RtspServer.Properties.Options.Output.Tip="打开 文件->设置->输出->串流 以更改输出选项。" RtspServer.Properties.Authentication="身份认证" RtspServer.Properties.Authentication.Enabled="启用" RtspServer.Properties.Authentication.Realm="领域:" RtspServer.Properties.Authentication.Username="用户名:" RtspServer.Properties.Authentication.Password="密码:" RtspServer.Properties.Authentication.PasswordPlaceholder="(可选)" RtspServer.Properties.Status="状态" RtspServer.Properties.Status.TotalDataSent="总数据输出:" RtspServer.Properties.Status.Bitrate="比特率:" RtspServer.Properties.Status.DroppedFrames="丢弃的帧:" RtspServer.Properties.Version="版本:" RtspOutput="RTSP 输出" RtspOutput.Error.BeginDataCapture="无法开始数据捕获" RtspOutput.Error.InitEncoders="初始化编码器时发生错误" RtspOutput.Error.StartRtspServer="在端口“%d”上启动RTSP服务器失败" RtspOutput.Error.StartMulticast="组播启动失败" RtspOutput.Error.Encode="编码错误" RtspOutput.Error.Unknown="未知错误" RtspOutput.Hotkey.StartOutput="启动输出" RtspOutput.Hotkey.StopOutput="停止输出" RtspOutput.Properties.Multicast="启用组播" RtspOutput.Properties.Port="端口" RtspOutput.Properties.UrlSuffix="URL 后缀" RtspOutput.Properties.OutputAudio="启用音频输出" RtspOutput.Properties.Authentication="身份认证" RtspOutput.Properties.Authentication.Realm="领域" RtspOutput.Properties.Authentication.Username="用户名" RtspOutput.Properties.Authentication.Password="密码" Reset="重置" ================================================ FILE: data/locale/zh-TW.ini ================================================ RtspServer="RTSP 伺服器" RstpServer.Description="OBS RTSP 伺服器外掛程式" RtspServer.Properties="RTSP 伺服器" RtspServer.Properties.StartOutput="開始" RtspServer.Properties.StopOutput="停止" RtspServer.Properties.Options="選項" RtspServer.Properties.Options.AutoStart="自動開始" RtspServer.Properties.Options.EnabledAudioTracks="音軌:" RtspServer.Properties.Target="目標" RtspServer.Properties.Target.Multicast="啟用組播" RtspServer.Properties.Target.Address="URL:" RtspServer.Properties.Target.Address.Copy="拷貝" RtspServer.Properties.Options.Output="輸出選項:" RtspServer.Properties.Options.Output.Tip="打開 檔案->設定->輸出->串流 以更改輸出選項。" RtspServer.Properties.Authentication="身份驗證" RtspServer.Properties.Authentication.Enabled="啟用" RtspServer.Properties.Authentication.Realm="領域:" RtspServer.Properties.Authentication.Username="用戶名:" RtspServer.Properties.Authentication.Password="密碼:" RtspServer.Properties.Authentication.PasswordPlaceholder="(可選)" RtspServer.Properties.Status="狀態" RtspServer.Properties.Status.TotalDataSent="總數據輸出:" RtspServer.Properties.Status.Bitrate="位元速率:" RtspServer.Properties.Status.DroppedFrames="落幀數:" RtspServer.Properties.Version="版本:" RtspOutput="RTSP 輸出" RtspOutput.Error.BeginDataCapture="無法開始數據捕獲" RtspOutput.Error.InitEncoders="初始化編碼器錯誤" RtspOutput.Error.StartRtspServer="連接埠 %d 上啟動RTSP伺服器失敗" RtspOutput.Error.StartMulticast="組播啟動失敗" RtspOutput.Error.Encode="編碼錯誤" RtspOutput.Error.Unknown="未知錯誤" RtspOutput.Hotkey.StartOutput="開始輸出" RtspOutput.Hotkey.StopOutput="停止輸出" RtspOutput.Properties.Multicast="啟用組播" RtspOutput.Properties.Port="連接埠" RtspOutput.Properties.UrlSuffix="URL 後綴" RtspOutput.Properties.OutputAudio="啟用音訊輸出" RtspOutput.Properties.Authentication="身份驗證" RtspOutput.Properties.Authentication.Realm="領域" RtspOutput.Properties.Authentication.Username="用戶名" RtspOutput.Properties.Authentication.Password="密碼" Reset="重置" ================================================ FILE: external/BuildHelper.cmake ================================================ set(OBS_PLUGIN_OBS_SOURCE_DIR ${OBS_SOURCE_DIR}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/external") set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/release") set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};${QTDIR};${DepsPath}") set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${OBS_PLUGUN_GIT_TAG}-linux") set(CPACK_PACKAGING_INSTALL_PREFIX "/usr") #set(CPACK_SOURCE_PACKAGE_FILE_NAME "${OBS_PLUGIN_PACKAGE_FILE_NAME}") set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") set(MACOSX_PLUGIN_BUNDLE_VERSION "${OBS_PLUGUN_LONG_VERSION}") set(MACOSX_PLUGIN_SHORT_VERSION_STRING "${OBS_PLUGUN_VERSION}") find_package(libobs REQUIRED) #add_library(OBS::libobs STATIC IMPORTED GLOBAL) #set_target_properties(OBS::libobs PROPERTIES # IMPORTED_LOCATION "${LIBOBS_LIB}" # ) add_library(OBS::libobs STATIC IMPORTED GLOBAL) if (LIBOBS_LIB MATCHES "/([^/]+)\\.framework$") set(_libobs_fw "${LIBOBS_LIB}/${CMAKE_MATCH_1}") if(EXISTS "${_libobs_fw}.tbd") string(APPEND _libobs_fw ".tbd") endif() message("${_libobs_fw}") set_target_properties(OBS::libobs PROPERTIES IMPORTED_LOCATION "${_libobs_fw}" ) else() set_target_properties(OBS::libobs PROPERTIES IMPORTED_LOCATION "${LIBOBS_LIB}" ) endif() add_library(libobs ALIAS OBS::libobs) find_package(obs-frontend-api REQUIRED) add_library(OBS::obs-frontend-api STATIC IMPORTED GLOBAL) set_target_properties(OBS::obs-frontend-api PROPERTIES IMPORTED_LOCATION "${OBS_FRONTEND_API_LIB}" ) add_library(obs-frontend-api ALIAS OBS::obs-frontend-api) include("${CMAKE_CURRENT_SOURCE_DIR}/external/ObsPluginHelpers.cmake") if(OS_WINDOWS) if(MSVC) target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE /W3) endif() elseif(OS_MACOS) configure_file( ${CMAKE_SOURCE_DIR}/bundle/installer-macos.pkgproj.in ${CMAKE_SOURCE_DIR}/bundle/installer-macos.generated.pkgproj) target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall) else() target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall) endif() ================================================ FILE: external/Findlibobs.cmake ================================================ # This module can be copied and used by external plugins for OBS # # Once done these will be defined: # # LIBOBS_FOUND # LIBOBS_INCLUDE_DIRS # LIBOBS_LIBRARIES if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(OS_MACOS ON) set(OS_POSIX ON) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") set(OS_POSIX ON) string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} ON) elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(OS_WINDOWS ON) set(OS_POSIX OFF) endif() find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_OBS QUIET obs libobs) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_lib_suffix 64) else() set(_lib_suffix 32) endif() if(DEFINED CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_build_type_base "debug") else() set(_build_type_base "release") endif() endif() find_path(LIBOBS_INCLUDE_DIR NAMES obs.h HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR ${OBS_SOURCE_DIR} PATHS /usr/include /usr/local/include /opt/local/include /sw/include PATH_SUFFIXES libobs ) function(find_obs_lib base_name repo_build_path lib_name) string(TOUPPER "${base_name}" base_name_u) if(DEFINED _build_type_base) set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}") set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") endif() if(OS_MACOS) set(_find_library_names ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib) else() set(_find_library_names ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}) endif() find_library(${base_name_u}_LIB NAMES ${_find_library_names} HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR ${OBS_SOURCE_DIR} ${_${base_name_u}_LIBRARY_DIRS} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin # base repo non-msvc-specific search paths ${_build_type_${repo_build_path}} ${_build_type_${repo_build_path}${_lib_suffix}} plugin_build/${repo_build_path} plugin_build${_lib_suffix}/${repo_build_path} # base repo msvc-specific search paths on windows plugin_build${_lib_suffix}/${repo_build_path}/Debug plugin_build${_lib_suffix}/${repo_build_path}/RelWithDebInfo plugin_build/${repo_build_path}/Debug plugin_build/${repo_build_path}/RelWithDebInfo ) endfunction() find_obs_lib(LIBOBS libobs obs) if(MSVC) find_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR) mark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB) if(LIBOBS_FOUND) if(MSVC) if (NOT DEFINED W32_PTHREADS_LIB) message(FATAL_ERROR "Could not find the w32-pthreads library" ) endif() set(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads) endif() set(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR}) set(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB}) # include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake) # allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg) if(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LIBOBS_INCLUDE_DIR}/../cmake/Modules/") set(INCLUDED_LIBOBS_CMAKE_MODULES true) endif() else() message(FATAL_ERROR "Could not find the libobs library" ) endif() ================================================ FILE: external/Findobs-frontend-api.cmake ================================================ # This module can be copied and used by external plugins for OBS # # Once done these will be defined: # # OBS_FRONTEND_API_FOUND # OBS_FRONTEND_API_INCLUDE_DIRS # OBS_FRONTEND_API_LIBRARIES if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(OS_MACOS ON) set(OS_POSIX ON) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") set(OS_POSIX ON) string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} ON) elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(OS_WINDOWS ON) set(OS_POSIX OFF) endif() find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_OBS QUIET obs obs-frontend-api) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_lib_suffix 64) else() set(_lib_suffix 32) endif() if(DEFINED CMAKE_BUILD_TYPE) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(_build_type_base "debug") else() set(_build_type_base "release") endif() endif() find_path(OBS_FRONTEND_API_INCLUDE_DIR NAMES obs-frontend-api.h HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR ${OBS_SOURCE_DIR} PATHS /usr/include /usr/local/include /opt/local/include /sw/include PATH_SUFFIXES UI/obs-frontend-api ) function(find_obs_lib base_name repo_build_path lib_name) string(TOUPPER "${base_name}" base_name_u) if(DEFINED _build_type_base) set(_build_type_${repo_build_path} "${_build_type_base}/${repo_build_path}") set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") endif() if(OS_MACOS) set(_find_library_names ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib) else() set(_find_library_names ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}) endif() find_library(${base_name_u}_LIB NAMES ${_find_library_names} HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR ${OBS_SOURCE_DIR} ${_${base_name_u}_LIBRARY_DIRS} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib PATH_SUFFIXES lib${_lib_suffix} lib libs${_lib_suffix} libs bin${_lib_suffix} bin ../lib${_lib_suffix} ../lib ../libs${_lib_suffix} ../libs ../bin${_lib_suffix} ../bin # base repo non-msvc-specific search paths ${_build_type_${repo_build_path}} ${_build_type_${repo_build_path}${_lib_suffix}} plugin_build/${repo_build_path} plugin_build${_lib_suffix}/${repo_build_path} # base repo msvc-specific search paths on windows plugin_build${_lib_suffix}/${repo_build_path}/Debug plugin_build${_lib_suffix}/${repo_build_path}/RelWithDebInfo plugin_build/${repo_build_path}/Debug plugin_build/${repo_build_path}/RelWithDebInfo ) endfunction() find_obs_lib(OBS_FRONTEND_API UI/obs-frontend-api obs-frontend-api) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(obs-frontend-api DEFAULT_MSG OBS_FRONTEND_API_LIB OBS_FRONTEND_API_INCLUDE_DIR) mark_as_advanced(OBS_FRONTEND_API_INCLUDE_DIR OBS_FRONTEND_API_LIB) if(OBS-FRONTEND-API_FOUND) set(OBS_FRONTEND_API_INCLUDE_DIRS ${OBS_FRONTEND_API_INCLUDE_DIR}) set(OBS_FRONTEND_API_LIBRARIES ${OBS_FRONTEND_API_LIB}) # include(${OBS_FRONTEND_API_INCLUDE_DIR}/../../cmake/external/ObsPluginHelpers.cmake) # allows external plugins to easily use/share common dependencies that are often included with obs-frontend-api (such as FFmpeg) if(NOT DEFINED INCLUDED_OBS_FRONTEND_API_CMAKE_MODULES) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${OBS_FRONTEND_API_INCLUDE_DIR}/../../cmake/Modules/") set(INCLUDED_OBS_FRONTEND_API_CMAKE_MODULES true) endif() else() message(FATAL_ERROR "Could not find the obs-frontend-api library" ) endif() ================================================ FILE: external/GitInfoHelper.cmake ================================================ function(get_git_version git_tag_name git_tag_version_name git_tag_short_version_name git_tag_long_version_name) execute_process(COMMAND git describe --tags --always --dirty=-dev WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" TIMEOUT 10 OUTPUT_VARIABLE git_tag OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND git rev-list HEAD --count WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" TIMEOUT 10 OUTPUT_VARIABLE git_rev_list_count OUTPUT_STRIP_TRAILING_WHITESPACE) string(REGEX MATCH "[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$" git_tag_version "${git_tag}") string(REGEX MATCH "^[0-9]+.[0-9]+.[0-9]+" git_tag_short_version "${git_tag_version}") #if("${git_tag_version}" MATCHES "-[0-9]+-g") # string(REGEX MATCH "-[0-9]+-g" _git_tag_tweak_version_temp "${git_tag_version}") # string(REGEX MATCH "[0-9]+" _git_tag_tweak_version "${_git_tag_tweak_version_temp}") #else() # set(_git_tag_tweak_version "0") #endif() if("${git_rev_list_count}" MATCHES "^[0-9]+$") set(_git_tag_tweak_version "${git_rev_list_count}") else() set(_git_tag_tweak_version "0") endif() if("${git_tag_short_version}" STREQUAL "") set(git_tag_long_version "0.0.1.${_git_tag_tweak_version}") else() set(git_tag_long_version "${git_tag_short_version}.${_git_tag_tweak_version}") endif() set(${git_tag_name} "${git_tag}" PARENT_SCOPE) set(${git_tag_version_name} "${git_tag_version}" PARENT_SCOPE) set(${git_tag_short_version_name} "${git_tag_short_version}" PARENT_SCOPE) set(${git_tag_long_version_name} "${git_tag_long_version}" PARENT_SCOPE) message("Git Tag:\t${git_tag}") message("Git Tag Version:\t${git_tag_version}") message("Git Tag Short Version:\t${git_tag_short_version}") message("Git Tag Long Version:\t${git_tag_long_version}") endfunction(get_git_version) ================================================ FILE: external/ObsPluginHelpers.cmake ================================================ if(POLICY CMP0087) cmake_policy(SET CMP0087 NEW) endif() set(OBS_STANDALONE_PLUGIN_DIR ${CMAKE_SOURCE_DIR}/release) include(GNUInstallDirs) if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") set(OS_MACOS ON) set(OS_POSIX ON) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") set(OS_POSIX ON) string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} ON) elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(OS_WINDOWS ON) set(OS_POSIX OFF) endif() # Old-Style plugin detected, find "modern" libobs variant instead and set global include directories # to fix "bad" plugin behavior if(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs) message( DEPRECATION "You are using an outdated method of adding 'libobs' to your project. Refer to the updated wiki on how to build and export 'libobs' and use it in your plugin projects." ) find_package(libobs REQUIRED) if(TARGET OBS::libobs) set_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE) message(STATUS "OBS: Using modern libobs target") add_library(libobs ALIAS OBS::libobs) if(OS_WINDOWS) add_library(w32-pthreads ALIAS OBS::w32-pthreads) endif() endif() endif() # Set macOS and Windows specific if default value is used if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS)) set(CMAKE_INSTALL_PREFIX ${OBS_STANDALONE_PLUGIN_DIR} CACHE STRING "Directory to install OBS plugin after building" FORCE) endif() # Set default build type to RelWithDebInfo and specify allowed alternative values if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel) endif() # Set default Qt version to AUTO, preferring an available Qt6 with a fallback to Qt5 if(NOT QT_VERSION) set(QT_VERSION AUTO CACHE STRING "OBS Qt version [AUTO, 6, 5]" FORCE) set_property(CACHE QT_VERSION PROPERTY STRINGS AUTO 6 5) endif() # Macro to find best possible Qt version for use with the project: # # * Use QT_VERSION value as a hint for desired Qt version # * If "AUTO" was specified, prefer Qt6 over Qt5 # * Creates versionless targets of desired component if none had been created by Qt itself (Qt # versions < 5.15) # macro(find_qt) set(multiValueArgs COMPONENTS COMPONENTS_WIN COMPONENTS_MAC COMPONENTS_LINUX) cmake_parse_arguments(FIND_QT "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Do not use versionless targets in the first step to avoid Qt::Core being clobbered by later # opportunistic find_package runs set(QT_NO_CREATE_VERSIONLESS_TARGETS ON) # Loop until _QT_VERSION is set or FATAL_ERROR aborts script execution early while(NOT _QT_VERSION) if(QT_VERSION STREQUAL AUTO AND NOT _QT_TEST_VERSION) set(_QT_TEST_VERSION 6) elseif(NOT QT_VERSION STREQUAL AUTO) set(_QT_TEST_VERSION ${QT_VERSION}) endif() find_package( Qt${_QT_TEST_VERSION} COMPONENTS Core QUIET) if(TARGET Qt${_QT_TEST_VERSION}::Core) set(_QT_VERSION ${_QT_TEST_VERSION} CACHE INTERNAL "") message(STATUS "Qt version found: ${_QT_VERSION}") unset(_QT_TEST_VERSION) break() elseif(QT_VERSION STREQUAL AUTO) if(_QT_TEST_VERSION EQUAL 6) message(WARNING "Qt6 was not found, falling back to Qt5") set(_QT_TEST_VERSION 5) continue() endif() endif() message(FATAL_ERROR "Neither Qt6 nor Qt5 found.") endwhile() # Enable versionless targets for the remaining Qt components set(QT_NO_CREATE_VERSIONLESS_TARGETS OFF) set(_QT_COMPONENTS ${FIND_QT_COMPONENTS}) if(OS_WINDOWS) list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_WIN}) elseif(OS_MACOS) list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_MAC}) else() list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_LINUX}) endif() find_package( Qt${_QT_VERSION} COMPONENTS ${_QT_COMPONENTS} REQUIRED) list(APPEND _QT_COMPONENTS Core) if("Gui" IN_LIST FIND_QT_COMPONENTS_LINUX) list(APPEND _QT_COMPONENTS "GuiPrivate") endif() # Check for versionless targets of each requested component and create if necessary foreach(_COMPONENT IN LISTS _QT_COMPONENTS) if(NOT TARGET Qt::${_COMPONENT} AND TARGET Qt${_QT_VERSION}::${_COMPONENT}) add_library(Qt::${_COMPONENT} INTERFACE IMPORTED) set_target_properties(Qt::${_COMPONENT} PROPERTIES INTERFACE_LINK_LIBRARIES Qt${_QT_VERSION}::${_COMPONENT}) endif() endforeach() endmacro() # Set relative path variables for file configurations file(RELATIVE_PATH RELATIVE_INSTALL_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX}) file(RELATIVE_PATH RELATIVE_BUILD_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) if(OS_POSIX) # Set default GCC/clang compile options: # # * Treat warnings as errors # * Enable extra warnings, https://clang.llvm.org/docs/DiagnosticsReference.html#wextra # * Warning about usage of variable length array, # https://clang.llvm.org/docs/DiagnosticsReference.html#wvla # * Warning about bad format specifiers, # https://clang.llvm.org/docs/DiagnosticsReference.html#wformat # * Warning about non-strings used as format strings, # https://clang.llvm.org/docs/DiagnosticsReference.html#wformat-security # * Warning about non-exhaustive switch blocks, # https://clang.llvm.org/docs/DiagnosticsReference.html#wswitch # * Warning about unused parameters, # https://clang.llvm.org/docs/DiagnosticsReference.html#wunused-parameter # * DISABLE warning about unused functions, # https://clang.llvm.org/docs/DiagnosticsReference.html#wunused-function # * DISABLE warning about missing field initializers, # https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-field-initializers # * DISABLE strict aliasing optimisations # * C ONLY - treat implicit function declarations (use before declare) as errors, # https://clang.llvm.org/docs/DiagnosticsReference.html#wimplicit-function-declaration # * C ONLY - DISABLE warning about missing braces around subobject initalizers, # https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-braces # * C ONLY, Clang ONLY - Warning about implicit conversion of NULL to another type, # https://clang.llvm.org/docs/DiagnosticsReference.html#wnull-conversion # * C & C++, Clang ONLY - Disable warning about integer conversion losing precision, # https://clang.llvm.org/docs/DiagnosticsReference.html#wshorten-64-to-32 # * C++, GCC ONLY - Warning about implicit conversion of NULL to another type # * Enable color diagnostics on Clang (CMAKE_COLOR_DIAGNOSTICS available in CMake 3.24) target_compile_options( ${CMAKE_PROJECT_NAME} PRIVATE -Werror -Wextra -Wvla -Wformat -Wformat-security -Wswitch -Wunused-parameter -Wno-unused-function -Wno-missing-field-initializers -fno-strict-aliasing "$<$:-Werror-implicit-function-declaration;-Wno-missing-braces>" "$<$:-Wnull-conversion;-Wno-error=shorten-64-to-32;-fcolor-diagnostics>" "$<$:-Wnull-conversion;-Wno-error=shorten-64-to-32;-fcolor-diagnostics>" "$<$:-Wconversion-null>" "$<$:-DDEBUG=1;-D_DEBUG=1>") # GCC 12.1.0 has a regression bug which trigger maybe-uninitialized warnings where there is not. # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "12.1.0") target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wno-error=maybe-uninitialized) endif() if(NOT CCACHE_SET) # Try to find and enable ccache find_program(CCACHE_PROGRAM "ccache") set(CCACHE_SUPPORT ON CACHE BOOL "Enable ccache support") mark_as_advanced(CCACHE_PROGRAM) if(CCACHE_PROGRAM AND CCACHE_SUPPORT) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE INTERNAL "") set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE INTERNAL "") set(CMAKE_OBJC_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE INTERNAL "") set(CMAKE_OBJCXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE INTERNAL "") set(CMAKE_CUDA_COMPILER_LAUNCHER ${CCACHE_PROGRAM} CACHE INTERNAL "") # CMake 3.9+ set(CCACHE_SET ON CACHE INTERNAL "") endif() endif() endif() # Set required C++ standard to C++17 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Get lowercase host architecture for easier comparison if(MSVC_CXX_ARCHITECTURE_ID) string(TOLOWER ${MSVC_CXX_ARCHITECTURE_ID} _HOST_ARCH) else() string(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} _HOST_ARCH) endif() if(_HOST_ARCH MATCHES "i[3-6]86|x86|x64|x86_64|amd64" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") # Enable MMX, SSE and SSE2 on compatible host systems (assuming no cross-compile) set(ARCH_SIMD_FLAGS -mmmx -msse -msse2) elseif(_HOST_ARCH MATCHES "arm64|arm64e|aarch64") # Enable available built-in SIMD support in Clang and GCC if(CMAKE_C_COMPILER_ID MATCHES "^(Apple)?Clang|GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang|GNU") include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) check_c_compiler_flag("-fopenmp-simd" C_COMPILER_SUPPORTS_OPENMP_SIMD) check_cxx_compiler_flag("-fopenmp-simd" CXX_COMPILER_SUPPORTS_OPENMP_SIMD) target_compile_options( ${CMAKE_PROJECT_NAME} PRIVATE -DSIMDE_ENABLE_OPENMP "$<$,$>:-fopenmp-simd>" "$<$,$>:-fopenmp-simd>") endif() endif() # macOS specific settings if(OS_MACOS) # Set macOS-specific C++ standard library target_compile_options( ${CMAKE_PROJECT_NAME} PRIVATE "$<$:-fcolor-diagnostics>" -stdlib=libc++) # Set build architecture to host architecture by default if(NOT CMAKE_OSX_ARCHITECTURES) set(CMAKE_OSX_ARCHITECTURES ${CMAKE_HOST_SYSTEM_PROCESSOR} CACHE STRING "Build architecture for macOS" FORCE) endif() set_property(CACHE CMAKE_OSX_ARCHITECTURES PROPERTY STRINGS arm64 x86_64 "arm64;x86_64") # Set deployment target to 11.0 for Apple Silicon or 10.15 for Intel and Universal builds if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=arm64] "11.0") set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=x86_64] "10.15") if("${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") set(_MACOS_DEPLOYMENT_TARGET "11.0") else() set(_MACOS_DEPLOYMENT_TARGET "10.15") endif() set(CMAKE_OSX_DEPLOYMENT_TARGET ${_MACOS_DEPLOYMENT_TARGET} CACHE STRING "Minimum macOS version to target for deployment (at runtime); newer APIs weak linked" FORCE) unset(_MACOS_DEPLOYMENT_TARGET) endif() set_property(CACHE CMAKE_OSX_DEPLOYMENT_TARGET PROPERTY STRINGS 13.0 12.0 11.0 10.15) # Override macOS install directory if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install CACHE STRING "Directory to install OBS to after building" FORCE) endif() # Set up codesigning for Xcode builds with team IDs or standalone builds with developer identity if(NOT OBS_BUNDLE_CODESIGN_TEAM) if(NOT OBS_BUNDLE_CODESIGN_IDENTITY) set(OBS_BUNDLE_CODESIGN_IDENTITY "-" CACHE STRING "OBS code signing identity for macOS" FORCE) endif() set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${OBS_BUNDLE_CODESIGN_IDENTITY}) else() # Team ID specified, warn if Xcode generator is not used and fall back to ad-hoc signing if(NOT XCODE) message( WARNING "Code signing with a team identifier is only supported with the Xcode generator. Using ad-hoc code signature instead." ) if(NOT OBS_BUNDLE_CODESIGN_IDENTITY) set(OBS_BUNDLE_CODESIGN_IDENTITY "-" CACHE STRING "OBS code signing identity for macOS" FORCE) endif() else() unset(OBS_BUNDLE_CODESIGN_IDENTITY) set_property(CACHE OBS_BUNDLE_CODESIGN_TEAM PROPERTY HELPSTRING "OBS code signing team for macOS") set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic) set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${OBS_BUNDLE_CODESIGN_TEAM}) endif() endif() # Set path to entitlements property list for codesigning. Entitlements should match the host # binary, in this case OBS.app. set(OBS_CODESIGN_ENTITLEMENTS ${CMAKE_SOURCE_DIR}/bundle/macos/entitlements.plist CACHE INTERNAL "Path to codesign entitlements plist") # Enable linker codesigning by default. Building OBS or plugins on host systems older than macOS # 10.15 is not supported set(OBS_CODESIGN_LINKER ON CACHE BOOL "Enable linker codesigning on macOS (macOS 11+ required)") # Tell Xcode to pretend the linker signed binaries so that editing with install_name_tool # preserves ad-hoc signatures. This option is supported by codesign on macOS 11 or higher. See # CMake Issue 21854: https://gitlab.kitware.com/cmake/cmake/-/issues/21854 if(OBS_CODESIGN_LINKER) set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "-o linker-signed") endif() # Set default options for bundling on macOS set(CMAKE_MACOSX_RPATH ON) set(CMAKE_SKIP_BUILD_RPATH OFF) set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF) set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks/") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) set(OBS_PLUGIN_DESTINATION "bin") set(OBS_DATA_DESTINATION "data") # Helper function for plugin targets (macOS version) function(setup_plugin_target target) # Sanity check for required bundle information # # * Bundle identifier # * Bundle version # * Short version string #if(NOT DEFINED MACOSX_PLUGIN_GUI_IDENTIFIER) # message( # FATAL_ERROR # "No 'MACOSX_PLUGIN_GUI_IDENTIFIER' set, but is required to build plugin bundles on macOS - example: 'com.yourname.pluginname'" # ) #endif() #if(NOT DEFINED MACOSX_PLUGIN_BUNDLE_VERSION) # message( # FATAL_ERROR # "No 'MACOSX_PLUGIN_BUNDLE_VERSION' set, but is required to build plugin bundles on macOS - example: '25'" # ) #endif() #if(NOT DEFINED MACOSX_PLUGIN_SHORT_VERSION_STRING) # message( # FATAL_ERROR # "No 'MACOSX_PLUGIN_SHORT_VERSION_STRING' set, but is required to build plugin bundles on macOS - example: '1.0.2'" # ) #endif() # Set variables for automatic property list generation #set(MACOSX_PLUGIN_BUNDLE_NAME # "${target}" # PARENT_SCOPE) #set(MACOSX_PLUGIN_BUNDLE_VERSION # "${MACOSX_PLUGIN_BUNDLE_VERSION}" # PARENT_SCOPE) #set(MACOSX_PLUGIN_SHORT_VERSION_STRING # "${MACOSX_PLUGIN_SHORT_VERSION_STRING}" # PARENT_SCOPE) #set(MACOSX_PLUGIN_EXECUTABLE_NAME # "${target}" # PARENT_SCOPE) #set(MACOSX_PLUGIN_BUNDLE_TYPE # "BNDL" # PARENT_SCOPE) # Set installation target to install prefix root (default for bundles) #install( # TARGETS ${target} # LIBRARY DESTINATION "." # COMPONENT obs_plugins # NAMELINK_COMPONENT ${target}_Development) # Set prefix to empty string to avoid automatic naming of generated library, i.e. # "lib" set_target_properties(${target} PROPERTIES PREFIX "") install( TARGETS ${target} RUNTIME DESTINATION "${target}/${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime LIBRARY DESTINATION "${target}/${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime NAMELINK_COMPONENT ${target}_Development) if(TARGET Qt::Core) # Framework version has changed between Qt5 (uses wrong numerical version) and Qt6 (uses # correct alphabetical version) if(${_QT_VERSION} EQUAL 5) set(_QT_FW_VERSION "${QT_VERSION}") else() set(_QT_FW_VERSION "A") endif() # Set up install-time command to fix Qt library references to point into OBS.app bundle set(_COMMAND "${CMAKE_INSTALL_NAME_TOOL} \\ -change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/${QT_VERSION}/QtWidgets @rpath/QtWidgets.framework/Versions/${_QT_FW_VERSION}/QtWidgets \\ -change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/${QT_VERSION}/QtCore @rpath/QtCore.framework/Versions/${_QT_FW_VERSION}/QtCore \\ -change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/${QT_VERSION}/QtGui @rpath/QtGui.framework/Versions/${_QT_FW_VERSION}/QtGui \\ \\\"\${CMAKE_INSTALL_PREFIX}/${target}.plugin/Contents/MacOS/${target}\\\"") install(CODE "execute_process(COMMAND /bin/sh -c \"${_COMMAND}\")" COMPONENT obs_plugins) unset(_QT_FW_VERSION) endif() # Set macOS bundle properties #set_target_properties( # ${target} # PROPERTIES PREFIX "" # BUNDLE ON # BUNDLE_EXTENSION "plugin" # OUTPUT_NAME ${target} # MACOSX_BUNDLE_INFO_PLIST # #"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/Plugin-Info.plist.in" # "${CMAKE_SOURCE_DIR}/bundle/macOS/Plugin-Info.plist.in" # XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${MACOSX_PLUGIN_GUI_IDENTIFIER}" # XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS # #"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/entitlements.plist") # "${CMAKE_SOURCE_DIR}/bundle/macOS/entitlements.plist") # If not building with Xcode, manually code-sign the plugin if(NOT XCODE) # set(_COMMAND # "/usr/bin/codesign --force \\ # --sign \\\"${OBS_BUNDLE_CODESIGN_IDENTITY}\\\" \\ # --options runtime \\ # --entitlements \\\"${CMAKE_SOURCE_DIR}/bundle/macOS/entitlements.plist\\\" \\ # \\\"\${CMAKE_INSTALL_PREFIX}/${target}.plugin\\\"") install(CODE "execute_process(COMMAND /bin/sh -c \"${_COMMAND}\")" COMPONENT obs_plugins) endif() install_bundle_resources(${target}) endfunction() # Helper function to add resources from "data" directory as bundle resources function(install_bundle_resources target) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) # file(GLOB_RECURSE _DATA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/data/*") # foreach(_DATA_FILE IN LISTS _DATA_FILES) # file(RELATIVE_PATH _RELATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${_DATA_FILE}) # get_filename_component(_RELATIVE_PATH ${_RELATIVE_PATH} PATH) # target_sources(${target} PRIVATE ${_DATA_FILE}) # set_source_files_properties(${_DATA_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION # Resources/${_RELATIVE_PATH}) # string(REPLACE "\\" "\\\\" _GROUP_NAME ${_RELATIVE_PATH}) # source_group("Resources\\${_GROUP_NAME}" FILES ${_DATA_FILE}) # endforeach() install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DESTINATION "${target}/${OBS_DATA_DESTINATION}" USE_SOURCE_PERMISSIONS) endif() endfunction() else() # Check for target architecture (64bit vs 32bit) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_ARCH_SUFFIX 64) else() set(_ARCH_SUFFIX 32) endif() set(OBS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/rundir) # Unix specific settings if(OS_POSIX) # Paths to binaries and plugins differ between portable and non-portable builds on Linux option(LINUX_PORTABLE "Build portable version (Linux)" ON) if(NOT LINUX_PORTABLE) set(OBS_LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}/${CMAKE_LIBRARY_ARCHITECTURE}") set(OBS_PLUGIN_DESTINATION "${OBS_LIBRARY_DESTINATION}/obs-plugins") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") set(OBS_DATA_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/obs") else() set(OBS_LIBRARY_DESTINATION bin/${_ARCH_SUFFIX}bit) set(OBS_PLUGIN_DESTINATION obs-plugins/${_ARCH_SUFFIX}bit) set(CMAKE_INSTALL_RPATH "$ORIGIN/" "${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}") set(OBS_DATA_DESTINATION "data") endif() # Setup Linux-specific CPack values for "deb" package generation if(OS_LINUX) set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${LINUX_MAINTAINER_EMAIL}") set(CPACK_PACKAGE_VERSION "${OBS_PLUGUIN_VERSION}") set(CPACK_PACKAGE_CONTACT "${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>") set(CPACK_PACKAGE_VENDOR "${LINUX_MAINTAINER}") set(CPACK_GENERATOR "DEB" "TGZ" "RPM") set(CPACK_DEBIAN_PACKAGE_DEPENDS "obs-studio (>= 28.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)") set(CPACK_DEBIAN_PACKAGE_SECTION "video") set(CPACK_RPM_PACKAGE_REQUIRES "obs-studio >= 28.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0") set(CPACK_RPM_PACKAGE_GROUP "Video") set(CPACK_RPM_PACKAGE_LICENSE "GPL-2.0") #set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-linux-x86_64") #set(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_SOURCE_DIR}/release) if(NOT LINUX_PORTABLE) set(CPACK_SET_DESTDIR ON) endif() include(CPack) endif() # Windows specific settings else() set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") set(OBS_LIBRARY32_DESTINATION "bin/32bit") set(OBS_LIBRARY64_DESTINATION "bin/64bit") set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") set(OBS_PLUGIN32_DESTINATION "obs-plugins/32bit") set(OBS_PLUGIN64_DESTINATION "obs-plugins/64bit") set(OBS_DATA_DESTINATION "data") if(MSVC) # Set default Visual Studio CL.exe compile options. # # * Enable building with multiple processes, # https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes?view=msvc-170 # * Enable lint-like warnings, # https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=msvc-170 # * Enable treating all warnings as errors, # https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=msvc-170 # * RelWithDebInfo ONLY - Enable expanding of all functions not explicitly marked for no # inlining, # https://docs.microsoft.com/en-us/cpp/build/reference/ob-inline-function-expansion?view=msvc-170 # * Enable UNICODE support, # https://docs.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings#unicode-and-ansi-functions # * DISABLE warnings about using POSIX function names, # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?view=msvc-170#posix-function-names # * DISABLE warnings about unsafe CRT library functions, # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?view=msvc-170#unsafe-crt-library-functions # * DISABLE warnings about nonstandard nameless structs/unions, # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4201?view=msvc-170 target_compile_options( ${CMAKE_PROJECT_NAME} PRIVATE /MP /W3 /WX /wd4201 "$<$:/Ob2>" "$<$:/DDEBUG=1;/D_DEBUG=1>" /DUNICODE /D_UNICODE /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS) # Set default Visual Studio linker options. # # * Enable removal of functions and data that are never used, # https://docs.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170 # * Enable treating all warnings as errors, # https://docs.microsoft.com/en-us/cpp/build/reference/wx-treat-linker-warnings-as-errors?view=msvc-170 # * x64 ONLY - DISABLE creation of table of safe exception handlers, # https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers?view=msvc-170 # * Debug ONLY - DISABLE incremental linking, # https://docs.microsoft.com/en-us/cpp/build/reference/incremental-link-incrementally?view=msvc-170 # * RelWithDebInfo ONLY - Disable incremental linking, but enable COMDAT folding, # https://docs.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170 target_link_options( ${CMAKE_PROJECT_NAME} PRIVATE "LINKER:/OPT:REF" "LINKER:/WX" "$<$>:LINKER\:/SAFESEH\:NO>" "$<$:LINKER\:/INCREMENTAL\:NO>" "$<$:LINKER\:/INCREMENTAL\:NO;/OPT\:ICF>") endif() endif() # Helper function for plugin targets (Windows and Linux version) function(setup_plugin_target target) # Set prefix to empty string to avoid automatic naming of generated library, i.e. # "lib" set_target_properties(${target} PROPERTIES PREFIX "") # Set install directories install( TARGETS ${target} RUNTIME DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime LIBRARY DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime NAMELINK_COMPONENT ${target}_Development) # Set rundir install directory install( FILES $ DESTINATION $/${OBS_PLUGIN_DESTINATION} COMPONENT obs_rundir EXCLUDE_FROM_ALL) if(OS_WINDOWS) # Set install directory for optional PDB symbol files install( FILES $ CONFIGURATIONS "RelWithDebInfo" "Debug" DESTINATION ${OBS_PLUGIN_DESTINATION} COMPONENT ${target}_Runtime OPTIONAL) # Set rundir install directory for optional PDB symbol files install( FILES $ CONFIGURATIONS "RelWithDebInfo" "Debug" DESTINATION $/${OBS_PLUGIN_DESTINATION} COMPONENT obs_rundir OPTIONAL EXCLUDE_FROM_ALL) endif() # Add resources from data directory setup_target_resources(${target} obs-plugins/${target}) # Set up plugin for testing in available OBS build on Windows if(OS_WINDOWS AND DEFINED OBS_BUILD_DIR) setup_target_for_testing(${target} obs-plugins/${target}) endif() # Custom command to install generated plugin into rundir add_custom_command( TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR} -DCMAKE_INSTALL_COMPONENT=obs_rundir -DCMAKE_INSTALL_CONFIG_NAME=$ -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake COMMENT "Installing to plugin rundir" VERBATIM) endfunction() # Helper function to add resources from "data" directory function(setup_target_resources target destination) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ DESTINATION ${OBS_DATA_DESTINATION}/${destination} USE_SOURCE_PERMISSIONS COMPONENT obs_plugins) install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data DESTINATION $/${OBS_DATA_DESTINATION}/${destination} USE_SOURCE_PERMISSIONS COMPONENT obs_rundir EXCLUDE_FROM_ALL) endif() endfunction() if(OS_WINDOWS) # Additional Windows-only helper function to copy plugin to existing OBS development directory: # # Copies plugin with associated PDB symbol files as well as contents of data directory into the # OBS rundir as specified by "OBS_BUILD_DIR". function(setup_target_for_testing target destination) install( FILES $ DESTINATION $/${OBS_PLUGIN_DESTINATION} COMPONENT obs_testing EXCLUDE_FROM_ALL) install( FILES $ CONFIGURATIONS "RelWithDebInfo" "Debug" DESTINATION $/${OBS_PLUGIN_DESTINATION} COMPONENT obs_testing OPTIONAL EXCLUDE_FROM_ALL) install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ DESTINATION $/${OBS_DATA_DESTINATION}/${destination} USE_SOURCE_PERMISSIONS COMPONENT obs_testing EXCLUDE_FROM_ALL) add_custom_command( TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_BUILD_DIR}/rundir -DCMAKE_INSTALL_COMPONENT=obs_testing -DCMAKE_INSTALL_CONFIG_NAME=$ -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake COMMENT "Installing to OBS test directory" VERBATIM) endfunction() endif() endif() ================================================ FILE: external/ObsPluginHelpers.cmake.bak ================================================ if(POLICY CMP0087) cmake_policy(SET CMP0087 NEW) endif() set(OBS_STANDALONE_PLUGIN_DIR "${CMAKE_SOURCE_DIR}/release") set(INCLUDED_LIBOBS_CMAKE_MODULES ON) include(GNUInstallDirs) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") set(OS_MACOS ON) set(OS_POSIX ON) elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux|FreeBSD|OpenBSD") set(OS_POSIX ON) string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} ON) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") set(OS_WINDOWS ON) set(OS_POSIX OFF) endif() # Old-Style plugin detected, find "modern" libobs variant instead and set # global include directories to fix "bad" plugin behaviour if(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs) message(DEPRECATION "You are using an outdated method of adding 'libobs' to your project. Refer to the updated wiki on how to build and export 'libobs' and use it in your plugin projects.") find_package(libobs REQUIRED) if(TARGET OBS::libobs) set_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE) message(STATUS "OBS: Using modern libobs target") add_library(libobs ALIAS OBS::libobs) if(OS_WINDOWS) add_library(w32-pthreads ALIAS OBS::w32-pthreads) endif() endif() endif() if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS)) set(CMAKE_INSTALL_PREFIX "${OBS_STANDALONE_PLUGIN_DIR}" CACHE STRING "Directory to install OBS plugin after building" FORCE) endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel) endif() file(RELATIVE_PATH RELATIVE_INSTALL_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_INSTALL_PREFIX}") file(RELATIVE_PATH RELATIVE_BUILD_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") # Set-up OS-specific environment and helper functions if(OS_MACOS) if(NOT CMAKE_OSX_ARCHITECTURES) set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "OBS plugin build architecture for macOS - x86_64 required at least" FORCE) endif() if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "OBS plugin deployment target for macOS - 10.13+ required" FORCE) endif() if(NOT DEFINED OBS_CODESIGN_LINKER) set(OBS_CODESIGN_LINKER ON CACHE BOOL "Enable linker code-signing on macOS (v11+ required" FORCE) endif() if(NOT DEFINED OBS_BUNDLE_CODESIGN_IDENTITY) set(OBS_BUNDLE_CODESIGN_IDENTITY "-" CACHE STRING "Codesign identity for macOS" FORCE) endif() # Xcode configuration if(XCODE) # Tell Xcode to pretend the linker signed binaries so that # editing with install_name_tool preserves ad-hoc signatures. # See CMake Issue 21854. # This option is supported by codesign on macOS 11 or higher. set(CMAKE_XCODE_GENERATE_SCHEME ON) if(OBS_CODESIGN_LINKER) set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "-o linker-signed") endif() endif() # Set default options for bundling on macOS set(CMAKE_MACOSX_RPATH ON) set(CMAKE_SKIP_BUILD_RPATH OFF) set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF) set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks/") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) function(setup_plugin_target target) if(NOT DEFINED MACOSX_PLUGIN_GUI_IDENTIFIER) message(FATAL_ERROR "No 'MACOSX_PLUGIN_GUI_IDENTIFIER' set, but is required to build plugin bundles on macOS - example: 'com.yourname.pluginname'") endif() if(NOT DEFINED MACOSX_PLUGIN_BUNDLE_VERSION) message(FATAL_ERROR "No 'MACOSX_PLUGIN_BUNDLE_VERSION' set, but is required to build plugin bundles on macOS - example: '25'") endif() if(NOT DEFINED MACOSX_PLUGIN_SHORT_VERSION_STRING) message(FATAL_ERROR "No 'MACOSX_PLUGIN_SHORT_VERSION_STRING' set, but is required to build plugin bundles on macOS - example: '1.0.2'") endif() set(MACOSX_PLUGIN_BUNDLE_NAME "${target}" PARENT_SCOPE) set(MACOSX_PLUGIN_BUNDLE_VERSION "${MACOSX_BUNDLE_BUNDLE_VERSION}" PARENT_SCOPE) set(MACOSX_PLUGIN_SHORT_VERSION_STRING "${MACOSX_BUNDLE_SHORT_VERSION_STRING}" PARENT_SCOPE) set(MACOSX_PLUGIN_EXECUTABLE_NAME "${target}" PARENT_SCOPE) set(MACOSX_PLUGIN_BUNDLE_TYPE "BNDL" PARENT_SCOPE) install(TARGETS ${target} LIBRARY DESTINATION "." COMPONENT obs_plugins NAMELINK_COMPONENT ${target}_Development) install(CODE "execute_process(COMMAND /bin/sh -c \"install_name_tool \\ -change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets.framework/Versions/5/QtWidgets \\ -change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore.framework/Versions/5/QtCore \\ -change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui.framework/Versions/5/QtGui \\ ${CMAKE_INSTALL_PREFIX}/${target}.plugin/Contents/MacOS/${target}\")") if(NOT XCODE) install(CODE "execute_process(COMMAND /bin/sh -c \"/usr/bin/codesign --force --sign \\\"${OBS_BUNDLE_CODESIGN_IDENTITY}\\\" --options runtime --entitlements ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/entitlements.plist ${CMAKE_INSTALL_PREFIX}/${target}.plugin\")") endif() set_target_properties(${target} PROPERTIES BUNDLE ON BUNDLE_EXTENSION "plugin" OUTPUT_NAME ${target} MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/Plugin-Info.plist.in" XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${MACOSX_PLUGIN_GUI_IDENTIFIER}" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${OBS_BUNDLE_CODESIGN_IDENTITY}" XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/entitlements.plist") add_custom_command(TARGET ${target} POST_BUILD COMMAND /bin/sh -c "codesign --force --sign \"-\" $<$:--options linker-signed >\"$\"" COMMENT "Codesigning ${target}" VERBATIM) install_bundle_resources(${target}) endfunction() function(install_bundle_resources target) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") file(GLOB_RECURSE _DATA_FILES "${CMAKE_CURRENT_SOURCE_DIR}/data/*") foreach(_DATA_FILE IN LISTS _DATA_FILES) file(RELATIVE_PATH _RELATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${_DATA_FILE}) get_filename_component(_RELATIVE_PATH "${_RELATIVE_PATH}" PATH) target_sources(${target} PRIVATE ${_DATA_FILE}) set_source_files_properties(${_DATA_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/${_RELATIVE_PATH}") string(REPLACE "\\" "\\\\" _GROUP_NAME "${_RELATIVE_PATH}") source_group("Resources\\${_GROUP_NAME}" FILES ${_DATA_FILE}) endforeach() endif() endfunction() else() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_ARCH_SUFFIX 64) else() set(_ARCH_SUFFIX 32) endif() set(OBS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rundir") if(OS_POSIX) IF(NOT LINUX_PORTABLE) set(OBS_LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") set(OBS_PLUGIN_DESTINATION "${OBS_LIBRARY_DESTINATION}/obs-plugins") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") set(OBS_DATA_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/obs") else() set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") set(CMAKE_INSTALL_RPATH "$ORIGIN/" "${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}") set(OBS_DATA_DESTINATION "data") endif() if(OS_LINUX) set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${OBS_PLUGUIN_VERSION}") set(CPACK_PACKAGE_CONTACT "${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>") set(CPACK_PACKAGE_VENDOR "${LINUX_MAINTAINER}") set(CPACK_GENERATOR "DEB" "TGZ" "RPM") set(CPACK_DEBIAN_PACKAGE_DEPENDS "obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)") set(CPACK_DEBIAN_PACKAGE_SECTION "video") set(CPACK_RPM_PACKAGE_REQUIRES "obs-studio >= 27.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0") set(CPACK_RPM_PACKAGE_GROUP "Video") set(CPACK_RPM_PACKAGE_LICENSE "GPL-2.0") if(NOT LINUX_PORTABLE) #set(CPACK_SET_DESTDIR ON) endif() include(CPack) endif() else() set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") set(OBS_LIBRARY32_DESTINATION "bin/32bit") set(OBS_LIBRARY64_DESTINATION "bin/64bit") set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") set(OBS_PLUGIN32_DESTINATION "obs-plugins/32bit") set(OBS_PLUGIN64_DESTINATION "obs-plugins/64bit") set(OBS_DATA_DESTINATION "data") endif() function(setup_plugin_target target) set_target_properties(${target} PROPERTIES PREFIX "") install(TARGETS ${target} RUNTIME DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime LIBRARY DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime NAMELINK_COMPONENT ${target}_Development) if(OS_WINDOWS) install(FILES "$" DESTINATION "${OBS_PLUGIN_DESTINATION}" OPTIONAL) endif() add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/$" VERBATIM) if(OS_WINDOWS) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E "$,$>,copy,true>" "$" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/$" VERBATIM) endif() if(MSVC) target_link_options(${target} PRIVATE "LINKER:/OPT:REF" "$<$>:LINKER\:/SAFESEH\:NO>" "$<$:LINKER\:/INCREMENTAL:NO>" "$<$:LINKER\:/INCREMENTAL:NO>") endif() setup_target_resources("${target}" "obs-plugins/${target}") if(OS_WINDOWS AND DEFINED OBS_BUILD_DIR) setup_target_for_testing(${target}) endif() endfunction() function(setup_target_resources target destination) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DESTINATION "${OBS_DATA_DESTINATION}/${destination}" USE_SOURCE_PERMISSIONS) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data" "${OBS_OUTPUT_DIR}/$/${OBS_DATA_DESTINATION}/${destination}" VERBATIM) endif() endfunction() if(OS_WINDOWS) function(setup_target_for_testing target) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" "${OBS_BUILD_DIR}/rundir/$/${OBS_PLUGIN_DESTINATION}/$" COMMAND "${CMAKE_COMMAND}" -E "$,$>,copy,true>" "$" "${OBS_BUILD_DIR}/rundir/$/${OBS_PLUGIN_DESTINATION}/$" COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_BUILD_DIR}/rundir/$/${OBS_DATA_DESTINATION}/obs-plugins/${target}" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data" "${OBS_BUILD_DIR}/rundir/$/${OBS_DATA_DESTINATION}/obs-plugins/${target}" VERBATIM) endfunction() endif() endif() ================================================ FILE: external/ObsPluginHelpers.cmake.bak.bak ================================================ if(POLICY CMP0087) cmake_policy(SET CMP0087 NEW) endif() set(OBS_STANDALONE_PLUGIN_DIR "${CMAKE_SOURCE_DIR}/release") set(INCLUDED_LIBOBS_CMAKE_MODULES ON) include(GNUInstallDirs) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") set(OS_MACOS ON) set(OS_POSIX ON) elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux|FreeBSD|OpenBSD") set(OS_POSIX ON) string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) set(OS_${_SYSTEM_NAME_U} ON) elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") set(OS_WINDOWS ON) set(OS_POSIX OFF) endif() # Old-Style plugin detected, find "modern" libobs variant instead and set # global include directories to fix "bad" plugin behaviour if(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs) message(DEPRECATION "You are using an outdated method of adding 'libobs' to your project. Refer to the updated wiki on how to build and export 'libobs' and use it in your plugin projects.") find_package(libobs REQUIRED) if(TARGET OBS::libobs) set_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE) message(STATUS "OBS: Using modern libobs target") add_library(libobs ALIAS OBS::libobs) if(OS_WINDOWS) add_library(w32-pthreads ALIAS OBS::w32-pthreads) endif() endif() endif() if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS)) set(CMAKE_INSTALL_PREFIX "${OBS_STANDALONE_PLUGIN_DIR}" CACHE STRING "Directory to install OBS plugin after building" FORCE) endif() if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel) endif() file(RELATIVE_PATH RELATIVE_INSTALL_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_INSTALL_PREFIX}") file(RELATIVE_PATH RELATIVE_BUILD_PATH "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}") # Set-up OS-specific environment and helper functions if(OS_MACOS) if(NOT CMAKE_OSX_ARCHITECTURES) set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "OBS plugin build architecture for macOS - x86_64 required at least" FORCE) endif() if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "OBS plugin deployment target for macOS - 10.13+ required" FORCE) endif() if(NOT DEFINED OBS_CODESIGN_LINKER) set(OBS_CODESIGN_LINKER ON CACHE BOOL "Enable linker code-signing on macOS (v11+ required" FORCE) endif() if(NOT DEFINED OBS_BUNDLE_CODESIGN_IDENTITY) set(OBS_BUNDLE_CODESIGN_IDENTITY "-" CACHE STRING "Codesign identity for macOS" FORCE) endif() # Xcode configuration if(XCODE) # Tell Xcode to pretend the linker signed binaries so that # editing with install_name_tool preserves ad-hoc signatures. # See CMake Issue 21854. # This option is supported by codesign on macOS 11 or higher. set(CMAKE_XCODE_GENERATE_SCHEME ON) if(OBS_CODESIGN_LINKER) set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "-o linker-signed") endif() endif() # Set default options for bundling on macOS set(CMAKE_MACOSX_RPATH ON) set(CMAKE_SKIP_BUILD_RPATH OFF) set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF) set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks/") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF) set(OBS_PLUGIN_DESTINATION "bin") set(OBS_DATA_DESTINATION "data") function(setup_plugin_target target) install(TARGETS ${target} RUNTIME DESTINATION "${target}/${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime LIBRARY DESTINATION "${target}/${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime NAMELINK_COMPONENT ${target}_Development) install(CODE "execute_process(COMMAND /bin/sh -c \"install_name_tool \\ -change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets.framework/Versions/5/QtWidgets \\ -change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore.framework/Versions/5/QtCore \\ -change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui.framework/Versions/5/QtGui \\ ${CMAKE_INSTALL_PREFIX}/${target}/bin\")") install(CODE "execute_process(COMMAND /bin/sh -c \"/usr/bin/codesign --force --sign \\\"${OBS_BUNDLE_CODESIGN_IDENTITY}\\\" --options runtime --entitlements ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/entitlements.plist ${CMAKE_INSTALL_PREFIX}/${target}.plugin\")") setup_target_resources("${target}" "${target}") endfunction() function(setup_target_resources target destination) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DESTINATION "${destination}/${OBS_DATA_DESTINATION}" USE_SOURCE_PERMISSIONS) endif() endfunction() else() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_ARCH_SUFFIX 64) else() set(_ARCH_SUFFIX 32) endif() set(OBS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/rundir") if(OS_POSIX) IF(NOT LINUX_PORTABLE) set(OBS_LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") set(OBS_PLUGIN_DESTINATION "${OBS_LIBRARY_DESTINATION}/obs-plugins") set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") set(OBS_DATA_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/obs") else() set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") set(CMAKE_INSTALL_RPATH "$ORIGIN/" "${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}") set(OBS_DATA_DESTINATION "data") endif() if(OS_LINUX) set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") set(CPACK_PACKAGE_VERSION "${OBS_PLUGUIN_VERSION}") set(CPACK_PACKAGE_CONTACT "${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>") set(CPACK_PACKAGE_VENDOR "${LINUX_MAINTAINER}") set(CPACK_GENERATOR "DEB" "TGZ" "RPM") set(CPACK_DEBIAN_PACKAGE_DEPENDS "obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)") set(CPACK_DEBIAN_PACKAGE_SECTION "video") set(CPACK_RPM_PACKAGE_REQUIRES "obs-studio >= 27.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0") set(CPACK_RPM_PACKAGE_GROUP "Video") set(CPACK_RPM_PACKAGE_LICENSE "GPL-2.0") if(NOT LINUX_PORTABLE) #set(CPACK_SET_DESTDIR ON) endif() include(CPack) endif() else() set(OBS_LIBRARY_DESTINATION "bin/${_ARCH_SUFFIX}bit") set(OBS_LIBRARY32_DESTINATION "bin/32bit") set(OBS_LIBRARY64_DESTINATION "bin/64bit") set(OBS_PLUGIN_DESTINATION "obs-plugins/${_ARCH_SUFFIX}bit") set(OBS_PLUGIN32_DESTINATION "obs-plugins/32bit") set(OBS_PLUGIN64_DESTINATION "obs-plugins/64bit") set(OBS_DATA_DESTINATION "data") endif() function(setup_plugin_target target) set_target_properties(${target} PROPERTIES PREFIX "") install(TARGETS ${target} RUNTIME DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime LIBRARY DESTINATION "${OBS_PLUGIN_DESTINATION}" COMPONENT ${target}_Runtime NAMELINK_COMPONENT ${target}_Development) if(OS_WINDOWS) install(FILES "$" DESTINATION "${OBS_PLUGIN_DESTINATION}" OPTIONAL) endif() add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/$" VERBATIM) if(OS_WINDOWS) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E "$,$>,copy,true>" "$" "${OBS_OUTPUT_DIR}/$/${OBS_PLUGIN_DESTINATION}/$" VERBATIM) endif() if(MSVC) target_link_options(${target} PRIVATE "LINKER:/OPT:REF" "$<$>:LINKER\:/SAFESEH\:NO>" "$<$:LINKER\:/INCREMENTAL:NO>" "$<$:LINKER\:/INCREMENTAL:NO>") endif() setup_target_resources("${target}" "obs-plugins/${target}") if(OS_WINDOWS AND DEFINED OBS_BUILD_DIR) setup_target_for_testing(${target}) endif() endfunction() function(setup_target_resources target destination) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DESTINATION "${OBS_DATA_DESTINATION}/${destination}" USE_SOURCE_PERMISSIONS) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data" "${OBS_OUTPUT_DIR}/$/${OBS_DATA_DESTINATION}/${destination}" VERBATIM) endif() endfunction() if(OS_WINDOWS) function(setup_target_for_testing target) add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy "$" "${OBS_BUILD_DIR}/rundir/$/${OBS_PLUGIN_DESTINATION}/$" COMMAND "${CMAKE_COMMAND}" -E "$,$>,copy,true>" "$" "${OBS_BUILD_DIR}/rundir/$/${OBS_PLUGIN_DESTINATION}/$" COMMAND "${CMAKE_COMMAND}" -E make_directory "${OBS_BUILD_DIR}/rundir/$/${OBS_DATA_DESTINATION}/obs-plugins/${target}" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data" "${OBS_BUILD_DIR}/rundir/$/${OBS_DATA_DESTINATION}/obs-plugins/${target}" VERBATIM) endfunction() endif() endif() ================================================ FILE: helper.h ================================================ #include #include #include #include #include #include #pragma once #define CONFIG_SECTIION "RstpOutput" #define HOTKEY_CONFIG_SECTIION "Hotkeys" enum encoder_codec { UNKNOW = 0, H264 = 1, HEVC = 2, AV1 = 3, AAC = 4 }; [[maybe_unused]] static bool make_config_dir() { auto path = obs_module_config_path(""); auto ret = os_mkdirs(path); bfree(path); return ret == MKDIR_SUCCESS || ret == MKDIR_EXISTS; } [[maybe_unused]] static obs_data_t *rtsp_output_read_data() { obs_data_t *data; auto path = obs_module_config_path("rtsp_output.json"); data = obs_data_create_from_json_file_safe(path, "bak"); bfree(path); return data; } [[maybe_unused]] static bool rtsp_output_save_data(obs_data_t *data) { if (!make_config_dir()) return false; auto path = obs_module_config_path("rtsp_output.json"); auto ret = obs_data_save_json_safe(data, path, "tmp", "bak"); bfree(path); return ret; } [[maybe_unused]] static config_t *rtsp_properties_open_config() { if (!make_config_dir()) return nullptr; auto path = obs_module_config_path("config.ini"); config_t *config; auto ret = config_open(&config, path, CONFIG_OPEN_ALWAYS); bfree(path); if (ret) return nullptr; config_set_default_bool(config, CONFIG_SECTIION, "AutoStart", false); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack1", true); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack2", false); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack3", false); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack4", false); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack5", false); config_set_default_bool(config, CONFIG_SECTIION, "AudioTrack6", false); return config; } [[maybe_unused]] static std::string string_format(char const *format, ...) { va_list argp; va_start(argp, format); auto size = (size_t)vsnprintf(nullptr, 0, format, argp) + 1; va_end(argp); auto buf = std::vector(size); va_start(argp, format); vsnprintf(buf.data(), size, format, argp); va_end(argp); return std::string(buf.data(), buf.data() + size - 1); } [[maybe_unused]] static std::string rtsp_properties_get_data_volume_display(uint64_t total_bytes) { const uint64_t kb = 1024; const uint64_t mb = kb * 1024; const uint64_t gb = mb * 1024; const uint64_t tb = gb * 1024; if (total_bytes == 0) return "0.0 MB"; if (total_bytes < kb) { return string_format("%lu bytes", total_bytes); } if (total_bytes < mb) { return string_format("%.1f KB", double(total_bytes) / kb); } if (total_bytes < gb) { return string_format("%.1f MB", double(total_bytes) / mb); } if (total_bytes < tb) { return string_format("%.1f GB", double(total_bytes) / gb); } return string_format("%.1f TB", double(total_bytes) / tb); } [[maybe_unused]] static encoder_codec get_encoder_codec(const obs_encoder_t *encoder) { const char *const codec = obs_encoder_get_codec(encoder); if (strcmp(codec, "h264") == 0) { return encoder_codec::H264; } if (strcmp(codec, "hevc") == 0) { return encoder_codec::HEVC; } if (strcmp(codec, "av1") == 0) { return encoder_codec::AV1; } if (strcmp(codec, "aac") == 0) { return encoder_codec::AAC; } return UNKNOW; } ================================================ FILE: installer/function/unprevious.nsi ================================================ ;Uninstall Previous Function UninstallPrevious ; Check for uninstaller. ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" StrCpy $R1 $INSTDIR ReadRegStr $R1 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "InstallLocation" ${If} $R0 == "" Return ${EndIf} DetailPrint $(LANGTEXT_REMOVING_PREV) ; Run the uninstaller silently. ExecWait '"$R0" /S _?=$R1' $0 ${If} $0 != 0 Abort $(LANGTEXT_REMOVING_PREV_FAILED) ${EndIf} FunctionEnd ; eof ================================================ FILE: installer/installer.nsi ================================================ Unicode true ; Define your application name !define APPNAME "obs-rtspserver" !define DISPLAYNAME $(LANGTEXT_DISPLAYNAME) !ifndef APPVERSION !define APPVERSION ${FVERSION} !define SHORTVERSION ${VERSION} !define PLANTFORMNAME ${PLANTFORM} !endif !define APPNAMEANDVERSION "${DISPLAYNAME} v${SHORTVERSION}" ; Main Install settings Name "${APPNAMEANDVERSION}" InstallDir "$PROGRAMFILES64\obs-studio" InstallDirRegKey HKLM "Software\OBS Studio" "" OutFile "..\obs-rtspserver-v${SHORTVERSION}-windows-${PLANTFORMNAME}-installer.exe" ; Use compression SetCompressor LZMA ; Modern interface settings !include MUI2.nsh ; Include nsDialogs !include nsDialogs.nsh ; Include library for dll stuff !include Library.nsh ; Include uninstall Previous function !include .\function\unprevious.nsi !define MUI_ICON "obs.ico" !define MUI_UNICON "obs.ico" ;!define MUI_HEADERIMAGE ;!define MUI_HEADERIMAGE_BITMAP "" ;!define MUI_HEADERIMAGE_RIGHT !define MUI_ABORTWARNING !define MUI_PAGE_HEADER_TEXT $(LANGTEXT_HEADER_TEXT) !define MUI_PAGE_HEADER_SUBTEXT $(LANGTEXT_HEADER_SUBTEXT) !define MUI_LICENSEPAGE_TEXT_TOP $(LANGTEXT_LICENSEPAGE_TEXT_TOP) !define MUI_LICENSEPAGE_TEXT_BOTTOM " " !define MUI_LICENSEPAGE_BUTTON $(LANGTEXT_LICENSEPAGE_BUTTON) !define MUI_FINISHPAGE_NOAUTOCLOSE !define MUI_HEADER_TRANSPARENT_TEXT !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "LICENSE" !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_COMPONENTS !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH !include .\locale.nsi !insertmacro MUI_RESERVEFILE_LANGDLL Section -SecUninstallPrevious Call UninstallPrevious SectionEnd Section "!OBS RTSP Server" SectionIn RO ; Set Section properties SetOverwrite on ; Set Section Files and Shortcuts SetOutPath "$INSTDIR\" File /r "..\release\" SectionEnd Section -FinishSection WriteRegStr HKLM "Software\${APPNAME}" "" "$INSTDIR" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${DISPLAYNAME}" WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "InstallLocation" "$INSTDIR" WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\uninstall_obs-rtspserver.exe" WriteRegExpandStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayIcon" "$INSTDIR\uninstall_obs-rtspserver.exe,0" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "URLInfoAbout" "https://obsproject.com/forum/resources/obs-rtspserver.1037/" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${SHORTVERSION}" WriteUninstaller "$INSTDIR\uninstall_obs-rtspserver.exe" SectionEnd ;Uninstall section Section "!un.OBS RTSP Server" SectionIn RO ; Clean up obs-rtspserver Delete "$INSTDIR\obs-plugins\64bit\obs-rtspserver.dll" Delete "$INSTDIR\obs-plugins\64bit\obs-rtspserver.pdb" Delete "$INSTDIR\obs-plugins\32bit\obs-rtspserver.dll" Delete "$INSTDIR\obs-plugins\32bit\obs-rtspserver.pdb" ; Remove data directory RMDir /r "$INSTDIR\data\obs-plugins\obs-rtspserver\" SectionEnd Section -un.FinishSection ;Remove from registry... DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" DeleteRegKey HKLM "SOFTWARE\${APPNAME}" ; Delete self Delete "$INSTDIR\uninstall_obs-rtspserver.exe" SectionEnd Function .onInit !insertmacro MUI_LANGDLL_DISPLAY FunctionEnd ;version information VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "OBS Studio" VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "OBS RTSP Server Plugin Installer" VIAddVersionKey /LANG=${LANG_ENGLISH} "OriginalFilename" "obs-rtspserver-v${SHORTVERSION}-windows-installer.exe" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "${APPVERSION}" VIProductVersion "${APPVERSION}" ================================================ FILE: installer/locale/de-DE.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_GERMAN} "OBS RTSP Server Plugin" LangString LANGTEXT_HEADER_TEXT ${LANG_GERMAN} "Lizenzinformationen" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_GERMAN} "Bitte überprüfen Sie die Lizenzbedingungen, bevor Sie ${DISPLAYNAME} installieren." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_GERMAN} "Drücken Sie auf die Bild Unten-Taste oder scrollen Sie, um den Rest der Lizenz anzuzeigen." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_GERMAN} "&Weiter >" LangString LANGTEXT_REMOVING_PREV ${LANG_GERMAN} "Vorherige Installation wird entfernt..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_GERMAN} "Fehler beim Entfernen der vorherigen Installation." ================================================ FILE: installer/locale/en-US.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_ENGLISH} "OBS RTSP Server Plugin" LangString LANGTEXT_HEADER_TEXT ${LANG_ENGLISH} "License Information" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_ENGLISH} "Please review the license terms before installing ${DISPLAYNAME}." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_ENGLISH} "Press Page Down or scroll to see the rest of the license." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_ENGLISH} "&Next >" LangString LANGTEXT_REMOVING_PREV ${LANG_ENGLISH} "Removing previous installation..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_ENGLISH} "Failed to remove previous installation." ================================================ FILE: installer/locale/es-ES.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_SPANISH} "OBS RTSP Server Plugin" LangString LANGTEXT_HEADER_TEXT ${LANG_SPANISH} "Información de licencia" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_SPANISH} "Revise los términos de la licencia antes de instalar ${DISPLAYNAME}." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_SPANISH} "Presione Av Pág o desplácese para ver el resto de la licencia." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_SPANISH} "&Siguiente >" LangString LANGTEXT_REMOVING_PREV ${LANG_SPANISH} "Eliminando la instalación anterior ..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_SPANISH} "No se pudo eliminar la instalación anterior." ================================================ FILE: installer/locale/fr-FR.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_FRENCH} "Module d'extension de serveur RTSP OBS" LangString LANGTEXT_HEADER_TEXT ${LANG_FRENCH} "Informations de licence" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_FRENCH} "Veuillez vérifier les termes de la licence avant d'installer ${DISPLAYNAME}." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_FRENCH} "Appuyez sur le bouton page bas ou faites défiler pour afficher le reste de la licence." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_FRENCH} "&Suivant >" LangString LANGTEXT_REMOVING_PREV ${LANG_FRENCH} "Suppression de l'installation précédente..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_FRENCH} "Échec de la suppression de l'installation précédente." ================================================ FILE: installer/locale/it-IT.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_ITALIAN} "OBS RTSP Server Plugin" LangString LANGTEXT_HEADER_TEXT ${LANG_ITALIAN} "Informazioni sulla licenza" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_ITALIAN} "Si prega di rivedere i termini della licenza prima di installare ${DISPLAYNAME}." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_ITALIAN} "Premi [Pagina Giù] o scorri per vedere il resto della licenza." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_ITALIAN} "&Seguente >" LangString LANGTEXT_REMOVING_PREV ${LANG_ITALIAN} "Rimozione dell'installazione precedente..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_ITALIAN} "Impossibile rimuovere l'installazione precedente." ================================================ FILE: installer/locale/ja-JP.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_JAPANESE} "OBS RTSPサーバープラグイン" LangString LANGTEXT_HEADER_TEXT ${LANG_JAPANESE} "ライセンス情報" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_JAPANESE} "${DISPLAYNAME} をインストールする前にライセンス条項を確認してください。" LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_JAPANESE} "「PageDown」を押すか、スクロールして残りのライセンスを表示します。" LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_JAPANESE} "&次 >" LangString LANGTEXT_REMOVING_PREV ${LANG_JAPANESE} "以前のインストールを削除します..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_JAPANESE} "以前のインストールを削除できませんでした。" ================================================ FILE: installer/locale/ko-KR.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_KOREAN} "OBS RTSP 서버 플러그인" LangString LANGTEXT_HEADER_TEXT ${LANG_KOREAN} "라이선스 정보" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_KOREAN} "${DISPLAYNAME}를 설치하기 전에 사용 조건을 검토하십시오." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_KOREAN} "[Page Down]을 누르거나 스크롤하여 나머지 라이선스를 확인합니다." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_KOREAN} "&다음 >" LangString LANGTEXT_REMOVING_PREV ${LANG_KOREAN} "이전 설치 제거 중 ..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_KOREAN} "이전 설치를 제거하지 못했습니다." ================================================ FILE: installer/locale/nl-NL.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_DUTCH} "OBS RTSP Server Plugin" LangString LANGTEXT_HEADER_TEXT ${LANG_DUTCH} "Licentie-informatie" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_DUTCH} "Lees de licentievoorwaarden voordat u gaat installeren ${DISPLAYNAME}." LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_DUTCH} "Druk op Page Down of scroll om de rest van de licentie te zien." LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_DUTCH} "&Volgende >" LangString LANGTEXT_REMOVING_PREV ${LANG_DUTCH} "Vorige installatie verwijderen..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_DUTCH} "Vorige installatie kan niet worden verwijderd." ================================================ FILE: installer/locale/zh-CN.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_SIMPCHINESE} "OBS RTSP 服务器插件" LangString LANGTEXT_HEADER_TEXT ${LANG_SIMPCHINESE} "许可证信息" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_SIMPCHINESE} "请在安装 ${DISPLAYNAME} 之前查看许可条款。" LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_SIMPCHINESE} "按 Page Down 或滚动查看许可证的其余部分。" LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_SIMPCHINESE} "下一步(&N) >" LangString LANGTEXT_REMOVING_PREV ${LANG_SIMPCHINESE} "正在删除以前的安装..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_SIMPCHINESE} "无法删除以前的安装。" ================================================ FILE: installer/locale/zh-TW.nsi ================================================ LangString LANGTEXT_DISPLAYNAME ${LANG_TRADCHINESE} "OBS RTSP 伺服器外掛程式" LangString LANGTEXT_HEADER_TEXT ${LANG_TRADCHINESE} "許可證資訊" LangString LANGTEXT_HEADER_SUBTEXT ${LANG_TRADCHINESE} "請在安裝 ${DISPLAYNAME} 之前查看許可條款。" LangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_TRADCHINESE} "按 Page Down 或滾動查看許可證的其餘部分。" LangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_TRADCHINESE} "下一步(&N) >" LangString LANGTEXT_REMOVING_PREV ${LANG_TRADCHINESE} "正在删除以前的安裝..." LangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_TRADCHINESE} "無法刪除以前的安裝。" ================================================ FILE: installer/locale.nsi ================================================ ; Set languages (first is default language) !insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "SimpChinese" !insertmacro MUI_LANGUAGE "TradChinese" !insertmacro MUI_LANGUAGE "German" !insertmacro MUI_LANGUAGE "Spanish" !insertmacro MUI_LANGUAGE "Dutch" !insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "Japanese" !insertmacro MUI_LANGUAGE "Italian" ; Locale List !include .\locale\en-US.nsi !include .\locale\zh-CN.nsi !include .\locale\zh-TW.nsi !include .\locale\de-DE.nsi !include .\locale\es-ES.nsi !include .\locale\nl-NL.nsi !include .\locale\fr-FR.nsi !include .\locale\ja-JP.nsi !include .\locale\it-IT.nsi ================================================ FILE: rtsp-server/3rdpart/libb64/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) project(libb64) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) include_directories("libb64/include") file(GLOB libb64_SOURCES libb64/src/*.c) file(GLOB libb64_HEADERS libb64/include/b64/*.h) add_library(libb64 STATIC ${libb64_SOURCES} ${libb64_HEADERS}) ================================================ FILE: rtsp-server/3rdpart/md5/COPYING ================================================ Main Library: Copyright (c) 2014, Peter Thorson. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the WebSocket++ Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Bundled Libraries: ****** Base 64 Library (base64/base64.hpp) ****** base64.hpp is a repackaging of the base64.cpp and base64.h files into a single header suitable for use as a header only library. This conversion was done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to the code are redistributed under the same license as the original, which is listed below. base64.cpp and base64.h Copyright (C) 2004-2008 René Nyffenegger This source code is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this source code must not be misrepresented; you must not claim that you wrote the original source code. If you use this source code in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original source code. 3. This notice may not be removed or altered from any source distribution. René Nyffenegger rene.nyffenegger@adp-gmbh.ch ****** SHA1 Library (sha1/sha1.hpp) ****** sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1 library (http://code.google.com/p/smallsha1/) into a single header suitable for use as a header only library. This conversion was done by Peter Thorson (webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed under the same license as the original, which is listed below. Copyright (c) 2011, Micael Hildenborg All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Micael Hildenborg nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ****** MD5 Library (common/md5.hpp) ****** md5.hpp is a reformulation of the md5.h and md5.c code from http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to function as a component of a header only library. This conversion was done by Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The changes are released under the same license as the original (listed below) Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com ****** UTF8 Validation logic (utf8_validation.hpp) ****** utf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann . See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. The original license: Copyright (c) 2008-2009 Bjoern Hoehrmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: rtsp-server/3rdpart/md5/md5.hpp ================================================ /* md5.hpp is a reformulation of the md5.h and md5.c code from http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to function as a component of a header only library. This conversion was done by Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The changes are released under the same license as the original (listed below) */ /* Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. L. Peter Deutsch ghost@aladdin.com */ /* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ /* Independent implementation of MD5 (RFC 1321). This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being copyrighted. The original and principal author of md5.h is L. Peter Deutsch . Other authors are noted in the change history that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed references to Ghostscript; clarified derivation from RFC 1321; now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); added conditionalization for C++ compilation from Martin Purschke . 1999-05-03 lpd Original version. */ #ifndef WEBSOCKETPP_COMMON_MD5_HPP #define WEBSOCKETPP_COMMON_MD5_HPP /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is * defined as non-zero, the code will be compiled to run only on big-endian * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to * run on either big- or little-endian CPUs, but will run slightly less * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. */ #include #include #include #include #include #include //namespace websocketpp { /// Provides MD5 hashing functionality namespace md5 { typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; /* Initialize the algorithm. */ inline void md5_init(md5_state_t *pms); /* Append a string to the message. */ inline void md5_append(md5_state_t *pms, md5_byte_t const *data, size_t nbytes); /* Finish the message and return the digest. */ inline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); #undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN #define ZSW_MD5_BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else #define ZSW_MD5_BYTE_ORDER 0 #endif #define ZSW_MD5_T_MASK ((md5_word_t)~0) #define ZSW_MD5_T1 /* 0xd76aa478 */ (ZSW_MD5_T_MASK ^ 0x28955b87) #define ZSW_MD5_T2 /* 0xe8c7b756 */ (ZSW_MD5_T_MASK ^ 0x173848a9) #define ZSW_MD5_T3 0x242070db #define ZSW_MD5_T4 /* 0xc1bdceee */ (ZSW_MD5_T_MASK ^ 0x3e423111) #define ZSW_MD5_T5 /* 0xf57c0faf */ (ZSW_MD5_T_MASK ^ 0x0a83f050) #define ZSW_MD5_T6 0x4787c62a #define ZSW_MD5_T7 /* 0xa8304613 */ (ZSW_MD5_T_MASK ^ 0x57cfb9ec) #define ZSW_MD5_T8 /* 0xfd469501 */ (ZSW_MD5_T_MASK ^ 0x02b96afe) #define ZSW_MD5_T9 0x698098d8 #define ZSW_MD5_T10 /* 0x8b44f7af */ (ZSW_MD5_T_MASK ^ 0x74bb0850) #define ZSW_MD5_T11 /* 0xffff5bb1 */ (ZSW_MD5_T_MASK ^ 0x0000a44e) #define ZSW_MD5_T12 /* 0x895cd7be */ (ZSW_MD5_T_MASK ^ 0x76a32841) #define ZSW_MD5_T13 0x6b901122 #define ZSW_MD5_T14 /* 0xfd987193 */ (ZSW_MD5_T_MASK ^ 0x02678e6c) #define ZSW_MD5_T15 /* 0xa679438e */ (ZSW_MD5_T_MASK ^ 0x5986bc71) #define ZSW_MD5_T16 0x49b40821 #define ZSW_MD5_T17 /* 0xf61e2562 */ (ZSW_MD5_T_MASK ^ 0x09e1da9d) #define ZSW_MD5_T18 /* 0xc040b340 */ (ZSW_MD5_T_MASK ^ 0x3fbf4cbf) #define ZSW_MD5_T19 0x265e5a51 #define ZSW_MD5_T20 /* 0xe9b6c7aa */ (ZSW_MD5_T_MASK ^ 0x16493855) #define ZSW_MD5_T21 /* 0xd62f105d */ (ZSW_MD5_T_MASK ^ 0x29d0efa2) #define ZSW_MD5_T22 0x02441453 #define ZSW_MD5_T23 /* 0xd8a1e681 */ (ZSW_MD5_T_MASK ^ 0x275e197e) #define ZSW_MD5_T24 /* 0xe7d3fbc8 */ (ZSW_MD5_T_MASK ^ 0x182c0437) #define ZSW_MD5_T25 0x21e1cde6 #define ZSW_MD5_T26 /* 0xc33707d6 */ (ZSW_MD5_T_MASK ^ 0x3cc8f829) #define ZSW_MD5_T27 /* 0xf4d50d87 */ (ZSW_MD5_T_MASK ^ 0x0b2af278) #define ZSW_MD5_T28 0x455a14ed #define ZSW_MD5_T29 /* 0xa9e3e905 */ (ZSW_MD5_T_MASK ^ 0x561c16fa) #define ZSW_MD5_T30 /* 0xfcefa3f8 */ (ZSW_MD5_T_MASK ^ 0x03105c07) #define ZSW_MD5_T31 0x676f02d9 #define ZSW_MD5_T32 /* 0x8d2a4c8a */ (ZSW_MD5_T_MASK ^ 0x72d5b375) #define ZSW_MD5_T33 /* 0xfffa3942 */ (ZSW_MD5_T_MASK ^ 0x0005c6bd) #define ZSW_MD5_T34 /* 0x8771f681 */ (ZSW_MD5_T_MASK ^ 0x788e097e) #define ZSW_MD5_T35 0x6d9d6122 #define ZSW_MD5_T36 /* 0xfde5380c */ (ZSW_MD5_T_MASK ^ 0x021ac7f3) #define ZSW_MD5_T37 /* 0xa4beea44 */ (ZSW_MD5_T_MASK ^ 0x5b4115bb) #define ZSW_MD5_T38 0x4bdecfa9 #define ZSW_MD5_T39 /* 0xf6bb4b60 */ (ZSW_MD5_T_MASK ^ 0x0944b49f) #define ZSW_MD5_T40 /* 0xbebfbc70 */ (ZSW_MD5_T_MASK ^ 0x4140438f) #define ZSW_MD5_T41 0x289b7ec6 #define ZSW_MD5_T42 /* 0xeaa127fa */ (ZSW_MD5_T_MASK ^ 0x155ed805) #define ZSW_MD5_T43 /* 0xd4ef3085 */ (ZSW_MD5_T_MASK ^ 0x2b10cf7a) #define ZSW_MD5_T44 0x04881d05 #define ZSW_MD5_T45 /* 0xd9d4d039 */ (ZSW_MD5_T_MASK ^ 0x262b2fc6) #define ZSW_MD5_T46 /* 0xe6db99e5 */ (ZSW_MD5_T_MASK ^ 0x1924661a) #define ZSW_MD5_T47 0x1fa27cf8 #define ZSW_MD5_T48 /* 0xc4ac5665 */ (ZSW_MD5_T_MASK ^ 0x3b53a99a) #define ZSW_MD5_T49 /* 0xf4292244 */ (ZSW_MD5_T_MASK ^ 0x0bd6ddbb) #define ZSW_MD5_T50 0x432aff97 #define ZSW_MD5_T51 /* 0xab9423a7 */ (ZSW_MD5_T_MASK ^ 0x546bdc58) #define ZSW_MD5_T52 /* 0xfc93a039 */ (ZSW_MD5_T_MASK ^ 0x036c5fc6) #define ZSW_MD5_T53 0x655b59c3 #define ZSW_MD5_T54 /* 0x8f0ccc92 */ (ZSW_MD5_T_MASK ^ 0x70f3336d) #define ZSW_MD5_T55 /* 0xffeff47d */ (ZSW_MD5_T_MASK ^ 0x00100b82) #define ZSW_MD5_T56 /* 0x85845dd1 */ (ZSW_MD5_T_MASK ^ 0x7a7ba22e) #define ZSW_MD5_T57 0x6fa87e4f #define ZSW_MD5_T58 /* 0xfe2ce6e0 */ (ZSW_MD5_T_MASK ^ 0x01d3191f) #define ZSW_MD5_T59 /* 0xa3014314 */ (ZSW_MD5_T_MASK ^ 0x5cfebceb) #define ZSW_MD5_T60 0x4e0811a1 #define ZSW_MD5_T61 /* 0xf7537e82 */ (ZSW_MD5_T_MASK ^ 0x08ac817d) #define ZSW_MD5_T62 /* 0xbd3af235 */ (ZSW_MD5_T_MASK ^ 0x42c50dca) #define ZSW_MD5_T63 0x2ad7d2bb #define ZSW_MD5_T64 /* 0xeb86d391 */ (ZSW_MD5_T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, md5_byte_t const *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if ZSW_MD5_BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; md5_word_t const *X; #endif { #if ZSW_MD5_BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static int const w = 1; if (*((md5_byte_t const *)&w)) /* dynamic little-endian */ #endif #if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (md5_byte_t const *)0) & 3)) { /* data are properly aligned */ X = (md5_word_t const *)data; } else { /* not aligned */ std::memcpy(xbuf, data, 64); X = xbuf; } } #endif #if ZSW_MD5_BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; #if ZSW_MD5_BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ #else #define xbuf X /* (static only) */ #endif for (i = 0; i < 16; ++i, xp += 4) xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } #endif } #define ZSW_MD5_ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define ZSW_MD5_F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + ZSW_MD5_F(b, c, d) + X[k] + Ti; \ a = ZSW_MD5_ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, ZSW_MD5_T1); SET(d, a, b, c, 1, 12, ZSW_MD5_T2); SET(c, d, a, b, 2, 17, ZSW_MD5_T3); SET(b, c, d, a, 3, 22, ZSW_MD5_T4); SET(a, b, c, d, 4, 7, ZSW_MD5_T5); SET(d, a, b, c, 5, 12, ZSW_MD5_T6); SET(c, d, a, b, 6, 17, ZSW_MD5_T7); SET(b, c, d, a, 7, 22, ZSW_MD5_T8); SET(a, b, c, d, 8, 7, ZSW_MD5_T9); SET(d, a, b, c, 9, 12, ZSW_MD5_T10); SET(c, d, a, b, 10, 17, ZSW_MD5_T11); SET(b, c, d, a, 11, 22, ZSW_MD5_T12); SET(a, b, c, d, 12, 7, ZSW_MD5_T13); SET(d, a, b, c, 13, 12, ZSW_MD5_T14); SET(c, d, a, b, 14, 17, ZSW_MD5_T15); SET(b, c, d, a, 15, 22, ZSW_MD5_T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define ZSW_MD5_G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + ZSW_MD5_G(b, c, d) + X[k] + Ti; \ a = ZSW_MD5_ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, ZSW_MD5_T17); SET(d, a, b, c, 6, 9, ZSW_MD5_T18); SET(c, d, a, b, 11, 14, ZSW_MD5_T19); SET(b, c, d, a, 0, 20, ZSW_MD5_T20); SET(a, b, c, d, 5, 5, ZSW_MD5_T21); SET(d, a, b, c, 10, 9, ZSW_MD5_T22); SET(c, d, a, b, 15, 14, ZSW_MD5_T23); SET(b, c, d, a, 4, 20, ZSW_MD5_T24); SET(a, b, c, d, 9, 5, ZSW_MD5_T25); SET(d, a, b, c, 14, 9, ZSW_MD5_T26); SET(c, d, a, b, 3, 14, ZSW_MD5_T27); SET(b, c, d, a, 8, 20, ZSW_MD5_T28); SET(a, b, c, d, 13, 5, ZSW_MD5_T29); SET(d, a, b, c, 2, 9, ZSW_MD5_T30); SET(c, d, a, b, 7, 14, ZSW_MD5_T31); SET(b, c, d, a, 12, 20, ZSW_MD5_T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define ZSW_MD5_H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti) \ t = a + ZSW_MD5_H(b, c, d) + X[k] + Ti; \ a = ZSW_MD5_ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, ZSW_MD5_T33); SET(d, a, b, c, 8, 11, ZSW_MD5_T34); SET(c, d, a, b, 11, 16, ZSW_MD5_T35); SET(b, c, d, a, 14, 23, ZSW_MD5_T36); SET(a, b, c, d, 1, 4, ZSW_MD5_T37); SET(d, a, b, c, 4, 11, ZSW_MD5_T38); SET(c, d, a, b, 7, 16, ZSW_MD5_T39); SET(b, c, d, a, 10, 23, ZSW_MD5_T40); SET(a, b, c, d, 13, 4, ZSW_MD5_T41); SET(d, a, b, c, 0, 11, ZSW_MD5_T42); SET(c, d, a, b, 3, 16, ZSW_MD5_T43); SET(b, c, d, a, 6, 23, ZSW_MD5_T44); SET(a, b, c, d, 9, 4, ZSW_MD5_T45); SET(d, a, b, c, 12, 11, ZSW_MD5_T46); SET(c, d, a, b, 15, 16, ZSW_MD5_T47); SET(b, c, d, a, 2, 23, ZSW_MD5_T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define ZSW_MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + ZSW_MD5_I(b, c, d) + X[k] + Ti; \ a = ZSW_MD5_ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, ZSW_MD5_T49); SET(d, a, b, c, 7, 10, ZSW_MD5_T50); SET(c, d, a, b, 14, 15, ZSW_MD5_T51); SET(b, c, d, a, 5, 21, ZSW_MD5_T52); SET(a, b, c, d, 12, 6, ZSW_MD5_T53); SET(d, a, b, c, 3, 10, ZSW_MD5_T54); SET(c, d, a, b, 10, 15, ZSW_MD5_T55); SET(b, c, d, a, 1, 21, ZSW_MD5_T56); SET(a, b, c, d, 8, 6, ZSW_MD5_T57); SET(d, a, b, c, 15, 10, ZSW_MD5_T58); SET(c, d, a, b, 6, 15, ZSW_MD5_T59); SET(b, c, d, a, 13, 21, ZSW_MD5_T60); SET(a, b, c, d, 4, 6, ZSW_MD5_T61); SET(d, a, b, c, 11, 10, ZSW_MD5_T62); SET(c, d, a, b, 2, 15, ZSW_MD5_T63); SET(b, c, d, a, 9, 21, ZSW_MD5_T64); #undef SET /* Then perform the following additions. (That is increment each of the four registers by the value it had before this block was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ ZSW_MD5_T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ ZSW_MD5_T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } void md5_append(md5_state_t *pms, md5_byte_t const *data, size_t nbytes) { md5_byte_t const *p = data; size_t left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) return; /* Update the message length. */ pms->count[1] += (md5_word_t)(nbytes >> 29); pms->count[0] += nbits; if (pms->count[0] < nbits) pms->count[1]++; /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : static_cast(nbytes)); std::memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) return; p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) md5_process(pms, p); /* Process a final partial block. */ if (left) std::memcpy(pms->buf, p, left); } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static md5_byte_t const pad[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } } #endif // WEBSOCKETPP_COMMON_MD5_HPP ================================================ FILE: rtsp-server/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) project(rtsp-server) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) include_directories("3rdpart/md5") include_directories("3rdpart/libb64/libb64/include") add_subdirectory(3rdpart/libb64) file(GLOB rtsp-server_net_SOURCES net/*.cpp) file(GLOB rtsp-server_xop_SOURCES xop/*.cpp) set(rtsp-server_SOURCES ${rtsp-server_net_SOURCES} ${rtsp-server_xop_SOURCES} ) file(GLOB rtsp-server_net_HEADERS net/*.h) file(GLOB rtsp-server_xop_HEADERS xop/*.h) set(rtsp-server_HEADERS ${rtsp-server_net_HEADERS} ${rtsp-server_xop_HEADERS} ) add_library(rtsp-server STATIC ${rtsp-server_SOURCES} ${rtsp-server_HEADERS} ) target_link_libraries(rtsp-server libb64) ================================================ FILE: rtsp-server/net/Acceptor.cpp ================================================ //Scott Xu //2020-12-6 Add IPv6 support. #include "Acceptor.h" #include "EventLoop.h" #include "SocketUtil.h" #include "Logger.h" using namespace xop; Acceptor::Acceptor(EventLoop *eventLoop) : event_loop_(eventLoop), tcp_socket_(new TcpSocket()) { } Acceptor::~Acceptor() = default; int Acceptor::Listen(const std::string &ip, const uint16_t port) { std::lock_guard locker(mutex_); if (tcp_socket_->GetSocket() > 0) { tcp_socket_->Close(); } const SOCKET sockfd = tcp_socket_->Create(SocketUtil::IsIpv6Address(ip)); channel_ptr_.reset(new Channel(sockfd)); SocketUtil::SetReuseAddr(sockfd); SocketUtil::SetReusePort(sockfd); SocketUtil::SetNonBlock(sockfd); if (!tcp_socket_->Bind(ip, port)) { return -1; } if (!tcp_socket_->Listen(1024)) { return -1; } channel_ptr_->SetReadCallback([this] { this->OnAccept(); }); channel_ptr_->EnableReading(); event_loop_->UpdateChannel(channel_ptr_); return 0; } void Acceptor::Close() { std::lock_guard locker(mutex_); if (tcp_socket_->GetSocket() > 0) { event_loop_->RemoveChannel(channel_ptr_); tcp_socket_->Close(); } } void Acceptor::OnAccept() { std::lock_guard locker(mutex_); if (const auto sockfd = tcp_socket_->Accept(); sockfd > 0) { if (new_connection_callback_) { new_connection_callback_(sockfd); } else { SocketUtil::Close(sockfd); } } } ================================================ FILE: rtsp-server/net/Acceptor.h ================================================ //Scott Xu //2020-12-6 Add IPv6 support. #ifndef XOP_ACCEPTOR_H #define XOP_ACCEPTOR_H #include #include #include #include "Channel.h" #include "TcpSocket.h" namespace xop { typedef std::function NewConnectionCallback; class EventLoop; class Acceptor { public: explicit Acceptor(EventLoop *eventLoop); virtual ~Acceptor(); void SetNewConnectionCallback(const NewConnectionCallback &cb) { new_connection_callback_ = cb; } int Listen(const std::string &ip, uint16_t port); void Close(); private: void OnAccept(); EventLoop *event_loop_ = nullptr; std::mutex mutex_; std::unique_ptr tcp_socket_; ChannelPtr channel_ptr_; NewConnectionCallback new_connection_callback_; }; } #endif ================================================ FILE: rtsp-server/net/BufferReader.cpp ================================================ // PHZ // 2018-5-15 #include "BufferReader.h" #include "Socket.h" using namespace xop; uint32_t xop::ReadUint32BE(char *data) { const auto p = reinterpret_cast(data); const uint32_t value = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; return value; } uint32_t xop::ReadUint32LE(char *data) { const auto p = reinterpret_cast(data); const uint32_t value = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]; return value; } uint32_t xop::ReadUint24BE(char *data) { const auto p = reinterpret_cast(data); const uint32_t value = (p[0] << 16) | (p[1] << 8) | p[2]; return value; } uint32_t xop::ReadUint24LE(char *data) { const auto p = reinterpret_cast(data); const uint32_t value = (p[2] << 16) | (p[1] << 8) | p[0]; return value; } uint16_t xop::ReadUint16BE(char *data) { const auto p = reinterpret_cast(data); const uint16_t value = (p[0] << 8) | p[1]; return value; } uint16_t xop::ReadUint16LE(char *data) { const auto *p = reinterpret_cast(data); const uint16_t value = (p[1] << 8) | p[0]; return value; } constexpr char BufferReader::kCRLF[] = "\r\n"; BufferReader::BufferReader(const uint32_t initialSize) : buffer_(initialSize) { buffer_.resize(initialSize); } BufferReader::~BufferReader() = default; int BufferReader::Read(const SOCKET sockfd) { if (const size_t size = WritableBytes(); size < MAX_BYTES_PER_READ) { const auto bufferReaderSize = buffer_.size(); if (bufferReaderSize > MAX_BUFFER_SIZE) { return 0; } buffer_.resize(bufferReaderSize + MAX_BYTES_PER_READ); } const int bytes_read = recv(sockfd, beginWrite(), MAX_BYTES_PER_READ, 0); if (bytes_read > 0) { writer_index_ += bytes_read; } return bytes_read; } size_t BufferReader::ReadAll(std::string &data) { const size_t size = ReadableBytes(); if (size > 0) { data.assign(Peek(), size); writer_index_ = 0; reader_index_ = 0; } return size; } size_t BufferReader::ReadUntilCrlf(std::string &data) { const char *crlf = FindLastCrlf(); if (crlf == nullptr) { return 0; } const auto size = static_cast(crlf - Peek() + 2); data.assign(Peek(), size); Retrieve(size); return size; } ================================================ FILE: rtsp-server/net/BufferReader.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_BUFFER_READER_H #define XOP_BUFFER_READER_H #include #include #include #include #include "Socket.h" namespace xop { uint32_t ReadUint32BE(char *data); uint32_t ReadUint32LE(char *data); uint32_t ReadUint24BE(char *data); uint32_t ReadUint24LE(char *data); uint16_t ReadUint16BE(char *data); uint16_t ReadUint16LE(char *data); class BufferReader { public: static constexpr uint32_t kInitialSize = 2048; explicit BufferReader(uint32_t initialSize = kInitialSize); virtual ~BufferReader(); size_t ReadableBytes() const { return writer_index_ - reader_index_; } size_t WritableBytes() const { return buffer_.size() - writer_index_; } char *Peek() { return Begin() + reader_index_; } const char *Peek() const { return Begin() + reader_index_; } const char *FindFirstCrlf() const { const char *crlf = std::search(Peek(), BeginWrite(), kCRLF, kCRLF + 2); return crlf == BeginWrite() ? nullptr : crlf; } const char *FindLastCrlf() const { const char *crlf = std::find_end(Peek(), BeginWrite(), kCRLF, kCRLF + 2); return crlf == BeginWrite() ? nullptr : crlf; } const char *FindLastCrlfCrlf() const { char crlfCrlf[] = "\r\n\r\n"; const char *crlf = std::find_end(Peek(), BeginWrite(), crlfCrlf, crlfCrlf + 4); return crlf == BeginWrite() ? nullptr : crlf; } void RetrieveAll() { writer_index_ = 0; reader_index_ = 0; } void Retrieve(const size_t len) { if (len <= ReadableBytes()) { reader_index_ += len; if (reader_index_ == writer_index_) { reader_index_ = 0; writer_index_ = 0; } } else { RetrieveAll(); } } void RetrieveUntil(const char *end) { Retrieve(end - Peek()); } int Read(SOCKET sockfd); size_t ReadAll(std::string &data); size_t ReadUntilCrlf(std::string &data); size_t Size() const { return buffer_.size(); } private: char *Begin() { return &*buffer_.begin(); } const char *Begin() const { return &*buffer_.begin(); } char *beginWrite() { return Begin() + writer_index_; } const char *BeginWrite() const { return Begin() + writer_index_; } std::vector buffer_; size_t reader_index_ = 0; size_t writer_index_ = 0; static const char kCRLF[]; static constexpr uint32_t MAX_BYTES_PER_READ = 4096; static constexpr uint32_t MAX_BUFFER_SIZE = 1024 * 100000; }; } #endif ================================================ FILE: rtsp-server/net/BufferWriter.cpp ================================================ // PHZ // 2018-5-15 #include "BufferWriter.h" #include "Socket.h" #include "SocketUtil.h" using namespace xop; void xop::WriteUint32BE(char *p, uint32_t value) { p[0] = value >> 24; p[1] = value >> 16; p[2] = value >> 8; p[3] = value & 0xff; } void xop::WriteUint32LE(char *p, uint32_t value) { p[0] = value & 0xff; p[1] = value >> 8; p[2] = value >> 16; p[3] = value >> 24; } void xop::WriteUint24BE(char *p, uint32_t value) { p[0] = value >> 16; p[1] = value >> 8; p[2] = value & 0xff; } void xop::WriteUint24LE(char *p, uint32_t value) { p[0] = value & 0xff; p[1] = value >> 8; p[2] = value >> 16; } void xop::WriteUint16BE(char *p, uint16_t value) { p[0] = value >> 8; p[1] = value & 0xff; } void xop::WriteUint16LE(char *p, uint16_t value) { p[0] = value & 0xff; p[1] = value >> 8; } BufferWriter::BufferWriter(const int capacity) : max_queue_length_(capacity) {} bool BufferWriter::Append(const std::shared_ptr &data, const size_t size, const uint32_t index) { if (size <= index) { return false; } if (static_cast(buffer_.size()) >= max_queue_length_) { return false; } Packet pkt = {data, size, index}; buffer_.emplace(std::move(pkt)); return true; } bool BufferWriter::Append(const char *data, const size_t size, const uint32_t index) { if (size <= index) { return false; } if (static_cast(buffer_.size()) >= max_queue_length_) { return false; } Packet pkt; pkt.data.reset(new char[size + 512], std::default_delete()); memcpy(pkt.data.get(), data, size); pkt.size = size; pkt.writeIndex = index; buffer_.emplace(std::move(pkt)); return true; } int BufferWriter::Send(const SOCKET sockfd, const int timeout) { if (timeout > 0) { SocketUtil::SetBlock(sockfd, timeout); } int ret; int count = 1; do { if (buffer_.empty()) { return 0; } count -= 1; Packet &pkt = buffer_.front(); ret = send(sockfd, pkt.data.get() + pkt.writeIndex, static_cast(pkt.size) - pkt.writeIndex, 0); if (ret > 0) { pkt.writeIndex += ret; if (pkt.size == pkt.writeIndex) { count += 1; buffer_.pop(); } } else if (ret < 0) { #if defined(WIN32) || defined(_WIN32) if (const int error = WSAGetLastError(); error == WSAEWOULDBLOCK || error == WSAEINPROGRESS || error == 0) #else if (errno == EINTR || errno == EAGAIN) #endif { ret = 0; } } } while (count > 0); if (timeout > 0) { SocketUtil::SetNonBlock(sockfd); } return ret; } ================================================ FILE: rtsp-server/net/BufferWriter.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_BUFFER_WRITER_H #define XOP_BUFFER_WRITER_H #include #include #include #include "Socket.h" namespace xop { void WriteUint32BE(char *p, uint32_t value); void WriteUint32LE(char *p, uint32_t value); void WriteUint24BE(char *p, uint32_t value); void WriteUint24LE(char *p, uint32_t value); void WriteUint16BE(char *p, uint16_t value); void WriteUint16LE(char *p, uint16_t value); class BufferWriter { public: explicit BufferWriter(int capacity = kMaxQueueLength); virtual ~BufferWriter() = default; bool Append(const std::shared_ptr &data, size_t size, uint32_t index = 0); bool Append(const char *data, size_t size, uint32_t index = 0); int Send(SOCKET sockfd, int timeout = 0); bool IsEmpty() const { return buffer_.empty(); } bool IsFull() const { return static_cast(buffer_.size()) >= max_queue_length_ ? true : false; } size_t Size() const { return buffer_.size(); } private: typedef struct Packet { std::shared_ptr data; size_t size{}; uint32_t writeIndex{}; } Packet; std::queue buffer_; int max_queue_length_ = 0; static constexpr int kMaxQueueLength = 10000; }; } #endif ================================================ FILE: rtsp-server/net/Channel.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_CHANNEL_H #define XOP_CHANNEL_H #include #include #include "Socket.h" namespace xop { enum EventType: uint32_t { EVENT_NONE = 0, EVENT_IN = 1, EVENT_PRI = 2, EVENT_OUT = 4, EVENT_ERR = 8, EVENT_HUP = 16, EVENT_RDHUP = 8192 }; class Channel { public: typedef std::function EventCallback; Channel() = delete; explicit Channel(const SOCKET sockfd) : sockfd_(sockfd) {} virtual ~Channel() = default; void SetReadCallback(const EventCallback &cb) { read_callback_ = cb; } void SetWriteCallback(const EventCallback &cb) { write_callback_ = cb; } void SetCloseCallback(const EventCallback &cb) { close_callback_ = cb; } void SetErrorCallback(const EventCallback &cb) { error_callback_ = cb; } SOCKET GetSocket() const { return sockfd_; } uint32_t GetEvents() const { return events_; } void SetEvents(const int events) { events_ = events; } void EnableReading() { events_ |= EVENT_IN; } void EnableWriting() { events_ |= EVENT_OUT; } void DisableReading() { events_ &= ~EVENT_IN; } void DisableWriting() { events_ &= ~EVENT_OUT; } bool IsNoneEvent() const { return events_ == EVENT_NONE; } bool IsWriting() const { return (events_ & EVENT_OUT) != 0; } bool IsReading() const { return (events_ & EVENT_IN) != 0; } void HandleEvent(const uint32_t events) const { if (events & (EVENT_PRI | EVENT_IN)) { read_callback_(); } if (events & EVENT_OUT) { write_callback_(); } if (events & EVENT_HUP) { close_callback_(); return; } if (events & EVENT_ERR) { error_callback_(); } } private: EventCallback read_callback_ = [] {}; EventCallback write_callback_ = [] {}; EventCallback close_callback_ = [] {}; EventCallback error_callback_ = [] {}; SOCKET sockfd_ = 0; uint32_t events_ = 0; }; typedef std::shared_ptr ChannelPtr; } #endif ================================================ FILE: rtsp-server/net/EpollTaskScheduler.cpp ================================================ // PHZ // 2018-5-15 #include "EpollTaskScheduler.h" #if defined(__linux) || defined(__linux__) #include #include #endif #include "Logger.h" using namespace xop; EpollTaskScheduler::EpollTaskScheduler(const int id) : TaskScheduler(id) { #if defined(__linux) || defined(__linux__) epollfd_ = epoll_create1(0); if (epollfd_ < 0) { LOG_ERROR("epoll_create1 errno: %d", errno); } #endif this->EpollTaskScheduler::UpdateChannel(wakeup_channel_); } EpollTaskScheduler::~EpollTaskScheduler() { #if defined(__linux) || defined(__linux__) if (epollfd_ >= 0) { close(epollfd_); epollfd_ = -1; } #endif } void EpollTaskScheduler::UpdateChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); #if defined(__linux) || defined(__linux__) int fd = channel->GetSocket(); if (channels_.find(fd) != channels_.end()) { if (channel->IsNoneEvent()) { Update(EPOLL_CTL_DEL, channel); channels_.erase(fd); } else { Update(EPOLL_CTL_MOD, channel); } } else { if (!channel->IsNoneEvent()) { channels_.emplace(fd, channel); Update(EPOLL_CTL_ADD, channel); } } #endif } void EpollTaskScheduler::Update(int operation, const ChannelPtr &channel) { #if defined(__linux) || defined(__linux__) struct epoll_event event = {0}; if (operation != EPOLL_CTL_DEL) { event.data.ptr = channel.get(); event.events = channel->GetEvents(); } if (::epoll_ctl(epollfd_, operation, channel->GetSocket(), &event) < 0) { LOG_ERROR("epoll_ctl errno: %d", errno); } #endif } void EpollTaskScheduler::RemoveChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); #if defined(__linux) || defined(__linux__) int fd = channel->GetSocket(); if (channels_.find(fd) != channels_.end()) { Update(EPOLL_CTL_DEL, channel); channels_.erase(fd); } #endif } bool EpollTaskScheduler::HandleEvent(int timeout) { #if defined(__linux) || defined(__linux__) struct epoll_event events[512] = {0}; int num_events = -1; num_events = epoll_wait(epollfd_, events, 512, timeout); if (num_events < 0) { if (errno != EINTR) { LOG_ERROR("epoll_wait errno: %d", errno); return false; } } for (int n = 0; n < num_events; n++) { if (events[n].data.ptr) { static_cast(events[n].data.ptr) ->HandleEvent(events[n].events); } } return true; #else return false; #endif } ================================================ FILE: rtsp-server/net/EpollTaskScheduler.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_EPOLL_TASK_SCHEDULER_H #define XOP_EPOLL_TASK_SCHEDULER_H #include "TaskScheduler.h" #include #include namespace xop { class EpollTaskScheduler : public TaskScheduler { public: explicit EpollTaskScheduler(int id = 0); ~EpollTaskScheduler() override; void UpdateChannel(const ChannelPtr &channel) override; void RemoveChannel(const ChannelPtr &channel) override; // timeout: ms bool HandleEvent(int timeout) override; private: void Update(int operation, const ChannelPtr &channel); int epollfd_ = -1; std::mutex mutex_; std::unordered_map channels_; }; } #endif ================================================ FILE: rtsp-server/net/EventLoop.cpp ================================================ // PHZ // 2019-10-18 // Scott Xu // 2021-1-20 Added MacOS support. #include "EventLoop.h" #if defined(WIN32) || defined(_WIN32) #include #include #include "SelectTaskScheduler.h" #pragma comment(lib, "Ws2_32.lib") #pragma comment(lib, "Iphlpapi.lib") #elif defined(__linux) || defined(__linux__) #include "EpollTaskScheduler.h" #elif defined(__APPLE__) || defined(__MACH__) #include "KqueueTaskScheduler.h" #else #include "SelectTaskScheduler.h" #endif using namespace xop; EventLoop::EventLoop(const uint32_t num_threads) : num_threads_(num_threads > 0 ? num_threads : 1) { this->Loop(); } EventLoop::~EventLoop() { this->Quit(); } std::shared_ptr EventLoop::GetTaskScheduler() { std::lock_guard locker(mutex_); if (task_schedulers_.size() == 1) { return task_schedulers_.at(0); } auto task_scheduler = task_schedulers_.at(index_); index_++; if (index_ >= task_schedulers_.size()) { index_ = 1; } return task_scheduler; //return nullptr; } void EventLoop::Loop() { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { return; } for (uint32_t n = 0; n < num_threads_; n++) { #if defined(WIN32) || defined(_WIN32) std::shared_ptr task_scheduler_ptr( new SelectTaskScheduler(n)); #elif defined(__linux) || defined(__linux__) std::shared_ptr task_scheduler_ptr( new EpollTaskScheduler(n)); #elif defined(__APPLE__) || defined(__MACH__) std::shared_ptr task_scheduler_ptr( new KqueueTaskScheduler(n)); #else std::shared_ptr task_scheduler_ptr( new SelectTaskScheduler(n)); #endif task_schedulers_.push_back(task_scheduler_ptr); std::shared_ptr thread(new std::thread( &TaskScheduler::Start, task_scheduler_ptr.get())); const auto native_handle_type = thread->native_handle(); //TODO threads_.push_back(thread); } const int priority = TASK_SCHEDULER_PRIORITY_REALTIME; for (const auto &iter : threads_) { #if defined(WIN32) || defined(_WIN32) switch (priority) { case TASK_SCHEDULER_PRIORITY_LOW: SetThreadPriority(iter->native_handle(), THREAD_PRIORITY_BELOW_NORMAL); break; case TASK_SCHEDULER_PRIORITY_NORMAL: SetThreadPriority(iter->native_handle(), THREAD_PRIORITY_NORMAL); break; case TASK_SCHEDULER_PRIORITYO_HIGH: SetThreadPriority(iter->native_handle(), THREAD_PRIORITY_ABOVE_NORMAL); break; case TASK_SCHEDULER_PRIORITY_HIGHEST: SetThreadPriority(iter->native_handle(), THREAD_PRIORITY_HIGHEST); break; case TASK_SCHEDULER_PRIORITY_REALTIME: SetThreadPriority(iter->native_handle(), THREAD_PRIORITY_TIME_CRITICAL); break; } #else #endif } } void EventLoop::Quit() { std::lock_guard locker(mutex_); for (const auto &iter : task_schedulers_) { iter->Stop(); } for (const auto iter : threads_) { iter->join(); } task_schedulers_.clear(); threads_.clear(); } void EventLoop::UpdateChannel(const ChannelPtr &channel) { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { task_schedulers_[0]->UpdateChannel(channel); } } void EventLoop::RemoveChannel(ChannelPtr &channel) { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { task_schedulers_[0]->RemoveChannel(channel); } } TimerId EventLoop::AddTimer(TimerEvent timerEvent, const uint32_t msec) { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { return task_schedulers_[0]->AddTimer(std::move(timerEvent), msec); } return 0; } void EventLoop::RemoveTimer(const TimerId timerId) { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { task_schedulers_[0]->RemoveTimer(timerId); } } bool EventLoop::AddTriggerEvent(const TriggerEvent &callback) { std::lock_guard locker(mutex_); if (!task_schedulers_.empty()) { return task_schedulers_[0]->AddTriggerEvent(callback); } return false; } ================================================ FILE: rtsp-server/net/EventLoop.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_EVENT_LOOP_H #define XOP_EVENT_LOOP_H #include #include #include #include "TaskScheduler.h" #include "Timer.h" #define TASK_SCHEDULER_PRIORITY_LOW 0 #define TASK_SCHEDULER_PRIORITY_NORMAL 1 #define TASK_SCHEDULER_PRIORITYO_HIGH 2 #define TASK_SCHEDULER_PRIORITY_HIGHEST 3 #define TASK_SCHEDULER_PRIORITY_REALTIME 4 namespace xop { class EventLoop { public: EventLoop(const EventLoop &) = delete; EventLoop &operator=(const EventLoop &) = delete; explicit EventLoop( uint32_t num_threads = 1); //std::thread::hardware_concurrency() virtual ~EventLoop(); std::shared_ptr GetTaskScheduler(); bool AddTriggerEvent(const TriggerEvent &callback); TimerId AddTimer(TimerEvent timerEvent, uint32_t msec); void RemoveTimer(TimerId timerId); void UpdateChannel(const ChannelPtr &channel); void RemoveChannel(ChannelPtr &channel); void Loop(); void Quit(); private: std::mutex mutex_; uint32_t num_threads_ = 1; uint32_t index_ = 1; std::vector> task_schedulers_; std::vector> threads_; }; } #endif ================================================ FILE: rtsp-server/net/KqueueTaskScheduler.cpp ================================================ // Scott Xu // 2021-1-19 #include "KqueueTaskScheduler.h" #if defined(__APPLE__) || defined(__MACH__) #include #include #endif using namespace xop; KqueueTaskScheduler::KqueueTaskScheduler(int id) : TaskScheduler(id) { #if defined(__APPLE__) || defined(__MACH__) kqueuefd_ = kqueue(); #endif this->KqueueTaskScheduler::UpdateChannel(wakeup_channel_); } KqueueTaskScheduler::~KqueueTaskScheduler() {} void KqueueTaskScheduler::UpdateChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); #if defined(__APPLE__) || defined(__MACH__) int fd = channel->GetSocket(); if (channels_.find(fd) != channels_.end()) { if (channel->IsNoneEvent()) { Update(EV_DELETE, channel); channels_.erase(fd); } else { Update(EV_ADD | EV_ENABLE, channel); } } else { if (!channel->IsNoneEvent()) { channels_.emplace(fd, channel); Update(EV_ADD | EV_ENABLE, channel); } } #endif } void KqueueTaskScheduler::Update(int operation, const ChannelPtr &channel) { #if defined(__APPLE__) || defined(__MACH__) struct kevent events[2] = {0}; int num_events = 0; if (channel->IsReading()) EV_SET(&events[num_events++], channel->GetSocket(), EVFILT_READ, operation, NULL, NULL, channel.get()); if (channel->IsWriting()) EV_SET(&events[num_events++], channel->GetSocket(), EVFILT_WRITE, operation, NULL, NULL, channel.get()); if (kevent(kqueuefd_, events, num_events, nullptr, 0, nullptr) < 0) { } #endif } void KqueueTaskScheduler::RemoveChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); #if defined(__APPLE__) || defined(__MACH__) int fd = channel->GetSocket(); if (channels_.find(fd) != channels_.end()) { Update(EV_DELETE, channel); channels_.erase(fd); } #endif } bool KqueueTaskScheduler::HandleEvent(int timeout) { #if defined(__APPLE__) || defined(__MACH__) struct kevent events[512] = {0}; int num_events = -1; if (timeout > 0) { struct timespec _timeout = {0}; _timeout.tv_sec = timeout / 1000; _timeout.tv_nsec = (timeout % 1000) * 1000 * 1000; num_events = kevent(kqueuefd_, nullptr, 0, events, 512, &_timeout); } else num_events = kevent(kqueuefd_, nullptr, 0, events, 512, nullptr); if (num_events < 0) { if (errno != EINTR) { return false; } } for (int n = 0; n < num_events; n++) { auto filter = events[n].filter; auto flags = events[n].flags; auto channel = (Channel *)events[n].udata; if (!channel) continue; int handleEventEvents = EVENT_NONE; if (filter == EVFILT_READ) handleEventEvents = EVENT_IN; else if (filter == EVFILT_WRITE) handleEventEvents = EVENT_OUT; if (flags & EV_ERROR) handleEventEvents = handleEventEvents | EVENT_ERR; if (flags & EV_EOF) handleEventEvents = handleEventEvents | EVENT_HUP; channel->HandleEvent(handleEventEvents); } return true; #else return false; #endif } ================================================ FILE: rtsp-server/net/KqueueTaskScheduler.h ================================================ // Scott Xu // 2021-1-19 #ifndef XOP_KQUEUE_TASK_SCHEDULER_H #define XOP_KQUEUE_TASK_SCHEDULER_H #include "TaskScheduler.h" #include #include namespace xop { class KqueueTaskScheduler : public TaskScheduler { public: explicit KqueueTaskScheduler(int id = 0); ~KqueueTaskScheduler() override; void UpdateChannel(const ChannelPtr &channel) override; void RemoveChannel(const ChannelPtr &channel) override; // timeout: ms bool HandleEvent(int timeout) override; private: void Update(int operation, const ChannelPtr &channel); int kqueuefd_ = -1; std::mutex mutex_; std::unordered_map channels_; }; } #endif ================================================ FILE: rtsp-server/net/Logger.cpp ================================================ // PHZ // 2018-5-15 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "Logger.h" #include "Timestamp.h" #include #include #include using namespace xop; const char *Priority_To_String[] = {"DEBUG", "CONFIG", "INFO", "WARNING", "ERROR"}; Logger::Logger() = default; Logger &Logger::Instance() { static Logger s_logger; return s_logger; } Logger::~Logger() = default; void Logger::Init(char *pathname) { std::unique_lock lock(mutex_); if (pathname != nullptr) { ofs_.open(pathname, std::ios::out | std::ios::binary); if (ofs_.fail()) { std::cerr << "Failed to open logfile." << std::endl; } } } void Logger::Exit() { std::unique_lock lock(mutex_); if (ofs_.is_open()) { ofs_.close(); } } void Logger::SetWriteCallback(const LogWriteCallbackFun writeCallback) { _writeCallback = writeCallback; } void Logger::Log(const Priority priority, const char *__file, const char *__func, const int __line, const char *fmt, ...) { std::unique_lock lock(mutex_); char buf[2048]; auto buf_ptr = buf; auto buf_end = buf + sizeof(buf); buf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, "[%s][%s:%s:%d] ", Priority_To_String[priority], __file, __func, __line); va_list args; va_start(args, fmt); vsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args); va_end(args); this->Write(std::string(buf)); _writeCallback(priority, std::string(buf)); } void Logger::Log2(const Priority priority, const char *fmt, ...) { std::unique_lock lock(mutex_); char buf[4096]; auto buf_ptr = buf; auto buf_end = buf + sizeof(buf); buf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, "[%s] ", Priority_To_String[priority]); va_list args; va_start(args, fmt); vsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args); va_end(args); this->Write(std::string(buf)); _writeCallback(priority, std::string(buf)); } void Logger::Write(const std::string &info) { if (ofs_.is_open()) { ofs_ << "[" << Timestamp::Localtime() << "]" << info << std::endl; } } ================================================ FILE: rtsp-server/net/Logger.h ================================================ // PHZ // 2020-5-15 // Scott Xu // 2020-12-2 // Add LogWriteCallbackFun. #ifndef XOP_LOGGER_H #define XOP_LOGGER_H #include #include #include #include namespace xop { enum Priority { LOG_DEBUG, LOG_STATE, LOG_INFO, LOG_WARNING, LOG_ERROR, }; typedef void (*LogWriteCallbackFun)(Priority priority, std::string info); class Logger { public: Logger &operator=(const Logger &) = delete; Logger(const Logger &) = delete; static Logger &Instance(); virtual ~Logger(); void Init(char *pathname = nullptr); void Exit(); void SetWriteCallback(LogWriteCallbackFun writeCallback); void Log(Priority priority, const char *__file, const char *__func, int __line, const char *fmt, ...); void Log2(Priority priority, const char *fmt, ...); private: void Write(const std::string &buf); Logger(); std::mutex mutex_; std::ofstream ofs_; LogWriteCallbackFun _writeCallback{}; }; } //#ifdef _DEBUG #define LOG_DEBUG(fmt, ...) \ xop::Logger::Instance().Log(LOG_DEBUG, __FILE__, __FUNCTION__, \ __LINE__, fmt, ##__VA_ARGS__) //#else //#define LOG_DEBUG(fmt, ...) //#endif #define LOG_INFO(fmt, ...) \ xop::Logger::Instance().Log2(LOG_INFO, fmt, ##__VA_ARGS__) #define LOG_ERROR(fmt, ...) \ xop::Logger::Instance().Log(LOG_ERROR, __FILE__, __FUNCTION__, \ __LINE__, fmt, ##__VA_ARGS__) #endif ================================================ FILE: rtsp-server/net/MemoryManager.cpp ================================================ #include "MemoryManager.h" using namespace xop; void *xop::Alloc(const uint32_t size) { return MemoryManager::Instance().Alloc(size); } void xop::Free(void *ptr) { return MemoryManager::Instance().Free(ptr); } MemoryPool::MemoryPool() = default; MemoryPool::~MemoryPool() { if (memory_) { free(memory_); } } void MemoryPool::Init(const uint32_t size, const uint32_t n) { if (memory_) { return; } block_size_ = size; num_blocks_ = n; memory_ = static_cast( malloc(num_blocks_ * (block_size_ + sizeof(MemoryBlock)))); head_ = reinterpret_cast(memory_); head_->block_id = 1; head_->pool = this; head_->next = nullptr; MemoryBlock *current = head_; for (uint32_t n = 1; n < num_blocks_; n++) { auto *next = reinterpret_cast( memory_ + n * (block_size_ + sizeof(MemoryBlock))); next->block_id = n + 1; next->pool = this; next->next = nullptr; current->next = next; current = next; } } void *MemoryPool::Alloc(const uint32_t size) { std::lock_guard locker(mutex_); if (head_ != nullptr) { MemoryBlock *block = head_; head_ = head_->next; return reinterpret_cast(block) + sizeof(MemoryBlock); } return nullptr; } void MemoryPool::Free(void *ptr) { if (const auto block = reinterpret_cast( static_cast(ptr) - sizeof(MemoryBlock)); block->block_id != 0) { std::lock_guard locker(mutex_); block->next = head_; head_ = block; } } MemoryManager::MemoryManager() { memory_pools_[0].Init(4096, 50); memory_pools_[1].Init(40960, 10); memory_pools_[2].Init(102400, 5); //memory_pools_[3].Init(204800, 2); } MemoryManager::~MemoryManager() = default; MemoryManager &MemoryManager::Instance() { static MemoryManager s_mgr; return s_mgr; } void *MemoryManager::Alloc(const uint32_t size) { for (auto &memory_pool : memory_pools_) { if (size <= memory_pool.BolckSize()) { if (void *ptr = memory_pool.Alloc(size); ptr != nullptr) { return ptr; } break; } } const auto block = static_cast(malloc(size + sizeof(MemoryBlock))); block->block_id = 0; block->pool = nullptr; block->next = nullptr; return reinterpret_cast(block) + sizeof(MemoryBlock); } void MemoryManager::Free(void *ptr) { const auto block = reinterpret_cast( static_cast(ptr) - sizeof(MemoryBlock)); if (MemoryPool *pool = block->pool; pool != nullptr && block->block_id > 0) { pool->Free(ptr); } else { free(block); } } ================================================ FILE: rtsp-server/net/MemoryManager.h ================================================ #ifndef XOP_MEMMORY_MANAGER_H #define XOP_MEMMORY_MANAGER_H #include #include namespace xop { void *Alloc(uint32_t size); void Free(void *ptr); class MemoryPool; struct MemoryBlock { uint32_t block_id = 0; MemoryPool *pool = nullptr; MemoryBlock *next = nullptr; }; class MemoryPool { public: MemoryPool(); virtual ~MemoryPool(); void Init(uint32_t size, uint32_t n); void *Alloc(uint32_t size); void Free(void *ptr); size_t BolckSize() const { return block_size_; } //private: char *memory_ = nullptr; uint32_t block_size_ = 0; uint32_t num_blocks_ = 0; MemoryBlock *head_ = nullptr; std::mutex mutex_; }; class MemoryManager { public: static MemoryManager &Instance(); ~MemoryManager(); void *Alloc(uint32_t size); void Free(void *ptr); private: MemoryManager(); static constexpr int kMaxMemoryPool = 3; MemoryPool memory_pools_[kMaxMemoryPool]; }; } #endif ================================================ FILE: rtsp-server/net/Pipe.cpp ================================================ // PHZ // 2018-5-15 #include "Pipe.h" #include "SocketUtil.h" #include using namespace xop; Pipe::Pipe() { memset(pipe_fd_, 0, 2); } Pipe::~Pipe() { Close(); } bool Pipe::Create() { #if defined(__linux) || defined(__linux__) if (pipe2(pipe_fd_, O_NONBLOCK | O_CLOEXEC) < 0) { return false; } #else const TcpSocket rp(socket(AF_INET, SOCK_STREAM, 0)); const TcpSocket wp(socket(AF_INET, SOCK_STREAM, 0)); std::random_device rd; pipe_fd_[0] = rp.GetSocket(); pipe_fd_[1] = wp.GetSocket(); uint16_t port = 0; int again = 5; while (again--) { port = static_cast(rd()); if (rp.Bind("127.0.0.1", port)) { break; } } if (again == 0) { return false; } if (!rp.Listen(1)) { return false; } if (!wp.Connect("127.0.0.1", port)) { return false; } pipe_fd_[0] = rp.Accept(); //TODO if (pipe_fd_[0] < 0) { return false; } SocketUtil::SetNonBlock(pipe_fd_[0]); SocketUtil::SetNonBlock(pipe_fd_[1]); #endif return true; } int Pipe::Write(void *buf, const int len) const { #if defined(WIN32) || defined(_WIN32) return ::send(pipe_fd_[1], static_cast(buf), len, 0); #else return ::write(pipe_fd_[1], buf, len); #endif } int Pipe::Read(void *buf, const int len) const { #if defined(WIN32) || defined(_WIN32) return recv(pipe_fd_[0], static_cast(buf), len, 0); #else return ::read(pipe_fd_[0], buf, len); #endif } void Pipe::Close() const { #if defined(WIN32) || defined(_WIN32) closesocket(pipe_fd_[0]); closesocket(pipe_fd_[1]); #else ::close(pipe_fd_[0]); ::close(pipe_fd_[1]); #endif } ================================================ FILE: rtsp-server/net/Pipe.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_PIPE_H #define XOP_PIPE_H #include "TcpSocket.h" namespace xop { class Pipe { public: Pipe(); virtual ~Pipe(); bool Create(); int Write(void *buf, int len) const; int Read(void *buf, int len) const; void Close() const; SOCKET Read() const { return pipe_fd_[0]; } SOCKET Write() const { return pipe_fd_[1]; } private: SOCKET pipe_fd_[2]{}; }; } #endif ================================================ FILE: rtsp-server/net/RingBuffer.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_RING_BUFFER_H #define XOP_RING_BUFFER_H #include #include namespace xop { template class RingBuffer { public: explicit RingBuffer(unsigned capacity = 60) : capacity_(capacity), num_datas_(0), buffer_(capacity) { } virtual ~RingBuffer() {} bool Push(const T &data) { return PushData(std::forward(data)); } bool Push(T &&data) { return PushData(data); } bool Pop(T &data) { if (num_datas_ > 0) { data = std::move(buffer_[get_pos_]); Add(get_pos_); --num_datas_; return true; } return false; } bool IsFull() const { return num_datas_ == capacity_ ? true : false; } bool IsEmpty() const { return num_datas_ == 0 ? true : false; } int Size() const { return num_datas_; } private: template bool PushData(F &&data) { if (num_datas_ < capacity_) { buffer_[put_pos_] = std::forward(data); Add(put_pos_); ++num_datas_; return true; } return false; } void Add(int &pos) const { pos = pos + 1 == capacity_ ? 0 : pos + 1; } int capacity_ = 0; int put_pos_ = 0; int get_pos_ = 0; std::atomic_int num_datas_; std::vector buffer_; }; } #endif ================================================ FILE: rtsp-server/net/SelectTaskScheduler.cpp ================================================ // PHZ // 2018-5-15 #include "SelectTaskScheduler.h" #include "Timer.h" #include #include using namespace xop; #define SELECT_CTL_ADD 0 #define SELECT_CTL_MOD 1 #define SELECT_CTL_DEL 2 SelectTaskScheduler::SelectTaskScheduler(const int id) : TaskScheduler(id) { FD_ZERO(&fd_read_backup_); FD_ZERO(&fd_write_backup_); FD_ZERO(&fd_exp_backup_); this->SelectTaskScheduler::UpdateChannel(wakeup_channel_); } SelectTaskScheduler::~SelectTaskScheduler() = default; void SelectTaskScheduler::UpdateChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); if (SOCKET socket = channel->GetSocket(); channels_.find(socket) != channels_.end()) { if (channel->IsNoneEvent()) { is_fd_read_reset_ = true; is_fd_write_reset_ = true; is_fd_exp_reset_ = true; channels_.erase(socket); } else { //is_fd_read_reset_ = true; is_fd_write_reset_ = true; } } else { if (!channel->IsNoneEvent()) { channels_.emplace(socket, channel); is_fd_read_reset_ = true; is_fd_write_reset_ = true; is_fd_exp_reset_ = true; } } } void SelectTaskScheduler::RemoveChannel(const ChannelPtr &channel) { std::lock_guard lock(mutex_); if (const SOCKET fd = channel->GetSocket(); channels_.find(fd) != channels_.end()) { is_fd_read_reset_ = true; is_fd_write_reset_ = true; is_fd_exp_reset_ = true; channels_.erase(fd); } } bool SelectTaskScheduler::HandleEvent(int timeout) { if (channels_.empty()) { if (timeout <= 0) { timeout = 10; } Timer::Sleep(timeout); return true; } fd_set fd_read{}; fd_set fd_write{}; fd_set fd_exp{}; FD_ZERO(&fd_read); FD_ZERO(&fd_write); FD_ZERO(&fd_exp); bool fd_read_reset = false; bool fd_write_reset = false; bool fd_exp_reset = false; if (is_fd_read_reset_ || is_fd_write_reset_ || is_fd_exp_reset_) { if (is_fd_exp_reset_) { maxfd_ = 0; } std::lock_guard lock(mutex_); for (const auto & [fst, snd] : channels_) { const uint32_t events = snd->GetEvents(); const SOCKET fd = snd->GetSocket(); if (is_fd_read_reset_ && (events & EVENT_IN)) { FD_SET(fd, &fd_read); } if (is_fd_write_reset_ && (events & EVENT_OUT)) { FD_SET(fd, &fd_write); } if (is_fd_exp_reset_) { FD_SET(fd, &fd_exp); if (fd > maxfd_) { maxfd_ = fd; } } } fd_read_reset = is_fd_read_reset_; fd_write_reset = is_fd_write_reset_; fd_exp_reset = is_fd_exp_reset_; is_fd_read_reset_ = false; is_fd_write_reset_ = false; is_fd_exp_reset_ = false; } if (fd_read_reset) { FD_ZERO(&fd_read_backup_); memcpy(&fd_read_backup_, &fd_read, sizeof(fd_set)); } else { memcpy(&fd_read, &fd_read_backup_, sizeof(fd_set)); } if (fd_write_reset) { FD_ZERO(&fd_write_backup_); memcpy(&fd_write_backup_, &fd_write, sizeof(fd_set)); } else { memcpy(&fd_write, &fd_write_backup_, sizeof(fd_set)); } if (fd_exp_reset) { FD_ZERO(&fd_exp_backup_); memcpy(&fd_exp_backup_, &fd_exp, sizeof(fd_set)); } else { memcpy(&fd_exp, &fd_exp_backup_, sizeof(fd_set)); } if (timeout <= 0) { timeout = 10; } #if defined(WIN32) || defined(_WIN32) const timeval tv = {timeout / 1000, timeout % 1000 * 1000}; #else timeval tv = {timeout / 1000, timeout % 1000 * 1000}; #endif const int ret = select(static_cast(maxfd_) + 1, &fd_read, &fd_write, &fd_exp, &tv); if (ret < 0) { #if defined(WIN32) || defined(_WIN32) #else if (errno == EINTR) { return true; } #endif return false; } std::forward_list> event_list; if (ret > 0) { std::lock_guard lock(mutex_); for (const auto & [fst, snd] : channels_) { uint32_t events = 0; const SOCKET socket = snd->GetSocket(); if (FD_ISSET(socket, &fd_read)) { events |= EVENT_IN; } if (FD_ISSET(socket, &fd_write)) { events |= EVENT_OUT; } if (FD_ISSET(socket, &fd_exp)) { events |= EVENT_HUP; // close } if (events != 0) { event_list.emplace_front(snd, events); } } } for (const auto & [fst, snd] : event_list) { fst->HandleEvent(snd); } return true; } ================================================ FILE: rtsp-server/net/SelectTaskScheduler.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_SELECT_TASK_SCHEDULER_H #define XOP_SELECT_TASK_SCHEDULER_H #include "TaskScheduler.h" #include "Socket.h" #include #include #if defined(WIN32) || defined(_WIN32) #else #include #include #include #include #endif namespace xop { class SelectTaskScheduler : public TaskScheduler { public: explicit SelectTaskScheduler(int id = 0); ~SelectTaskScheduler() override; void UpdateChannel(const ChannelPtr &channel) override; void RemoveChannel(const ChannelPtr &channel) override; bool HandleEvent(int timeout) override; private: fd_set fd_read_backup_{}; fd_set fd_write_backup_{}; fd_set fd_exp_backup_{}; SOCKET maxfd_ = 0; bool is_fd_read_reset_ = false; bool is_fd_write_reset_ = false; bool is_fd_exp_reset_ = false; std::mutex mutex_; std::unordered_map channels_; }; } #endif ================================================ FILE: rtsp-server/net/Socket.h ================================================ // PHZ // 2018-5-15 // Scott Xu // 2020-12-2 Add IPv6 Support. #ifndef XOP_SOCKET_H #define XOP_SOCKET_H #if defined(WIN32) || defined(_WIN32) #define FD_SETSIZE 1024 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #include #include #include #include #define SHUT_RD 0 #define SHUT_WR 1 #define SHUT_RDWR 2 #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux) || defined(__linux__) #include #include #endif #define SOCKET int #define INVALID_SOCKET (-1) #define SOCKET_ERROR (-1) #define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */ #define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */ #endif #include #include #endif // _XOP_SOCKET_H ================================================ FILE: rtsp-server/net/SocketUtil.cpp ================================================ // PHZ // 2018-5-15 // Scott Xu // 2020-12-2 Add IPv6 Support. #include "SocketUtil.h" #include "Socket.h" #include using namespace xop; bool SocketUtil::Bind(const SOCKET sockfd, const std::string &ip, const uint16_t port, const bool ipv6) { sockaddr *psockaddr; socklen_t addrlen; if (ipv6) { sockaddr_in6 addr = {0}; addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); inet_pton(AF_INET6, ip.c_str(), &addr.sin6_addr); psockaddr = reinterpret_cast(&addr); addrlen = sizeof(addr); } else { sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &addr.sin_addr); psockaddr = reinterpret_cast(&addr); addrlen = sizeof(addr); } if (bind(sockfd, psockaddr, addrlen) == SOCKET_ERROR) { return false; } return true; } void SocketUtil::SetNonBlock(const SOCKET fd) { #if defined(WIN32) || defined(_WIN32) unsigned long on = 1; ioctlsocket(fd, FIONBIO, &on); #else int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); #endif } void SocketUtil::SetBlock(const SOCKET fd, const int write_timeout) { #if defined(WIN32) || defined(_WIN32) unsigned long on = 0; ioctlsocket(fd, FIONBIO, &on); #else int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)); #endif if (write_timeout > 0) { #ifdef SO_SNDTIMEO #if defined(WIN32) || defined(_WIN32) auto ms = static_cast(write_timeout); setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast(&ms), sizeof(unsigned long)); #else struct timeval tv = {write_timeout / 1000, (write_timeout % 1000) * 1000}; setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof tv); #endif #endif } } void SocketUtil::SetReuseAddr(const SOCKET sockfd) { constexpr int on = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&on), sizeof on); } void SocketUtil::SetReusePort(const SOCKET sockfd) { #ifdef SO_REUSEPORT int on = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char *)&on, sizeof(on)); #endif } void SocketUtil::SetNoDelay(const SOCKET sockfd) { #ifdef TCP_NODELAY int on = 1; int ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&on), sizeof on); #endif } void SocketUtil::SetKeepAlive(const SOCKET sockfd) { int on = 1; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&on), sizeof on); } void SocketUtil::SetNoSigpipe(const SOCKET sockfd) { #ifdef SO_NOSIGPIPE int on = 1; setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (char *)&on, sizeof(on)); #endif } void SocketUtil::SetSendBufSize(const SOCKET sockfd, int size) { setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&size), sizeof size); } void SocketUtil::SetRecvBufSize(const SOCKET sockfd, int size) { setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&size), sizeof size); } std::string SocketUtil::GetPeerIp(const SOCKET sockfd, const bool ipv6) { if (ipv6) { sockaddr_in6 addr = {0}; char str[INET6_ADDRSTRLEN] = "::0"; if (GetPeerAddr6(sockfd, &addr) == 0) inet_ntop(AF_INET6, &addr.sin6_addr, str, sizeof str); return str; } sockaddr_in addr = {0}; char str[INET_ADDRSTRLEN] = "0.0.0.0"; if (GetPeerAddr(sockfd, &addr) == 0) inet_ntop(AF_INET, &addr.sin_addr, str, sizeof str); return str; } std::string SocketUtil::GetSocketIp(const SOCKET sockfd, const bool ipv6) { if (ipv6) { sockaddr_in6 addr = {0}; char str[INET6_ADDRSTRLEN] = "::1"; if (GetSocketAddr6(sockfd, &addr) == 0) inet_ntop(AF_INET6, &addr.sin6_addr, str, sizeof str); return str; } sockaddr_in addr = {0}; char str[INET_ADDRSTRLEN] = "127.0.0.1"; if (GetSocketAddr(sockfd, &addr) == 0) inet_ntop(AF_INET, &addr.sin_addr, str, sizeof str); return str; } uint16_t SocketUtil::GetPeerPort(const SOCKET sockfd, const bool ipv6) { if (ipv6) { sockaddr_in6 addr = {0}; if (GetPeerAddr6(sockfd, &addr) == 0) return ntohs(addr.sin6_port); } sockaddr_in addr = {0}; if (GetPeerAddr(sockfd, &addr) == 0) return ntohs(addr.sin_port); return 0; } int SocketUtil::GetPeerAddr(const SOCKET sockfd, sockaddr_in *addr) { socklen_t addrlen = sizeof(struct sockaddr_in); return getpeername(sockfd, reinterpret_cast(addr), &addrlen); } int SocketUtil::GetPeerAddr6(const SOCKET sockfd, sockaddr_in6 *addr) { socklen_t addrlen = sizeof(struct sockaddr_in6); return getpeername(sockfd, reinterpret_cast(addr), &addrlen); } int SocketUtil::GetSocketAddr(const SOCKET sockfd, sockaddr_in *addr) { socklen_t addrlen = sizeof(struct sockaddr_in); return getsockname(sockfd, reinterpret_cast(addr), &addrlen); } int SocketUtil::GetSocketAddr6(const SOCKET sockfd, sockaddr_in6 *addr) { socklen_t addrlen = sizeof(struct sockaddr_in6); return getsockname(sockfd, reinterpret_cast(addr), &addrlen); } void SocketUtil::Close(const SOCKET sockfd) { #if defined(WIN32) || defined(_WIN32) ::closesocket(sockfd); #else ::close(sockfd); #endif } bool SocketUtil::Connect(const SOCKET sockfd, const std::string &ip, const uint16_t port, const int timeout, const bool ipv6) { bool is_connected = true; if (timeout > 0) { SetNonBlock(sockfd); } sockaddr *psockaddr; socklen_t addrlen; if (ipv6) { sockaddr_in6 addr = {0}; addr.sin6_family = AF_INET6; addr.sin6_port = htons(port); inet_pton(AF_INET6, ip.c_str(), &addr.sin6_addr); psockaddr = reinterpret_cast(&addr); addrlen = sizeof(addr); } else { sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &addr.sin_addr); psockaddr = reinterpret_cast(&addr); addrlen = sizeof(addr); } if (connect(sockfd, psockaddr, addrlen) == SOCKET_ERROR) { if (timeout > 0) { is_connected = false; fd_set fd_write{}; FD_ZERO(&fd_write); FD_SET(sockfd, &fd_write); #if defined(WIN32) || defined(_WIN32) const timeval tv = {timeout / 1000, timeout % 1000 * 1000}; #else timeval tv = {timeout / 1000, timeout % 1000 * 1000}; #endif select(static_cast(sockfd) + 1, nullptr, &fd_write, nullptr, &tv); if (FD_ISSET(sockfd, &fd_write)) { is_connected = true; } SetBlock(sockfd); } else { is_connected = false; } } return is_connected; } bool SocketUtil::IsIpv6Address(const std::string &ip) { in6_addr addr6{}; return inet_pton(AF_INET6, ip.c_str(), &addr6) > 0; } bool SocketUtil::IsIpv6Socket(const SOCKET sockfd) { sockaddr_in6 addr = {0}; socklen_t addrlen = sizeof addr; getsockname(sockfd, reinterpret_cast(&addr), &addrlen); if (addr.sin6_family == AF_INET6) return true; return false; } ================================================ FILE: rtsp-server/net/SocketUtil.h ================================================ // PHZ // 2018-5-15 // Scott Xu // 2020-12-2 Add IPv6 Support. #ifndef XOP_SOCKET_UTIL_H #define XOP_SOCKET_UTIL_H #include "Socket.h" #include namespace xop { class SocketUtil { public: static bool Bind(SOCKET sockfd, const std::string &ip, uint16_t port, bool ipv6 = false); static void SetNonBlock(SOCKET fd); static void SetBlock(SOCKET fd, int write_timeout = 0); static void SetReuseAddr(SOCKET fd); static void SetReusePort(SOCKET sockfd); static void SetNoDelay(SOCKET sockfd); static void SetKeepAlive(SOCKET sockfd); static void SetNoSigpipe(SOCKET sockfd); static void SetSendBufSize(SOCKET sockfd, int size); static void SetRecvBufSize(SOCKET sockfd, int size); static std::string GetPeerIp(SOCKET sockfd, bool ipv6 = false); static std::string GetSocketIp(SOCKET sockfd, bool ipv6 = false); static uint16_t GetPeerPort(SOCKET sockfd, bool ipv6 = false); static int GetPeerAddr(SOCKET sockfd, sockaddr_in *addr); static int GetPeerAddr6(SOCKET sockfd, sockaddr_in6 *addr); static int GetSocketAddr(SOCKET sockfd, sockaddr_in *addr); static int GetSocketAddr6(SOCKET sockfd, sockaddr_in6 *addr); static void Close(SOCKET sockfd); static bool Connect(SOCKET sockfd, const std::string &ip, uint16_t port, int timeout = 0, bool ipv6 = false); static bool IsIpv6Address(const std::string &ip); static bool IsIpv6Socket(SOCKET sockfd); }; } #endif // _SOCKET_UTIL_H ================================================ FILE: rtsp-server/net/TaskScheduler.cpp ================================================ #include "TaskScheduler.h" #if defined(WIN32) || defined(_WIN32) #else #include #endif using namespace xop; TaskScheduler::TaskScheduler(const int id) : id_(id), is_shutdown_(false), wakeup_pipe_(new Pipe()), trigger_events_(new xop::RingBuffer(kMaxTriggetEvents)) { static std::once_flag flag; std::call_once(flag, [] { #if defined(WIN32) || defined(_WIN32) WSADATA wsa_data; if (WSAStartup(MAKEWORD(2, 2), &wsa_data)) { WSACleanup(); } #endif }); if (wakeup_pipe_->Create()) { wakeup_channel_.reset(new Channel(wakeup_pipe_->Read())); wakeup_channel_->EnableReading(); wakeup_channel_->SetReadCallback([this]() { this->Wake(); }); } } TaskScheduler::~TaskScheduler() = default; void TaskScheduler::Start() { #if defined(WIN32) || defined(_WIN32) #else signal(SIGPIPE, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGUSR1, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGKILL, SIG_IGN); #endif is_shutdown_ = false; while (!is_shutdown_) { this->HandleTriggerEvent(); this->timer_queue_.HandleTimerEvent(); const int64_t timeout = this->timer_queue_.GetTimeRemaining(); this->HandleEvent(static_cast(timeout)); } } void TaskScheduler::Stop() { is_shutdown_ = true; char event = kTriggetEvent; wakeup_pipe_->Write(&event, 1); } TimerId TaskScheduler::AddTimer(const TimerEvent &timerEvent, const uint32_t msec) { const TimerId id = timer_queue_.AddTimer(timerEvent, msec); return id; } void TaskScheduler::RemoveTimer(const TimerId timerId) { timer_queue_.RemoveTimer(timerId); } bool TaskScheduler::AddTriggerEvent(TriggerEvent callback) { if (trigger_events_->Size() < kMaxTriggetEvents) { std::lock_guard lock(mutex_); char event = kTriggetEvent; trigger_events_->Push(std::move(callback)); wakeup_pipe_->Write(&event, 1); return true; } return false; } void TaskScheduler::Wake() const { char event[10] = {0}; while (wakeup_pipe_->Read(event, 10) > 0) ; } void TaskScheduler::HandleTriggerEvent() const { do { if (TriggerEvent callback; trigger_events_->Pop(callback)) { callback(); } } while (trigger_events_->Size() > 0); } ================================================ FILE: rtsp-server/net/TaskScheduler.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_TASK_SCHEDULER_H #define XOP_TASK_SCHEDULER_H #include "Channel.h" #include "Pipe.h" #include "Timer.h" #include "RingBuffer.h" namespace xop { typedef std::function TriggerEvent; class TaskScheduler { public: explicit TaskScheduler(int id = 1); virtual ~TaskScheduler(); void Start(); void Stop(); TimerId AddTimer(const TimerEvent &timerEvent, uint32_t msec); void RemoveTimer(TimerId timerId); bool AddTriggerEvent(TriggerEvent callback); virtual void UpdateChannel(const ChannelPtr &channel) = 0; virtual void RemoveChannel(const ChannelPtr &channel) = 0; virtual bool HandleEvent(int timeout) = 0; int GetId() const { return id_; } protected: void Wake() const; void HandleTriggerEvent() const; int id_ = 0; std::atomic_bool is_shutdown_; std::unique_ptr wakeup_pipe_; std::shared_ptr wakeup_channel_; std::unique_ptr> trigger_events_; std::mutex mutex_; TimerQueue timer_queue_; static constexpr char kTriggetEvent = 1; static constexpr char kTimerEvent = 2; static constexpr int kMaxTriggetEvents = 50000; }; } #endif ================================================ FILE: rtsp-server/net/TcpConnection.cpp ================================================ //Scott Xu //2020-12-6 Add IPv6 support. #include "TcpConnection.h" #include "SocketUtil.h" using namespace xop; TcpConnection::TcpConnection( const SOCKET sockfd, std::shared_ptr task_scheduler) : read_buffer_(new BufferReader), write_buffer_(new BufferWriter(500)), is_closed_(false), task_scheduler_(std::move(task_scheduler)), channel_(new Channel(sockfd)), ipv6_(SocketUtil::IsIpv6Socket(sockfd)) { channel_->SetReadCallback([this] { this->HandleRead(); }); channel_->SetWriteCallback([this] { this->HandleWrite(); }); channel_->SetCloseCallback([this] { this->HandleClose(); }); channel_->SetErrorCallback([this] { this->HandleError(); }); SocketUtil::SetNonBlock(sockfd); SocketUtil::SetSendBufSize(sockfd, 100 * 1024); SocketUtil::SetKeepAlive(sockfd); channel_->EnableReading(); task_scheduler_->UpdateChannel(channel_); } TcpConnection::~TcpConnection() { if (const SOCKET fd = channel_->GetSocket(); fd > 0) { SocketUtil::Close(fd); } } void TcpConnection::Send(const std::shared_ptr &data, const size_t size) { if (!is_closed_) { mutex_.lock(); write_buffer_->Append(data, size); mutex_.unlock(); this->HandleWrite(); } } void TcpConnection::Send(const char *data, const size_t size) { if (!is_closed_) { mutex_.lock(); write_buffer_->Append(data, size); mutex_.unlock(); this->HandleWrite(); } } void TcpConnection::Disconnect() { std::lock_guard lock(mutex_); task_scheduler_->AddTriggerEvent([this] { this->Close(); }); } void TcpConnection::HandleRead() { { std::lock_guard lock(mutex_); if (is_closed_) { return; } if (const int ret = read_buffer_->Read(channel_->GetSocket()); ret <= 0) { this->Close(); return; } } if (read_cb_) { if (const bool ret = read_cb_(weak_from_this(), *read_buffer_); !ret) { std::lock_guard lock(mutex_); this->Close(); } } } void TcpConnection::HandleWrite() { if (is_closed_) { return; } //std::lock_guard lock(mutex_); if (!mutex_.try_lock()) { return; } bool empty; //do //{ if (const int ret = write_buffer_->Send(channel_->GetSocket()); ret < 0) { this->Close(); mutex_.unlock(); return; } empty = write_buffer_->IsEmpty(); //} while (false); if (empty) { if (channel_->IsWriting()) { channel_->DisableWriting(); task_scheduler_->UpdateChannel(channel_); } } else if (!channel_->IsWriting()) { channel_->EnableWriting(); task_scheduler_->UpdateChannel(channel_); } mutex_.unlock(); } void TcpConnection::Close() { if (!is_closed_) { is_closed_ = true; task_scheduler_->RemoveChannel(channel_); if (close_cb_) { close_cb_(weak_from_this()); } if (disconnect_cb_) { disconnect_cb_(weak_from_this()); } } } void TcpConnection::HandleClose() { std::lock_guard lock(mutex_); this->Close(); } void TcpConnection::HandleError() { std::lock_guard lock(mutex_); this->Close(); } ================================================ FILE: rtsp-server/net/TcpConnection.h ================================================ //Scott Xu //2020-12-6 Add IPv6 support. #ifndef XOP_TCP_CONNECTION_H #define XOP_TCP_CONNECTION_H #include #include #include "TaskScheduler.h" #include "BufferReader.h" #include "BufferWriter.h" #include "Channel.h" #include "SocketUtil.h" namespace xop { class TcpConnection : public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; using Weak = std::weak_ptr; using DisconnectCallback = std::function; using CloseCallback = std::function; using ReadCallback = std::function; TcpConnection(SOCKET sockfd, std::shared_ptr task_scheduler); virtual ~TcpConnection(); std::shared_ptr GetTaskScheduler() const { return task_scheduler_; } void SetReadCallback(const ReadCallback &cb) { read_cb_ = cb; } void SetCloseCallback(const CloseCallback &cb) { close_cb_ = cb; } void Send(const std::shared_ptr &data, size_t size); void Send(const char *data, size_t size); void Disconnect(); bool IsClosed() const { return is_closed_; } bool IsIpv6() const { return ipv6_; } SOCKET GetSocket() const { return channel_->GetSocket(); } uint16_t GetPort() const { return SocketUtil::GetPeerPort(channel_->GetSocket(), ipv6_); } std::string GetIp() const { return SocketUtil::GetPeerIp(channel_->GetSocket(), ipv6_); } protected: friend class TcpServer; virtual void HandleRead(); virtual void HandleWrite(); virtual void HandleClose(); virtual void HandleError(); void SetDisconnectCallback(const DisconnectCallback &cb) { disconnect_cb_ = cb; } std::unique_ptr read_buffer_; std::unique_ptr write_buffer_; std::atomic_bool is_closed_; private: void Close(); std::shared_ptr task_scheduler_; std::shared_ptr channel_; std::mutex mutex_; DisconnectCallback disconnect_cb_; CloseCallback close_cb_; ReadCallback read_cb_; bool ipv6_; }; } #endif ================================================ FILE: rtsp-server/net/TcpServer.cpp ================================================ // Scott Xu // 2020-12-04 Add multiple socket support. #include "TcpServer.h" #include "Acceptor.h" #include "EventLoop.h" #include using namespace xop; using namespace std; TcpServer::TcpServer(EventLoop *event_loop) : event_loop_(event_loop) //, port_(0) //, acceptor_(new Acceptor(event_loop_)) //, is_started_(false) { } TcpServer::~TcpServer() { TcpServer::Stop(); } bool TcpServer::Start(const std::string &ip, const uint16_t port) { /*Stop(); if (!is_started_) { if (acceptor_->Listen(ip, port) < 0) { return false; } port_ = port; ip_ = ip; is_started_ = true; return true; }*/ //return false; auto acceptor = std::make_unique(event_loop_); acceptor->SetNewConnectionCallback([this](SOCKET sockfd) { if (const auto conn = this->OnConnect(sockfd)) { this->AddConnection(sockfd, conn); conn->SetDisconnectCallback([this](const TcpConnection:: Weak &conn) { const auto scheduler = conn.lock()->GetTaskScheduler(); if (SOCKET sockfd = conn.lock()->GetSocket(); !scheduler->AddTriggerEvent([this, sockfd] { this->RemoveConnection(sockfd); })) { scheduler->AddTimer( [this, sockfd]() { this->RemoveConnection( sockfd); return false; }, 100); } }); } }); if (acceptor->Listen(ip, port) < 0) return false; acceptors_.push_back(std::move(acceptor)); return true; } void TcpServer::Stop() { /*if (is_started_) { mutex_.lock(); for (auto iter : connections_) { iter.second->Disconnect(); } mutex_.unlock(); acceptor_->Close(); is_started_ = false; while (1) { Timer::Sleep(1); if (connections_.empty()) { break; } } }*/ if (acceptors_.empty()) return; mutex_.lock(); for (const auto iter : connections_) iter.second->Disconnect(); mutex_.unlock(); for (auto it = acceptors_.begin(); it != acceptors_.end(); ++it) (*it)->Close(); while (!connections_.empty()) Timer::Sleep(10); acceptors_.clear(); return; } TcpConnection::Ptr TcpServer::OnConnect(const SOCKET sockfd) { return std::make_shared(sockfd, event_loop_->GetTaskScheduler()); } void TcpServer::AddConnection(const SOCKET sockfd, TcpConnection::Ptr tcpConn) { std::lock_guard locker(mutex_); connections_.emplace(sockfd, tcpConn); } void TcpServer::RemoveConnection(const SOCKET sockfd) { std::lock_guard locker(mutex_); connections_.erase(sockfd); } ================================================ FILE: rtsp-server/net/TcpServer.h ================================================ // PHZ // 2018-11-10 // Scott Xu // 2020-12-04 Add multiple socket support. #ifndef XOP_TCPSERVER_H #define XOP_TCPSERVER_H #include #include #include #include #include "EventLoop.h" #include "TcpConnection.h" namespace xop { class Acceptor; class EventLoop; class TcpServer { public: explicit TcpServer(EventLoop *event_loop); virtual ~TcpServer(); virtual bool Start(const std::string &ip, uint16_t port); virtual void Stop(); /*std::string GetIPAddress() const { return ip_; } uint16_t GetPort() const { return port_; }*/ protected: virtual TcpConnection::Ptr OnConnect(SOCKET sockfd); virtual void AddConnection(SOCKET sockfd, TcpConnection::Ptr tcpConn); virtual void RemoveConnection(SOCKET sockfd); EventLoop *event_loop_; //uint16_t port_; //std::string ip_; std::vector> acceptors_; //bool is_started_; std::mutex mutex_; std::unordered_map> connections_; }; } #endif ================================================ FILE: rtsp-server/net/TcpSocket.cpp ================================================ // PHZ // 2018-5-15 // Scott Xu // 2020-12-2 Add IPv6 Support. #include "TcpSocket.h" #include "Socket.h" #include "SocketUtil.h" #include "Logger.h" using namespace xop; TcpSocket::TcpSocket(const SOCKET sockfd, const bool ipv6) : sockfd_(sockfd), ipv6_(ipv6) { } TcpSocket::~TcpSocket() = default; SOCKET TcpSocket::Create(const bool ipv6) { ipv6_ = ipv6; sockfd_ = ::socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_STREAM, 0); //TODO return sockfd_; } bool TcpSocket::Bind(const std::string &ip, const uint16_t port) const { if (!SocketUtil::Bind(sockfd_, ip, port, ipv6_)) { LOG_ERROR(" bind <%s:%u> failed.\n", sockfd_, ip.c_str(), port); return false; } return true; } bool TcpSocket::Listen(const int backlog) const { if (::listen(sockfd_, backlog) == SOCKET_ERROR) { LOG_ERROR(" listen failed.\n", sockfd_); return false; } return true; } SOCKET TcpSocket::Accept() const { sockaddr *psockaddr; socklen_t addrlen; if (ipv6_) { sockaddr_in6 addr = {0}; addrlen = sizeof addr; psockaddr = reinterpret_cast(&addr); } else { sockaddr_in addr = {0}; addrlen = sizeof addr; psockaddr = reinterpret_cast(&addr); } const SOCKET socket_fd = accept(sockfd_, psockaddr, &addrlen); return socket_fd; } bool TcpSocket::Connect(const std::string &ip, const uint16_t port, const int timeout) const { if (!SocketUtil::Connect(sockfd_, ip, port, timeout, ipv6_)) { LOG_ERROR(" connect failed.\n", sockfd_); return false; } return true; } void TcpSocket::Close() { #if defined(WIN32) || defined(_WIN32) closesocket(sockfd_); #else ::close(sockfd_); #endif sockfd_ = 0; } void TcpSocket::ShutdownWrite() { shutdown(sockfd_, SHUT_WR); sockfd_ = 0; } ================================================ FILE: rtsp-server/net/TcpSocket.h ================================================ // PHZ // 2018-5-15 // Scott Xu // 2020-12-2 Add IPv6 Support. #ifndef XOP_TCP_SOCKET_H #define XOP_TCP_SOCKET_H #include #include #include "Socket.h" namespace xop { class TcpSocket { public: TcpSocket(SOCKET sockfd = -1, bool ipv6 = false); virtual ~TcpSocket(); SOCKET Create(bool ipv6 = false); bool Bind(const std::string &ip, uint16_t port) const; bool Listen(int backlog) const; SOCKET Accept() const; bool Connect(const std::string &ip, uint16_t port, int timeout = 0) const; void Close(); void ShutdownWrite(); SOCKET GetSocket() const { return sockfd_; } bool IsIpv6Socket() const { return ipv6_; } private: SOCKET sockfd_ = 0; bool ipv6_; }; } #endif ================================================ FILE: rtsp-server/net/ThreadSafeQueue.h.bak ================================================ #ifndef THREAD_SAFE_QUEUE_H #define THREAD_SAFE_QUEUE_H #include #include #include namespace xop { template class ThreadSafeQueue { public: ThreadSafeQueue() { } ThreadSafeQueue(ThreadSafeQueue const& other) { std::lock_guard lock(other._mutex); _dataQueue = other._dataQueue; } ~ThreadSafeQueue() { } void push(T value) { std::lock_guard lock(_mutex); _dataQueue.push(value); _dataCond.notify_one(); } bool waitAndPop(T& value) { std::unique_lock lock(_mutex); _dataCond.wait(lock); if (_dataQueue.empty()) { return false; } value = _dataQueue.front(); _dataQueue.pop(); return true; } std::shared_ptr waitAndPop() { std::unique_lock lock(_mutex); _dataCond.wait(lock); if (_dataQueue.empty()) { return nullptr; } std::shared_ptr res(std::make_shared(_dataQueue.front())); _dataQueue.pop(); return res; } bool tryPop(T& value) { std::lock_guard lock(_mutex); if(_dataQueue.empty()) return false; value = _dataQueue.front(); _dataQueue.pop(); return true; } std::shared_ptr tryPop() { std::lock_guard lock(_mutex); if(_dataQueue.empty()) return std::shared_ptr(); std::shared_ptr res(std::make_shared(_dataQueue.front())); _dataQueue.pop(); return res; } size_t size() const { std::lock_guard lock(_mutex); return _dataQueue.size(); } bool empty() const { std::lock_guard lock(_mutex); return _dataQueue.empty(); } void clear() { std::lock_guard lock(_mutex); std::queue empty; _dataQueue.swap(empty); } void wake() { _dataCond.notify_one(); } private: mutable std::mutex _mutex; std::queue _dataQueue; std::condition_variable _dataCond; }; } #endif ================================================ FILE: rtsp-server/net/Timer.cpp ================================================ #include "Timer.h" using namespace xop; using namespace std; using namespace std::chrono; TimerId TimerQueue::AddTimer(const TimerEvent &event, uint32_t ms) { std::lock_guard locker(mutex_); const int64_t timeout = GetTimeNow(); TimerId timer_id = ++last_timer_id_; auto timer = make_shared(event, ms); timer->SetNextTimeout(timeout); timers_.emplace(timer_id, timer); events_.emplace(std::pair(timeout + ms, timer_id), std::move(timer)); return timer_id; } void TimerQueue::RemoveTimer(TimerId timerId) { std::lock_guard locker(mutex_); if (const auto iter = timers_.find(timerId); iter != timers_.end()) { int64_t timeout = iter->second->getNextTimeout(); events_.erase(std::pair(timeout, timerId)); timers_.erase(timerId); } } int64_t TimerQueue::GetTimeNow() const { const auto time_point = steady_clock::now(); return duration_cast(time_point.time_since_epoch()) .count(); } int64_t TimerQueue::GetTimeRemaining() { std::lock_guard locker(mutex_); if (timers_.empty()) { return -1; } int64_t msec = events_.begin()->first.first - GetTimeNow(); if (msec < 0) { msec = 0; } return msec; } void TimerQueue::HandleTimerEvent() { if (!timers_.empty()) { std::lock_guard locker(mutex_); const int64_t timePoint = GetTimeNow(); while (!timers_.empty() && events_.begin()->first.first <= timePoint) { TimerId timerId = events_.begin()->first.second; if (const bool flag = events_.begin()->second->event_callback_(); flag) { events_.begin()->second->SetNextTimeout( timePoint); auto timerPtr = events_.begin()->second; events_.erase(events_.begin()); events_.emplace( std::pair(timerPtr->getNextTimeout(), timerId), timerPtr); } else { events_.erase(events_.begin()); timers_.erase(timerId); } } } } ================================================ FILE: rtsp-server/net/Timer.h ================================================ // PHZ // 2018-5-15 #ifndef _XOP_TIMER_H #define _XOP_TIMER_H #include #include #include #include #include #include #include #include #include namespace xop { typedef std::function TimerEvent; typedef uint32_t TimerId; class Timer { public: Timer(TimerEvent event, const uint32_t msec) : event_callback_(std::move(event)), interval_(msec) { if (interval_ == 0) { interval_ = 1; } } static void Sleep(const uint32_t msec) { std::this_thread::sleep_for(std::chrono::milliseconds(msec)); } void SetEventCallback(const TimerEvent &event) { event_callback_ = event; } void Start(const int64_t microseconds, const bool repeat = false) { is_repeat_ = repeat; auto time_begin = std::chrono::high_resolution_clock::now(); int64_t elapsed = 0; do { std::this_thread::sleep_for(std::chrono::microseconds( microseconds - elapsed)); time_begin = std::chrono::high_resolution_clock::now(); if (event_callback_) { event_callback_(); } elapsed = std::chrono::duration_cast< std::chrono::microseconds>( std::chrono::high_resolution_clock::now() - time_begin) .count(); if (elapsed < 0) { elapsed = 0; } } while (is_repeat_); } void Stop() { is_repeat_ = false; } private: friend class TimerQueue; void SetNextTimeout(const int64_t time_point) { next_timeout_ = time_point + interval_; } int64_t getNextTimeout() const { return next_timeout_; } bool is_repeat_ = false; TimerEvent event_callback_ = [] { return false; }; uint32_t interval_ = 0; int64_t next_timeout_ = 0; }; class TimerQueue { public: TimerId AddTimer(const TimerEvent &event, uint32_t msec); void RemoveTimer(TimerId timerId); int64_t GetTimeRemaining(); void HandleTimerEvent(); private: int64_t GetTimeNow() const; std::mutex mutex_; std::unordered_map> timers_; std::map, std::shared_ptr> events_; uint32_t last_timer_id_ = 0; }; } #endif ================================================ FILE: rtsp-server/net/Timestamp.cpp ================================================ #include "Timestamp.h" #include #include using namespace xop; using namespace std; using namespace std::chrono; std::string Timestamp::Localtime() { std::ostringstream stream; const auto now = system_clock::now(); const time_t tt = system_clock::to_time_t(now); #if defined(WIN32) || defined(_WIN32) tm tm{}; localtime_s(&tm, &tt); stream << std::put_time(&tm, "%F %T"); #else char buffer[200] = {0}; std::string timeString; std::strftime(buffer, 200, "%F %T", std::localtime(&tt)); stream << buffer; #endif return stream.str(); } ================================================ FILE: rtsp-server/net/Timestamp.h ================================================ // PHZ // 2018-5-15 #ifndef XOP_TIMESTAMP_H #define XOP_TIMESTAMP_H #include #include #include #include #include namespace xop { class Timestamp { public: Timestamp() : begin_time_point_(std::chrono::high_resolution_clock::now()) { } void reset() { begin_time_point_ = std::chrono::high_resolution_clock::now(); } int64_t Elapsed() const { return std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - begin_time_point_) .count(); } static std::string Localtime(); private: std::chrono::time_point begin_time_point_; }; } #endif ================================================ FILE: rtsp-server/xop/AACSource.cpp ================================================ // PHZ // 2018-5-16 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "AACSource.h" //#include #include #include #include #include #include //#include #if defined(WIN32) || defined(_WIN32) #else #include #endif using namespace xop; using namespace std; AACSource::AACSource(const uint32_t samplerate, const uint8_t channels, const bool has_adts) : samplerate_(samplerate), channels_(channels), has_adts_(has_adts) { payload_ = 97; media_type_ = MediaType::AAC; clock_rate_ = samplerate; } AACSource *AACSource::CreateNew(const uint32_t samplerate, const uint8_t channels, const bool has_adts) { return new AACSource(samplerate, channels, has_adts); } AACSource::~AACSource() = default; string AACSource::GetMediaDescription(const uint16_t port) { char buf[100]; snprintf(buf, sizeof(buf), "m=audio %hu RTP/AVP 97", port); // \r\nb=AS:64 return buf; } static array samplingFrequencyTable = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 0, 0, 0 /*reserved */ }; string AACSource::GetAttribute() // RFC 3640 { uint8_t samplingFrequencyIndex = 0; for (const auto samplingFrequency : samplingFrequencyTable) { if (samplingFrequency == samplerate_) break; samplingFrequencyIndex++; } constexpr uint8_t profile = 1; if (samplingFrequencyIndex == samplingFrequencyTable.size()) return ""; // error const auto *rtpmap_fmt = "a=rtpmap:97 MPEG4-GENERIC/%u/%u\r\n"; const auto *fmtp_fmt = "a=fmtp:97 profile-level-id=1;" "mode=AAC-hbr;" "sizelength=13;indexlength=3;indexdeltalength=3;" "config=%02X%02X"; const size_t buf_size = snprintf(nullptr, 0, rtpmap_fmt, samplerate_, channels_) + strlen(fmtp_fmt); auto buf = vector(buf_size); const size_t rtpmap_format_size = snprintf(buf.data(), buf_size, rtpmap_fmt, samplerate_, channels_); const array audioSpecificConfig = { static_cast((profile + 1) << 3 | samplingFrequencyIndex >> 1), static_cast(samplingFrequencyIndex << 7 | channels_ << 3)}; snprintf(buf.data() + rtpmap_format_size, buf_size - rtpmap_format_size, fmtp_fmt, audioSpecificConfig[0], audioSpecificConfig[1]); return buf.data(); } bool AACSource::HandleFrame(const MediaChannelId channel_id, const AVFrame frame) { if (frame.size > (MAX_RTP_PAYLOAD_SIZE - AU_SIZE)) { return false; } size_t adts_size = 0; if (has_adts_) { adts_size = ADTS_SIZE; } const uint8_t *frame_buf = frame.buffer.get() + adts_size; size_t frame_size = frame.size - adts_size; const char AU[AU_SIZE] = {0x00, 0x10, static_cast((frame_size & 0x1fe0) >> 5), static_cast((frame_size & 0x1f) << 3)}; RtpPacket rtp_pkt; rtp_pkt.type = FrameType::AUDIO_FRAME; rtp_pkt.timestamp = frame.timestamp; rtp_pkt.size = static_cast(frame_size) + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + AU_SIZE; rtp_pkt.last = 1; uint8_t *rtp_pkt_data = rtp_pkt.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE; *(rtp_pkt_data++) = AU[0]; *(rtp_pkt_data++) = AU[1]; *(rtp_pkt_data++) = AU[2]; *(rtp_pkt_data++) = AU[3]; memcpy(rtp_pkt_data, frame_buf, frame_size); if (send_frame_callback_) { return send_frame_callback_(channel_id, rtp_pkt); //TODO } return true; } uint32_t AACSource::GetTimestamp(const uint32_t sampleRate) { //auto time_point = chrono::time_point_cast(chrono::high_resolution_clock::now()); //return (uint32_t)(time_point.time_since_epoch().count() * sampleRate / 1000); const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( (time_point.time_since_epoch().count() + 500) / 1000 * sampleRate / 1000); } ================================================ FILE: rtsp-server/xop/AACSource.h ================================================ // PHZ // 2018-5-16 #ifndef XOP_AAC_SOURCE_H #define XOP_AAC_SOURCE_H #include "MediaSource.h" #include "rtp.h" namespace xop { class AACSource : public MediaSource { public: static AACSource *CreateNew(uint32_t samplerate = 44100, uint8_t channels = 2, bool has_adts = true); ~AACSource() override; uint32_t GetSamplerate() const { return samplerate_; } uint32_t GetChannels() const { return channels_; } std::string GetMediaDescription(uint16_t port = 0) override; std::string GetAttribute() override; bool HandleFrame(MediaChannelId channel_id, AVFrame frame) override; static uint32_t GetTimestamp(uint32_t samplerate = 44100); private: AACSource(uint32_t samplerate, uint8_t channels, bool has_adts); uint32_t samplerate_ = 44100; uint8_t channels_ = 2; bool has_adts_ = true; static constexpr size_t ADTS_SIZE = 7; static constexpr size_t AU_SIZE = 4; }; } #endif ================================================ FILE: rtsp-server/xop/Base64Encode.cpp ================================================ #include "Base64Encode.h" #include extern "C" { #include "b64/cencode.h" } std::string xop::Base64Encode(const void *input, const size_t size) { std::vector buffer(size / 3 * 4 + (size % 3 > 0 ? 4 : 0) + 1); base64_encodestate b64encoder; base64_init_encodestate(&b64encoder); const auto length = base64_encode_block( static_cast(input), static_cast(size), buffer.data(), &b64encoder); base64_encode_blockend(buffer.data() + length, &b64encoder); return std::string(buffer.cbegin(), buffer.cend() - 1); //TODO } ================================================ FILE: rtsp-server/xop/Base64Encode.h ================================================ #ifndef _XOP_BASE64ENCODE_H #define _XOP_BASE64ENCODE_H #include namespace xop { std::string Base64Encode(const void *input, const size_t size); } #endif ================================================ FILE: rtsp-server/xop/BaseMd5.cpp ================================================ #include "BaseMd5.h" #include "md5.hpp" using namespace xop; BaseMd5::BaseMd5() : Md5() {} BaseMd5::~BaseMd5() = default; void BaseMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize, unsigned char *outHash) { md5::md5_state_t state; md5_init(&state); md5_append(&state, data, dataSize); md5_finish(&state, outHash); } ================================================ FILE: rtsp-server/xop/BaseMd5.h ================================================ #ifndef _XOP_BASEMD5_H #define _XOP_BASEMD5_H #include "Md5.h" namespace xop { class BaseMd5 : public Md5 { public: BaseMd5(); ~BaseMd5() override; void GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash) override; }; } #endif ================================================ FILE: rtsp-server/xop/CngMd5.cpp ================================================ #include "CngMd5.h" #include "net/Logger.h" #if defined(WIN32) || defined(_WIN32) #pragma comment(lib, "Bcrypt.lib") #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) #endif using namespace xop; CngMd5::CngMd5() : Md5() { #if defined(WIN32) || defined(_WIN32) DWORD cbData = 0; NTSTATUS status; if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider( &hAlgorithm_, BCRYPT_MD5_ALGORITHM, nullptr, 0))) { LOG_ERROR( "**** Error 0x%x returned by BCryptOpenAlgorithmProvider", status); return; } if (!NT_SUCCESS(status = BCryptGetProperty( hAlgorithm_, BCRYPT_OBJECT_LENGTH, (PBYTE)&cbHashObject_, sizeof(DWORD), &cbData, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptGetProperty", status); CngMd5::~CngMd5(); return; } if (!NT_SUCCESS(status = BCryptGetProperty( hAlgorithm_, BCRYPT_HASH_LENGTH, (PBYTE)&cbHash_, sizeof(DWORD), &cbData, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptGetProperty", status); CngMd5::~CngMd5(); return; } if (cbHash_ > MD5_HASH_LENGTH) { LOG_ERROR("**** The generated hash value is too long"); CngMd5::~CngMd5(); } #endif } CngMd5::~CngMd5() { #if defined(WIN32) || defined(_WIN32) if (hAlgorithm_) { BCryptCloseAlgorithmProvider(hAlgorithm_, 0); hAlgorithm_ = nullptr; } #endif } void CngMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize, unsigned char *outHash) { #if defined(WIN32) || defined(_WIN32) if (hAlgorithm_ == nullptr) return; const auto pbHashObject = static_cast( HeapAlloc(GetProcessHeap(), 0, cbHashObject_)); //create a hash BCRYPT_HASH_HANDLE hHash = nullptr; auto cleanup = [&] () { if (hHash) BCryptDestroyHash(hHash); if (pbHashObject) HeapFree(GetProcessHeap(), 0, pbHashObject); }; if (nullptr == pbHashObject) { LOG_ERROR("**** memory allocation failed"); cleanup(); return; } NTSTATUS status; if (!NT_SUCCESS(status = BCryptCreateHash(hAlgorithm_, &hHash, pbHashObject, cbHashObject_, nullptr, 0, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptCreateHash", status); cleanup(); return; } if (!NT_SUCCESS(status = BCryptHashData(hHash, const_cast(data), static_cast(dataSize), 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptHashData", status); cleanup(); return; } //close the hash if (!NT_SUCCESS(status = BCryptFinishHash(hHash, outHash, cbHash_, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptFinishHash", status); } cleanup(); #endif } ================================================ FILE: rtsp-server/xop/CngMd5.h ================================================ #ifndef _XOP_CNGMD5_H #define _XOP_CNGMD5_H #include "Md5.h" #if defined(WIN32) || defined(_WIN32) #include #include #endif namespace xop { class CngMd5 : public Md5 { public: CngMd5(); ~CngMd5() override; void GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash) override; private: #if defined(WIN32) || defined(_WIN32) BCRYPT_ALG_HANDLE hAlgorithm_ = nullptr; DWORD cbHash_ = 0, cbHashObject_ = 0; #endif }; } #endif ================================================ FILE: rtsp-server/xop/DigestAuthentication.cpp ================================================ #include "DigestAuthentication.h" #include #include #include #if defined(WIN32) || defined(_WIN32) #include "CngMd5.h" #elif defined(__linux) || defined(__linux__) #include "BaseMd5.h" #elif defined(__APPLE__) || defined(__MACH__) #include "MacMd5.h" #else #include "BaseMd5.h" #endif using namespace xop; DigestAuthentication::DigestAuthentication(std::string realm, std::string username, std::string password) : realm_(std::move(realm)), username_(std::move(username)), password_(std::move(password)) { #if defined(WIN32) || defined(_WIN32) md5_ = new CngMd5(); #elif defined(__linux) || defined(__linux__) md5_ = new BaseMd5(); #elif defined(__APPLE__) || defined(__MACH__) md5_ = new MacMd5(); #else md5_ = new BaseMd5(); #endif } DigestAuthentication::~DigestAuthentication() { delete md5_; } std::string DigestAuthentication::GetNonce() const { std::random_device rd; const auto timePoint = std::chrono::time_point_cast( std::chrono::steady_clock::now()); const uint32_t timestamp = static_cast(timePoint.time_since_epoch().count()); return md5_->GetMd5HashString(std::to_string(timestamp + rd())); } std::string DigestAuthentication::GetResponse(const std::string &nonce, const std::string &cmd, const std::string &url) const { //md5(md5(: : ) : : md5(:)) const auto hex1 = md5_->GetMd5HashString(username_ + ":" + realm_ + ":" + password_); const auto hex2 = md5_->GetMd5HashString(cmd + ":" + url); auto response = md5_->GetMd5HashString(hex1 + ":" + nonce + ":" + hex2); return response; } ================================================ FILE: rtsp-server/xop/DigestAuthentication.h ================================================ //PHZ //2019-10-6 #ifndef RTSP_DIGEST_AUTHENTICATION_H #define RTSP_DIGEST_AUTHENTICATION_H #include #include "Md5.h" namespace xop { class DigestAuthentication { public: DigestAuthentication(std::string realm, std::string username, std::string password); virtual ~DigestAuthentication(); std::string GetRealm() const { return realm_; } std::string GetUsername() const { return username_; } std::string GetPassword() const { return password_; } std::string GetNonce() const; std::string GetResponse(const std::string &nonce, const std::string &cmd, const std::string &url) const; private: std::string realm_; std::string username_; std::string password_; Md5 *md5_; }; } #endif ================================================ FILE: rtsp-server/xop/G711ASource.cpp ================================================ // PHZ // 2018-5-16 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "G711ASource.h" #include #include #include #if defined(WIN32) || defined(_WIN32) #else #include #endif using namespace xop; using namespace std; G711ASource::G711ASource() { payload_ = 8; media_type_ = MediaType::PCMA; clock_rate_ = 8000; } G711ASource *G711ASource::CreateNew() { return new G711ASource(); } G711ASource::~G711ASource() = default; string G711ASource::GetMediaDescription(const uint16_t port) { char buf[100]; snprintf(buf, sizeof(buf), "m=audio %hu RTP/AVP 8", port); return buf; } string G711ASource::GetAttribute() { return "a=rtpmap:8 PCMA/8000/1"; } bool G711ASource::HandleFrame(const MediaChannelId channel_id, const AVFrame frame) { if (frame.size > MAX_RTP_PAYLOAD_SIZE) { return false; } const uint8_t *frame_buf = frame.buffer.get(); const size_t frame_size = frame.size; RtpPacket rtp_pkt; rtp_pkt.type = FrameType::AUDIO_FRAME; rtp_pkt.timestamp = frame.timestamp; rtp_pkt.size = static_cast(frame_size) + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE; rtp_pkt.last = 1; memcpy(rtp_pkt.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE, frame_buf, frame_size); if (send_frame_callback_) { return send_frame_callback_(channel_id, rtp_pkt); //TODO } return true; } uint32_t G711ASource::GetTimestamp() { const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( (time_point.time_since_epoch().count() + 500) / 1000 * 8); } ================================================ FILE: rtsp-server/xop/G711ASource.h ================================================ // PHZ // 2018-5-16 #ifndef XOP_G711A_SOURCE_H #define XOP_G711A_SOURCE_H #include "MediaSource.h" #include "rtp.h" namespace xop { class G711ASource : public MediaSource { public: static G711ASource *CreateNew(); ~G711ASource() override; uint32_t GetSampleRate() const { return samplerate_; } uint32_t GetChannels() const { return channels_; } std::string GetMediaDescription(uint16_t port = 0) override; std::string GetAttribute() override; bool HandleFrame(MediaChannelId channel_id, AVFrame frame) override; static uint32_t GetTimestamp(); private: G711ASource(); uint32_t samplerate_ = 8000; uint32_t channels_ = 1; }; } #endif ================================================ FILE: rtsp-server/xop/H264NalUnit.cpp ================================================ #include "H264NalUnit.h" using namespace xop; H264NalUnit::H264NalUnit(const uint8_t *data, size_t dataSize) : NalUnit(data, dataSize) { } uint8_t H264NalUnit::GetType() { uint8_t *data; if (GetHeader(&data) == H264_NALU_HEADER_SIZE) { return data[0] & 0x1f; } return 0; } uint8_t H264NalUnit::GetRefIdc() { uint8_t *data; if (GetHeader(&data) == H264_NALU_HEADER_SIZE) { return (data[0] & 0x60) >> 5; } return 0; } H264NalType H264NalUnit::GetH264Type() { return static_cast(GetType()); } size_t H264NalUnit::GetHeader(uint8_t **data) { if (GetData(data) >= H264_NALU_HEADER_SIZE) return H264_NALU_HEADER_SIZE; return 0; } size_t H264NalUnit::CopyHeader(uint8_t *start, size_t size) { if (size > H264_NALU_HEADER_SIZE) { size = H264_NALU_HEADER_SIZE; } return CopyData(start, size); } size_t H264NalUnit::GetHeaderSize() { const auto size = GetSize(); if (size > H264_NALU_HEADER_SIZE) return H264_NALU_HEADER_SIZE; return 0; } size_t H264NalUnit::GetBody(uint8_t **data) { const auto size = GetData(data); if (size > H264_NALU_HEADER_SIZE) { *data += H264_NALU_HEADER_SIZE; return size - H264_NALU_HEADER_SIZE; } return 0; } size_t H264NalUnit::CopyBody(uint8_t *start, size_t size, size_t skip) { skip += H264_NALU_HEADER_SIZE; return CopyData(start, size, skip); } size_t H264NalUnit::GetBodySize() { const auto size = GetSize(); if (size > H264_NALU_HEADER_SIZE) return size - H264_NALU_HEADER_SIZE; return 0; } bool H264NalUnit::IsIdrFrame() { const auto type = GetH264Type(); return type == H264NalType::H264_NAL_SLICE_IDR; } bool H264NalUnit::IsFrame() { const auto type = GetH264Type(); return type >= H264NalType::H264_NAL_SLICE && type <= H264NalType::H264_NAL_SLICE_IDR; } NalUnit * H264NalUnit::GetNalUnit(const uint8_t *data, size_t dataSize) { return new H264NalUnit(data, dataSize); } ================================================ FILE: rtsp-server/xop/H264NalUnit.h ================================================ #ifndef XOP_H264_NALUNIT_H #define XOP_H264_NALUNIT_H #include #include "NalUnit.h" #define H264_NALU_HEADER_SIZE 1 namespace xop { enum class H264NalType: uint8_t { H264_NAL_UNKNOWN = 0, H264_NAL_SLICE = 1, H264_NAL_SLICE_DPA = 2, H264_NAL_SLICE_DPB = 3, H264_NAL_SLICE_DPC = 4, H264_NAL_SLICE_IDR = 5, H264_NAL_SEI = 6, H264_NAL_SPS = 7, H264_NAL_PPS = 8, H264_NAL_AUD = 9, H264_NAL_EOSEQ = 10, H264_NAL_EOSTREAM = 11, H264_NAL_FILLER = 12, H264_NAL_RSV13 = 13, H264_NAL_RSV14 = 14, H264_NAL_RSV15 = 15, H264_NAL_RSV16 = 16, H264_NAL_RSV17 = 17, H264_NAL_RSV18 = 18, H264_NAL_RSV19 = 19, H264_NAL_RSV20 = 20, H264_NAL_RSV21 = 21, H264_NAL_RSV22 = 22, H264_NAL_RSV23 = 23, H264_NAL_UNSPEC24 = 24, H264_NAL_UNSPEC25 = 25, H264_NAL_UNSPEC26 = 26, H264_NAL_UNSPEC27 = 27, H264_NAL_UNSPEC28 = 28, H264_NAL_UNSPEC29 = 29, H264_NAL_UNSPEC30 = 30, H264_NAL_UNSPEC31 = 31 }; class H264NalUnit : public NalUnit { public: uint8_t GetType() override; uint8_t GetRefIdc(); H264NalType GetH264Type(); size_t GetHeader(uint8_t **data) override; size_t CopyHeader(uint8_t *start, size_t size) override; size_t GetHeaderSize() override; size_t GetBody(uint8_t **data) override; size_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) override; size_t GetBodySize() override; bool IsIdrFrame() override; bool IsFrame() override; static NalUnit *GetNalUnit(const uint8_t *data, size_t dataSize); private: H264NalUnit(const uint8_t *data, size_t dataSize); }; } #endif ================================================ FILE: rtsp-server/xop/H264Source.cpp ================================================ // PHZ // 2018-5-16 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "H264Source.h" #include #include #include #include #include #include #if defined(WIN32) || defined(_WIN32) #else #include #endif #include "Base64Encode.h" #include "Nal.h" #include "H264NalUnit.h" using namespace xop; using namespace std; H264Source::H264Source(vector sps, vector pps, const uint32_t framerate) : framerate_(framerate), sps_(std::move(sps)), pps_(std::move(pps)) { payload_ = 96; media_type_ = MediaType::H264; clock_rate_ = 90000; } H264Source *H264Source::CreateNew(vector extraData, const uint32_t framerate) { Nal nal(extraData); vector sps, pps; const auto sps_nal_unit = nal.GetNalUnitByType( static_cast(H264NalType::H264_NAL_SPS)), pps_nal_unit = nal.GetNalUnitByType( static_cast(H264NalType::H264_NAL_PPS)); if (sps_nal_unit != nullptr) sps = sps_nal_unit->GetData(); if (pps_nal_unit != nullptr) pps = pps_nal_unit->GetData(); return new H264Source(sps, pps, framerate); } H264Source *H264Source::CreateNew(vector sps, vector pps, const uint32_t framerate) { return new H264Source(std::move(sps), std::move(pps), framerate); } H264Source::~H264Source() = default; string H264Source::GetMediaDescription(const uint16_t port) { char buf[100]; snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); // \r\nb=AS:2000 return buf; } string H264Source::GetAttribute() { auto sdp = string("a=rtpmap:96 H264/90000\r\n"); if (!sps_.empty() && !pps_.empty()) { const auto fmtp = "a=fmtp:96 packetization-mode=1;" "profile-level-id=%06X;" "sprop-parameter-sets=%s,%s"; const auto pps_base64 = Base64Encode(pps_.data(), pps_.size()); const auto sps_base64 = Base64Encode(sps_.data(), sps_.size()); const uint32_t profile_level_id = (sps_.at(1) << 16) | (sps_.at(2) << 8) | sps_.at(3); const size_t buf_size = 1 + strlen(fmtp) + 6 + sps_base64.length() + pps_base64.length(); auto buf = vector(buf_size); snprintf(buf.data(), buf_size, fmtp, profile_level_id, sps_base64.c_str(), pps_base64.c_str()); sdp.append(buf.data()); } return sdp; } bool H264Source::HandleFrame(const MediaChannelId channelId, const AVFrame frame) { RtpPacket rtp_packet; //rtpPacket.timestamp = frame.timestamp == 0 ? GetTimestamp() : frame.timestamp; rtp_packet.timestamp = frame.timestamp; const auto rtp_packet_data = rtp_packet.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE; Nal nal(frame.buffer.get(), frame.size); if (nal.GetCount() == 0) return false; size_t nal_index = 0; while (nal_index < nal.GetCount()) { size_t end_index = nal_index; size_t size_count = H264_NALU_HEADER_SIZE; while (size_count < MAX_RTP_PAYLOAD_SIZE && end_index < nal.GetCount()) { size_count += nal[end_index++]->GetSize() + 2; } end_index--; if (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index) size_count -= nal[end_index--]->GetSize() + 2; if (end_index > nal_index) { //Single-Time Aggregation Packet (STAP-A) /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RTP Header | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NALU 1 Data | * : : * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | NALU 2 Size | NALU 2 HDR | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NALU 2 Data | * : : * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size_count); rtp_packet.last = 1; size_t skip = H264_NALU_HEADER_SIZE; uint8_t maximum_ref_idc = 0; auto all_frame_type = FrameType::NONE; for (; nal_index <= end_index; nal_index++) { const auto nal_unit = nal[nal_index]; const auto ref_idc = nal_unit->GetRefIdc(); const auto frame_type = GetRtpFrameType(nal_unit); if (maximum_ref_idc < ref_idc) maximum_ref_idc = ref_idc; if (frame_type == FrameType::VIDEO_FRAME_IDR) all_frame_type = FrameType::VIDEO_FRAME_IDR; else if (all_frame_type == FrameType::NONE) all_frame_type = frame_type; const auto size = static_cast( nal_unit->GetSize()); rtp_packet_data[skip++] = size >> 8; rtp_packet_data[skip++] = size & 0xff; skip += nal_unit->CopyData( rtp_packet_data + skip, MAX_RTP_PAYLOAD_SIZE - H264_NALU_HEADER_SIZE - 2); } //STAP-A NAL HDR rtp_packet_data[0] = static_cast( (maximum_ref_idc & 0x03) << 5 | 24); rtp_packet.type = all_frame_type; if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { //Single NAL Unit Packets /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |F|NRI| type | | * +-+-+-+-+-+-+-+-+ | * | | * | Bytes 2..n of a Single NAL unit | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ const auto nal_unit = nal[nal_index++]; if (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) { const auto size = nal_unit->CopyData( rtp_packet_data, MAX_RTP_PAYLOAD_SIZE); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size); rtp_packet.last = 1; rtp_packet.type = GetRtpFrameType(nal_unit); if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { //Fragmentation Units (FU-A) /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | FU indicator | FU header | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | | * | FU payload | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ //PayloadHeader nal_unit->CopyHeader(rtp_packet_data, H264_NALU_HEADER_SIZE); rtp_packet_data[0] &= 0xe0; rtp_packet_data[0] |= 28; //FU Header rtp_packet_data[1] = nal_unit->GetType() & 0x1f; size_t skip = 0; const size_t size = nal_unit->GetBodySize(); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + MAX_RTP_PAYLOAD_SIZE; rtp_packet.last = 0; rtp_packet.type = GetRtpFrameType(nal_unit); //First rtp_packet_data[1] |= 0x80; skip += nal_unit->CopyBody( rtp_packet_data + H264_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H264_NALU_HEADER_SIZE - 1, skip); if (!send_frame_callback_(channelId, rtp_packet)) return false; //Middle rtp_packet_data[1] &= 0x1f; while (size - skip > MAX_RTP_PAYLOAD_SIZE - H264_NALU_HEADER_SIZE - 1) { skip += nal_unit->CopyBody( rtp_packet_data + H264_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H264_NALU_HEADER_SIZE - 1, skip); if (!send_frame_callback_(channelId, rtp_packet)) return false; } //Last rtp_packet_data[1] |= 0x40; rtp_packet.last = 1; const auto last_size = nal_unit->CopyBody( rtp_packet_data + H264_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H264_NALU_HEADER_SIZE - 1, skip); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(last_size) + H264_NALU_HEADER_SIZE + 1; if (!send_frame_callback_(channelId, rtp_packet)) return false; } } } return true; } uint32_t H264Source::GetTimestamp() { /* #if defined(__linux) || defined(__linux__) struct timeval tv = {0}; gettimeofday(&tv, NULL); uint32_t ts = ((tv.tv_sec*1000)+((tv.tv_usec+500)/1000))*90; // 90: _clockRate/1000; return ts; #else */ const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( (time_point.time_since_epoch().count() + 500) / 1000 * 90); //#endif } FrameType H264Source::GetRtpFrameType(std::shared_ptr nalUnit) { if (nalUnit->IsIdrFrame()) return FrameType::VIDEO_FRAME_IDR; if (nalUnit->IsFrame()) return FrameType::VIDEO_FRAME_NOTIDR; return FrameType::NONE; } ================================================ FILE: rtsp-server/xop/H264Source.h ================================================ // PHZ // 2018-5-16 #ifndef XOP_H264_SOURCE_H #define XOP_H264_SOURCE_H #include "MediaSource.h" #include "rtp.h" #include "NalUnit.h" namespace xop { class H264Source : public MediaSource { public: static H264Source *CreateNew(std::vector extraData, uint32_t framerate = 25); static H264Source *CreateNew(std::vector sps, std::vector pps, uint32_t framerate = 25); ~H264Source() override; void SetFramerate(const uint32_t framerate) { framerate_ = framerate; } uint32_t GetFramerate() const { return framerate_; } std::string GetMediaDescription(uint16_t port) override; std::string GetAttribute() override; bool HandleFrame(MediaChannelId channel_id, AVFrame frame) override; static uint32_t GetTimestamp(); private: H264Source(std::vector sps, std::vector pps, uint32_t framerate); static FrameType GetRtpFrameType(std::shared_ptr nalUnit); uint32_t framerate_ = 25; //uint32_t profileLevelId_; std::vector sps_; std::vector pps_; }; } #endif ================================================ FILE: rtsp-server/xop/H265NalUnit.cpp ================================================ #include "H265NalUnit.h" using namespace xop; H265NalUnit::H265NalUnit(const uint8_t *data, size_t dataSize) : NalUnit(data, dataSize) { } uint8_t H265NalUnit::GetType() { uint8_t *data; if (GetHeader(&data) == H265_NALU_HEADER_SIZE) { return (data[0] & 0x7e) >> 1; } return 0; } uint8_t H265NalUnit::GetLayerId() { uint8_t *data; if (GetHeader(&data) == H265_NALU_HEADER_SIZE) { return (data[0] << 5 & 0x20) | (data[1] >> 3 & 0x1f); } return 0; } uint8_t H265NalUnit::GetTemporalId() { uint8_t *data; if (GetHeader(&data) == H265_NALU_HEADER_SIZE) { return data[1] & 0x07; } return 0; } H265NalType H265NalUnit::GetH265Type() { return static_cast(GetType()); } size_t H265NalUnit::GetHeader(uint8_t **data) { if (GetData(data) >= H265_NALU_HEADER_SIZE) return H265_NALU_HEADER_SIZE; return 0; } size_t H265NalUnit::CopyHeader(uint8_t *start, size_t size) { if (size > H265_NALU_HEADER_SIZE) { size = H265_NALU_HEADER_SIZE; } return CopyData(start, size); } size_t H265NalUnit::GetHeaderSize() { const auto size = GetSize(); if (size > H265_NALU_HEADER_SIZE) return H265_NALU_HEADER_SIZE; return 0; } size_t H265NalUnit::GetBody(uint8_t **data) { const auto size = GetData(data); if (size > H265_NALU_HEADER_SIZE) { *data += H265_NALU_HEADER_SIZE; return size - H265_NALU_HEADER_SIZE; } return 0; } size_t H265NalUnit::CopyBody(uint8_t *start, size_t size, size_t skip) { skip += H265_NALU_HEADER_SIZE; return CopyData(start, size, skip); } size_t H265NalUnit::GetBodySize() { const auto size = GetSize(); if (size > H265_NALU_HEADER_SIZE) return size - H265_NALU_HEADER_SIZE; return 0; } bool H265NalUnit::IsIdrFrame() { const auto type = GetH265Type(); return type == H265NalType::H265_NAL_IDR_N_LP || type == H265NalType::H265_NAL_IDR_W_RADL; } bool H265NalUnit::IsFrame() { const auto type = GetH265Type(); return type <= H265NalType::H265_NAL_CRA_NUT; } NalUnit* H265NalUnit::GetNalUnit(const uint8_t *data, size_t dataSize) { return new H265NalUnit(data, dataSize); } ================================================ FILE: rtsp-server/xop/H265NalUnit.h ================================================ #ifndef XOP_H265_NALUNIT_H #define XOP_H265_NALUNIT_H #include #include "NalUnit.h" #define H265_NALU_HEADER_SIZE 2 namespace xop { enum class H265NalType: uint8_t { H265_NAL_TRAIL_N = 0, H265_NAL_TRAIL_R = 1, H265_NAL_TSA_N = 2, H265_NAL_TSA_R = 3, H265_NAL_STSA_N = 4, H265_NAL_STSA_R = 5, H265_NAL_RADL_N = 6, H265_NAL_RADL_R = 7, H265_NAL_RASL_N = 8, H265_NAL_RASL_R = 9, H265_NAL_VCL_N10 = 10, H265_NAL_VCL_R11 = 11, H265_NAL_VCL_N12 = 12, H265_NAL_VCL_R13 = 13, H265_NAL_VCL_N14 = 14, H265_NAL_VCL_R15 = 15, H265_NAL_BLA_W_LP = 16, H265_NAL_BLA_W_RADL = 17, H265_NAL_BLA_N_LP = 18, H265_NAL_IDR_W_RADL = 19, H265_NAL_IDR_N_LP = 20, H265_NAL_CRA_NUT = 21, H265_NAL_RSV_IRAP_VCL22 = 22, H265_NAL_RSV_IRAP_VCL23 = 23, H265_NAL_RSV_VCL24 = 24, H265_NAL_RSV_VCL25 = 25, H265_NAL_RSV_VCL26 = 26, H265_NAL_RSV_VCL27 = 27, H265_NAL_RSV_VCL28 = 28, H265_NAL_RSV_VCL29 = 29, H265_NAL_RSV_VCL30 = 30, H265_NAL_RSV_VCL31 = 31, H265_NAL_VPS = 32, H265_NAL_SPS = 33, H265_NAL_PPS = 34, H265_NAL_AUD = 35, H265_NAL_EOS_NUT = 36, H265_NAL_EOB_NUT = 37, H265_NAL_FD_NUT = 38, H265_NAL_SEI_PREFIX = 39, H265_NAL_SEI_SUFFIX = 40, H265_NAL_RSV_NVCL41 = 41, H265_NAL_RSV_NVCL42 = 42, H265_NAL_RSV_NVCL43 = 43, H265_NAL_RSV_NVCL44 = 44, H265_NAL_RSV_NVCL45 = 45, H265_NAL_RSV_NVCL46 = 46, H265_NAL_RSV_NVCL47 = 47, H265_NAL_UNSPEC48 = 48, H265_NAL_UNSPEC49 = 49, H265_NAL_UNSPEC50 = 50, H265_NAL_UNSPEC51 = 51, H265_NAL_UNSPEC52 = 52, H265_NAL_UNSPEC53 = 53, H265_NAL_UNSPEC54 = 54, H265_NAL_UNSPEC55 = 55, H265_NAL_UNSPEC56 = 56, H265_NAL_UNSPEC57 = 57, H265_NAL_UNSPEC58 = 58, H265_NAL_UNSPEC59 = 59, H265_NAL_UNSPEC60 = 60, H265_NAL_UNSPEC61 = 61, H265_NAL_UNSPEC62 = 62, H265_NAL_UNSPEC63 = 63, }; class H265NalUnit : public NalUnit { public: uint8_t GetType() override; uint8_t GetLayerId(); uint8_t GetTemporalId(); H265NalType GetH265Type(); size_t GetHeader(uint8_t **data) override; size_t CopyHeader(uint8_t *start, size_t size) override; size_t GetHeaderSize() override; size_t GetBody(uint8_t **data) override; size_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) override; size_t GetBodySize() override; bool IsIdrFrame() override; bool IsFrame() override; static NalUnit *GetNalUnit(const uint8_t *data, size_t dataSize); private: H265NalUnit(const uint8_t *data, size_t dataSize); }; } #endif ================================================ FILE: rtsp-server/xop/H265Source.cpp ================================================ // PHZ // 2018-6-7 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "H265Source.h" #include #include #include #include #if defined(WIN32) || defined(_WIN32) #else #include #endif #include "Base64Encode.h" #include "Nal.h" #include "H265NalUnit.h" using namespace xop; using namespace std; H265Source::H265Source(vector vps, vector sps, vector pps, vector sei, const uint32_t framerate) : framerate_(framerate), vps_(std::move(vps)), sps_(std::move(sps)), pps_(std::move(pps)), sei_(std::move(sei)) { payload_ = 96; media_type_ = MediaType::H265; clock_rate_ = 90000; } H265Source *H265Source::CreateNew(vector extraData, vector sei, const uint32_t framerate) { Nal nal(extraData); vector vps, sps, pps; const auto vps_nal_unit = nal.GetNalUnitByType( static_cast(H265NalType::H265_NAL_VPS)), sps_nal_unit = nal.GetNalUnitByType( static_cast(H265NalType::H265_NAL_SPS)), pps_nal_unit = nal.GetNalUnitByType( static_cast(H265NalType::H265_NAL_PPS)); if (vps_nal_unit != nullptr) vps = vps_nal_unit->GetData(); if (sps_nal_unit != nullptr) sps = sps_nal_unit->GetData(); if (pps_nal_unit != nullptr) pps = pps_nal_unit->GetData(); return new H265Source(vps, sps, pps, std::move(sei), framerate); } H265Source::~H265Source() = default; H265Source *H265Source::CreateNew(vector vps, vector sps, vector pps, vector sei, const uint32_t framerate) { return new H265Source(std::move(vps), std::move(sps), std::move(pps), std::move(sei), framerate); } string H265Source::GetMediaDescription(const uint16_t port) { char buf[100]; snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); return buf; } string H265Source::GetAttribute() { auto sdp = string("a=rtpmap:96 H265/90000\r\n"); if (!vps_.empty() && !sps_.empty() && !pps_.empty()) { const auto fmtp = "a=fmtp:96 profile-space=%u;tier-flag=%u;" "profile-id=%u;level-id=%u;interop-constraints=%012llX;" "sprop-vps=%s;sprop-pps=%s;sprop-sps=%s;%s"; string vps_base64, pps_base64, sps_base64, sei; vps_base64 = Base64Encode(vps_.data(), vps_.size()); pps_base64 = Base64Encode(pps_.data(), pps_.size()); sps_base64 = Base64Encode(sps_.data(), sps_.size()); if (!sei_.empty()) { sei = "sprop-sei="; sei.append(Base64Encode(sei_.data(), sei_.size())); sei.append(";"); } else sei = ""; const uint8_t profile_space = sps_.at(3) >> 6; const uint8_t tier_flag = (sps_.at(3) & 0x20) >> 5; const uint8_t profile_id = sps_.at(3) & 0x1f; const uint8_t level_id = sps_.at(17); const uint64_t interop_constraints = static_cast(sps_.at(9)) << 40 | static_cast(sps_.at(10)) << 32 | static_cast(sps_.at(11)) << 24 | static_cast(sps_.at(12)) << 16 | static_cast(sps_.at(13)) << 8 | static_cast(sps_.at(14)); const size_t buf_size = 1 + strlen(fmtp) + vps_base64.length() + pps_base64.length() + sps_base64.length() + sei.length(); auto buf = vector(buf_size); snprintf(buf.data(), buf_size, fmtp, profile_space, tier_flag, profile_id, level_id, interop_constraints, vps_base64.c_str(), pps_base64.c_str(), sps_base64.c_str(), sei.c_str()); buf[strlen(buf.data()) - 1] = '\0'; sdp.append(buf.data()); } return sdp; } bool H265Source::HandleFrame(const MediaChannelId channelId, const AVFrame frame) { RtpPacket rtp_packet; //rtp_packet.timestamp = frame.timestamp == 0 ? GetTimestamp() : frame.timestamp; rtp_packet.timestamp = frame.timestamp; const auto rtp_packet_data = rtp_packet.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE; Nal nal(frame.buffer.get(), frame.size); if (nal.GetCount() == 0) return false; size_t nal_index = 0; while (nal_index < nal.GetCount()) { size_t end_index = nal_index; size_t size_count = H265_NALU_HEADER_SIZE; while (size_count < MAX_RTP_PAYLOAD_SIZE && end_index < nal.GetCount()) { size_count += nal[end_index++]->GetSize() + 2; } end_index--; if (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index) size_count -= nal[end_index--]->GetSize() + 2; if (end_index > nal_index) { //Aggregation Packets /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | RTP Header | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=48) | NALU 1 Size | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NALU 1 HDR | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 1 Data | * | . . . | * | | * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | . . . | NALU 2 Size | NALU 2 HDR | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | NALU 2 HDR | | * +-+-+-+-+-+-+-+-+ NALU 2 Data | * | . . . | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size_count); rtp_packet.last = 1; size_t skip = H265_NALU_HEADER_SIZE; uint8_t lowest_layer_id = 0x3f; uint8_t lowest_temporal_id = 0x07; auto all_frame_type = FrameType::NONE; for (; nal_index <= end_index; nal_index++) { const auto nal_unit = nal[nal_index]; const auto layer_id = nal_unit->GetLayerId(); const auto temporal_id = nal_unit->GetTemporalId(); const auto frame_type = GetRtpFrameType(nal_unit); if (lowest_layer_id > layer_id) lowest_layer_id = layer_id; if (lowest_temporal_id > temporal_id) lowest_temporal_id = temporal_id; if (frame_type == FrameType::VIDEO_FRAME_IDR) all_frame_type = FrameType::VIDEO_FRAME_IDR; else if (all_frame_type == FrameType::NONE) all_frame_type = frame_type; const auto size = static_cast( nal_unit->GetSize()); rtp_packet_data[skip++] = size >> 8; rtp_packet_data[skip++] = size & 0xff; skip += nal_unit->CopyData( rtp_packet_data + skip, MAX_RTP_PAYLOAD_SIZE - H265_NALU_HEADER_SIZE - 2); } //PayloadHeader rtp_packet_data[0] = (lowest_layer_id & 0x3f) >> 5 | 48 << 1; rtp_packet_data[1] = static_cast( (lowest_layer_id & 0x3f) << 3 | (lowest_temporal_id & 0x07)); rtp_packet.type = all_frame_type; if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { //Single NAL Unit Packets /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | | * | | * | NAL unit payload data | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ const auto nal_unit = nal[nal_index++]; if (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) { const auto size = nal_unit->CopyData( rtp_packet_data, MAX_RTP_PAYLOAD_SIZE); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size); rtp_packet.last = 1; rtp_packet.type = GetRtpFrameType(nal_unit); if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { //Fragmentation Units /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=49) | FU header | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | | * | | * | FU payload | * | | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ //PayloadHeader nal_unit->CopyHeader(rtp_packet_data, H265_NALU_HEADER_SIZE); rtp_packet_data[0] &= 0x81; rtp_packet_data[0] |= 49 << 1; //FU Header rtp_packet_data[2] = nal_unit->GetType() & 0x3f; size_t skip = 0; const size_t size = nal_unit->GetBodySize(); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + MAX_RTP_PAYLOAD_SIZE; rtp_packet.last = 0; rtp_packet.type = GetRtpFrameType(nal_unit); //First rtp_packet_data[2] |= 0x80; skip += nal_unit->CopyBody( rtp_packet_data + H265_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H265_NALU_HEADER_SIZE - 1, skip); if (!send_frame_callback_(channelId, rtp_packet)) return false; //Middle rtp_packet_data[2] &= 0x3f; while (size - skip > MAX_RTP_PAYLOAD_SIZE - 3) { skip += nal_unit->CopyBody( rtp_packet_data + H265_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H265_NALU_HEADER_SIZE - 1, skip); if (!send_frame_callback_(channelId, rtp_packet)) return false; } //Last rtp_packet_data[2] |= 0x40; rtp_packet.last = 1; const auto last_size = nal_unit->CopyBody( rtp_packet_data + H265_NALU_HEADER_SIZE + 1, MAX_RTP_PAYLOAD_SIZE - H265_NALU_HEADER_SIZE - 1, skip); rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(last_size) + H265_NALU_HEADER_SIZE + 1; if (!send_frame_callback_(channelId, rtp_packet)) return false; } } } return true; } uint32_t H265Source::GetTimestamp() { /* #if defined(__linux) || defined(__linux__) struct timeval tv = {0}; gettimeofday(&tv, NULL); uint32_t ts = ((tv.tv_sec*1000)+((tv.tv_usec+500)/1000))*90; // 90: _clockRate/1000; return ts; #else */ //auto time_point = chrono::time_point_cast(chrono::system_clock::now()); const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( (time_point.time_since_epoch().count() + 500) / 1000 * 90); //#endif } FrameType H265Source::GetRtpFrameType(std::shared_ptr nalUnit) { if (nalUnit->IsIdrFrame()) return FrameType::VIDEO_FRAME_IDR; if (nalUnit->IsFrame()) return FrameType::VIDEO_FRAME_NOTIDR; return FrameType::NONE; } ================================================ FILE: rtsp-server/xop/H265Source.h ================================================ // PHZ // 2018-5-16 #ifndef XOP_H265_SOURCE_H #define XOP_H265_SOURCE_H #include "MediaSource.h" #include "rtp.h" #include "NalUnit.h" namespace xop { class H265Source : public MediaSource { public: static H265Source * CreateNew(std::vector extraData, std::vector sei = std::vector(), uint32_t framerate = 25); static H265Source * CreateNew(std::vector vps, std::vector sps, std::vector pps, std::vector sei = std::vector(), uint32_t framerate = 25); ~H265Source() override; void SetFramerate(const uint32_t framerate) { framerate_ = framerate; } uint32_t GetFramerate() const { return framerate_; } std::string GetMediaDescription(uint16_t port = 0) override; std::string GetAttribute() override; bool HandleFrame(MediaChannelId channelId, AVFrame frame) override; static uint32_t GetTimestamp(); private: H265Source(std::vector vps, std::vector sps, std::vector pps, std::vector sei, uint32_t framerate); static FrameType GetRtpFrameType(std::shared_ptr nalUnit); uint32_t framerate_ = 25; std::vector vps_; std::vector sps_; std::vector pps_; std::vector sei_; }; } #endif ================================================ FILE: rtsp-server/xop/MacMd5.cpp ================================================ #include "MacMd5.h" #if defined(__APPLE__) || defined(__MACH__) #include #endif using namespace xop; MacMd5::MacMd5() : Md5() {} MacMd5::~MacMd5() = default; void MacMd5::GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash) { #if defined(__APPLE__) || defined(__MACH__) CC_MD5(data, (CC_LONG)dataSize, outHash); #endif } ================================================ FILE: rtsp-server/xop/MacMd5.h ================================================ #ifndef _XOP_MACMD5_H #define _XOP_MACMD5_H #include "Md5.h" namespace xop { class MacMd5 : public Md5 { public: MacMd5(); ~MacMd5() override; void GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash) override; }; } #endif ================================================ FILE: rtsp-server/xop/Md5.cpp ================================================ #include "Md5.h" using namespace xop; Md5::Md5() = default; Md5::~Md5() = default; void Md5::GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash) { } void Md5::GetMd5Hash(const std::string &str, unsigned char *outHash) { GetMd5Hash(reinterpret_cast(str.c_str()), str.length(), outHash); } std::string Md5::GetMd5HashString(const unsigned char *data, const size_t dataSize) { unsigned char hash[MD5_HASH_LENGTH]; GetMd5Hash(data, dataSize, hash); char hashStr[2 * MD5_HASH_LENGTH + 1]{}; for (size_t i = 0; i < MD5_HASH_LENGTH; i++) { hashStr[2 * i] = hex_value_[((hash[i] >> 4) & 0xF)]; hashStr[2 * i + 1] = hex_value_[(hash[i]) & 0x0F]; } hashStr[2 * MD5_HASH_LENGTH] = '\0'; return hashStr; } std::string Md5::GetMd5HashString(const std::string &str) { return GetMd5HashString( reinterpret_cast(str.c_str()), str.length()); } ================================================ FILE: rtsp-server/xop/Md5.h ================================================ #ifndef _XOP_MD5_H #define _XOP_MD5_H #include #define MD5_HASH_LENGTH 16 namespace xop { class Md5 { public: Md5(); virtual ~Md5(); virtual void GetMd5Hash(const unsigned char *data, size_t dataSize, unsigned char *outHash); void GetMd5Hash(const std::string &str, unsigned char *outHash); std::string GetMd5HashString(const unsigned char *data, size_t dataSize); std::string GetMd5HashString(const std::string &str); private: const char hex_value_[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; }; } #endif ================================================ FILE: rtsp-server/xop/MediaSession.cpp ================================================ // PHZ // 2018-9-30 // Scott Xu // 2020-12-5 Add IPv6 Support. #include "MediaSession.h" #include "RtpConnection.h" #include "RtspConnection.h" #include #include #include #include #include #include "net/Logger.h" using namespace xop; using namespace std; std::atomic_uint MediaSession::last_session_id_(1); MediaSession::MediaSession(std::string url_suffix, const uint8_t max_channel_count) : max_channel_count_(max_channel_count), session_id_(++last_session_id_), suffix_(std::move(url_suffix)), media_sources_(max_channel_count), buffer_(max_channel_count), multicast_port_(max_channel_count, 0), has_new_client_(false) { } MediaSession *MediaSession::CreateNew(std::string url_suffix, const uint32_t max_channel_count) { return new MediaSession(std::move(url_suffix), max_channel_count); } MediaSession::~MediaSession() { if (!multicast_ip6_.empty()) { MulticastAddr::instance().Release(multicast_ip6_); } if (!multicast_ip_.empty()) { MulticastAddr::instance().Release(multicast_ip_); } } void MediaSession::AddNotifyConnectedCallback( const NotifyConnectedCallback &callback) { notify_connected_callbacks_.push_back(callback); } void MediaSession::AddNotifyDisconnectedCallback( const NotifyDisconnectedCallback &callback) { notify_disconnected_callbacks_.push_back(callback); } bool MediaSession::AddSource(MediaChannelId channel_id, MediaSource *source) { if (static_cast(channel_id) >= max_channel_count_) return false; source->SetSendFrameCallback([this](const MediaChannelId channel_id, const RtpPacket &pkt) { std::lock_guard lock(map_mutex_); std::forward_list> clients; //std::map packets; RtpPacket tmp_pkt; memcpy(tmp_pkt.data.get(), pkt.data.get(), pkt.size); tmp_pkt.size = pkt.size; tmp_pkt.last = pkt.last; tmp_pkt.timestamp = pkt.timestamp; tmp_pkt.type = pkt.type; for (auto iter = clients_.begin(); iter != clients_.end(); ++iter) { if (auto conn = iter->second.lock(); conn == nullptr) { clients_.erase(iter); } else { if (conn->IsMulticast()) continue; conn->SendRtpPacket(channel_id, tmp_pkt); } } /*for (auto iter = clients_.begin(); iter != clients_.end();) { if (auto conn = iter->second.lock(); conn == nullptr) { clients_.erase(iter++); } else { if (int id = conn->GetId(); id >= 0) { if (packets.find(id) == packets.end()) { RtpPacket tmp_pkt; memcpy(tmp_pkt.data.get(), pkt.data.get(), pkt.size); tmp_pkt.size = pkt.size; tmp_pkt.last = pkt.last; tmp_pkt.timestamp = pkt.timestamp; tmp_pkt.type = pkt.type; packets.emplace(id, tmp_pkt); } clients.emplace_front(conn); } ++iter; } } int count = 0; for (const auto &iter : clients) { if (int id = iter->GetId(); id >= 0) { if (auto iter2 = packets.find(id); iter2 != packets.end()) { count++; if (const int ret = iter->SendRtpPacket( channel_id, iter2->second); is_multicast_ && ret == 0) { break; } } } }*/ if (is_multicast_) { multicast_v6_client_->SendRtpPacket( channel_id, tmp_pkt); multicast_client_->SendRtpPacket( channel_id, tmp_pkt); } return true; }); media_sources_[static_cast(channel_id)].reset(source); return true; } bool MediaSession::RemoveSource(MediaChannelId channel_id) { media_sources_[static_cast(channel_id)] = nullptr; return true; } bool MediaSession::StartMulticast() { if (task_scheduler_.lock() == nullptr) return false; if (is_multicast_) return true; multicast_ip6_ = MulticastAddr::instance().GetAddr6(); multicast_ip_ = MulticastAddr::instance().GetAddr(); if (multicast_ip6_.empty() || multicast_ip_.empty()) return false; std::random_device rd; for (uint32_t chn = 0; chn < max_channel_count_; chn++) multicast_port_[chn] = htons(rd() & 0xfffe); multicast_v6_client_ = make_shared(GetMaxChannelCount(), task_scheduler_, true); multicast_client_ = make_shared(GetMaxChannelCount(), task_scheduler_, false); for (uint32_t chn = 0; chn < max_channel_count_; chn++) { if (!multicast_v6_client_->SetupRtpOverMulticast( static_cast(chn), multicast_ip6_, multicast_port_[chn])) return false; if (!multicast_client_->SetupRtpOverMulticast( static_cast(chn), multicast_ip_, multicast_port_[chn])) return false; } is_multicast_ = true; return true; } std::string MediaSession::GetSdpMessage(const std::string ip, const std::string &session_name, const bool ipv6) const { if (media_sources_.empty()) return ""; //std::string ip = NetInterface::GetLocalIPAddress(ipv6); char buf[2048] = {0}; snprintf(buf, sizeof(buf), "v=0\r\n" "o=- 9%ld 1 IN IP%d %s\r\n" "t=0 0\r\n" "a=control:*\r\n", static_cast(std::time(nullptr)), ipv6 ? 6 : 4, ip.c_str()); if (!session_name.empty()) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "s=%s\r\n", session_name.c_str()); } if (is_multicast_) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=type:broadcast\r\n" "a=rtcp-unicast:reflection\r\n"); } for (uint32_t chn = 0; chn < max_channel_count_; chn++) { if (media_sources_[chn]) { if (is_multicast_) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s\r\n", media_sources_[chn] ->GetMediaDescription( multicast_port_[chn]) .c_str()); snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "c=IN IP%d %s/%d\r\n", ipv6 ? 6 : 4, ipv6 ? multicast_ip6_.c_str() : multicast_ip_.c_str(), ipv6 ? 255 : 8); } else { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s\r\n", media_sources_[chn] ->GetMediaDescription(0) .c_str()); } snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s\r\n", media_sources_[chn]->GetAttribute().c_str()); snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=control:track%d\r\n", chn); } } return buf; } MediaSource *MediaSession::GetMediaSource(MediaChannelId channel_id) const { if (static_cast(channel_id) < max_channel_count_) { return media_sources_[static_cast(channel_id)].get(); } return nullptr; } bool MediaSession::HandleFrame(MediaChannelId channelId, AVFrame frame) { std::lock_guard lock(mutex_); if (static_cast(channelId) < max_channel_count_) { media_sources_[static_cast(channelId)]->HandleFrame( channelId, std::move(frame)); } else { return false; } return true; } bool MediaSession::AddClient(const SOCKET rtspfd, std::shared_ptr rtp_conn, string ip, uint16_t port) { std::lock_guard lock(map_mutex_); if (const auto iter = clients_.find(rtspfd); iter == clients_.end()) { std::weak_ptr rtp_conn_weak_ptr = rtp_conn; clients_.emplace(rtspfd, rtp_conn_weak_ptr); for (auto &callback : notify_connected_callbacks_) callback(session_id_, ip, port); /* 回调通知当前客户端数量 */ has_new_client_ = true; return true; } return false; } void MediaSession::RemoveClient(const SOCKET rtspfd, std::string ip, uint16_t port) { std::lock_guard lock(map_mutex_); if (const auto iter = clients_.find(rtspfd); iter != clients_.end()) { if (const auto conn = iter->second.lock()) { for (auto &callback : notify_disconnected_callbacks_) callback(session_id_, ip, port); /* 回调通知当前客户端数量 */ } clients_.erase(iter); } } ================================================ FILE: rtsp-server/xop/MediaSession.h ================================================ // PHZ // 2018-6-8 // Scott Xu // 2020-12-5 Add IPv6 Support. #ifndef XOP_MEDIA_SESSION_H #define XOP_MEDIA_SESSION_H #include #include #include #include #include #include #include #include #include #include "media.h" #include "MediaSource.h" #include "net/Socket.h" #include "net/RingBuffer.h" namespace xop { class RtpConnection; class TaskScheduler; class EventLoop; class MediaSession { public: using Ptr = std::shared_ptr; using NotifyConnectedCallback = std::function; using NotifyDisconnectedCallback = std::function; static MediaSession *CreateNew(std::string url_suffix = "live", uint32_t max_channel_count = 2); virtual ~MediaSession(); bool AddSource(MediaChannelId channel_id, MediaSource *source); bool RemoveSource(MediaChannelId channel_id); bool StartMulticast(); void AddNotifyConnectedCallback(const NotifyConnectedCallback &callback); void AddNotifyDisconnectedCallback( const NotifyDisconnectedCallback &callback); std::string GetRtspUrlSuffix() const { return suffix_; } void SetRtspUrlSuffix(const std::string &suffix) { suffix_ = suffix; } std::string GetSdpMessage(std::string ip, const std::string &session_name, bool ipv6 = false) const; MediaSource *GetMediaSource(MediaChannelId channel_id) const; bool HandleFrame(MediaChannelId channel_id, AVFrame frame); bool AddClient(SOCKET rtspfd, std::shared_ptr rtp_conn, std::string ip, uint16_t port); void RemoveClient(SOCKET rtspfd, std::string ip, uint16_t port); MediaSessionId GetMediaSessionId() const { return session_id_; } uint32_t GetNumClient() const { return static_cast(clients_.size()); } uint8_t GetMaxChannelCount() const { return max_channel_count_; } bool IsMulticast() const { return is_multicast_; } std::string GetMulticastIp(const bool ipv6) const { return ipv6 ? multicast_ip6_ : multicast_ip_; } uint16_t GetMulticastPort(MediaChannelId channel_id) const { if (static_cast(channel_id) >= multicast_port_.size()) { return 0; } return multicast_port_[static_cast(channel_id)]; } std::shared_ptr GetMulticastRtpConnection(const bool ipv6) const { return ipv6 ? multicast_v6_client_ : multicast_client_; } private: friend class MediaSource; friend class RtspServer; MediaSession(std::string url_suffix, uint8_t max_channel_count); uint8_t max_channel_count_ = 0; MediaSessionId session_id_ = 0; std::string suffix_; std::vector> media_sources_; std::vector> buffer_; std::vector notify_connected_callbacks_; std::vector notify_disconnected_callbacks_; std::mutex mutex_; std::mutex map_mutex_; std::map> clients_; std::shared_ptr multicast_client_; std::shared_ptr multicast_v6_client_; bool is_multicast_ = false; std::vector multicast_port_; std::string multicast_ip_; std::string multicast_ip6_; std::atomic_bool has_new_client_; static std::atomic_uint last_session_id_; std::weak_ptr task_scheduler_; }; class MulticastAddr { public: static MulticastAddr &instance() { static MulticastAddr s_multi_addr; return s_multi_addr; } std::string GetAddr6() { std::lock_guard lock(mutex_); std::random_device rd; char addr6_str[INET6_ADDRSTRLEN]; for (int n = 0; n <= 10; n++) { in6_addr addr6{}; uint8_t *addr_bytes = addr6.s6_addr; addr_bytes[0] = 0xff; addr_bytes[1] = //flgs: |0|R|P|T| 0x00 << 7 | //0: reserved 0x00 << 6 | //R: default 0x00 << 5 | //P: default 0x01 << 4; //T: dynamic addr_bytes[1] |= 0x02; //scop: Link-Local scope //group id uint32_t group_id = rd(); memcpy(addr_bytes + 2, &group_id, 4); group_id = rd(); memcpy(addr_bytes + 6, &group_id, 4); group_id = rd(); memcpy(addr_bytes + 10, &group_id, 4); group_id = rd(); memcpy(addr_bytes + 14, &group_id, 2); inet_ntop(AF_INET6, &addr6, addr6_str, INET6_ADDRSTRLEN); if (addrs_.find(addr6_str) == addrs_.end()) { addrs_.insert(addr6_str); break; } } return addr6_str; } std::string GetAddr6(const in6_addr local_addr6, uint8_t plen) { std::lock_guard lock(mutex_); std::random_device rd; char addr6_str[INET6_ADDRSTRLEN]; for (int n = 0; n <= 10; n++) { in6_addr addr6{}; uint8_t *addr_bytes = addr6.s6_addr; const uint8_t *local_addr_bytes = local_addr6.s6_addr; addr_bytes[0] = 0xff; addr_bytes[1] = //flgs: |0|R|P|T| 0x00 << 7 | //0: reserved 0x00 << 6 | //R: default 0x01 << 5 | //P: RFC3306 0x01 << 4; //T: dynamic addr_bytes[1] |= 0x02; //scop: Link-Local scope addr_bytes[2] = 0x01; //reserved addr_bytes[3] = plen; //plen //network prefix const size_t plan_size = plen / 8; memcpy(addr_bytes + 4, local_addr_bytes, plan_size); const uint8_t plan_remainder = plen % 8; if (plan_remainder > 0) addr_bytes[4 + plan_size] = local_addr_bytes[plan_size] & (0xff << (8 - plan_remainder)); //group id const uint32_t group_id = rd(); memcpy(addr_bytes + 13, &group_id, sizeof(uint32_t)); inet_ntop(AF_INET6, &addr6, addr6_str, INET6_ADDRSTRLEN); if (addrs_.find(addr6_str) == addrs_.end()) { addrs_.insert(addr6_str); break; } } return addr6_str; } std::string GetAddr() { std::lock_guard lock(mutex_); std::random_device rd; char addr_str[INET_ADDRSTRLEN]; for (int n = 0; n <= 10; n++) { in_addr addr{}; constexpr uint32_t range = 0xE8FFFFFF - 0xE8000100; addr.s_addr = htonl(0xE8000100 + rd() % range); inet_ntop(AF_INET, &addr, addr_str, INET_ADDRSTRLEN); if (addrs_.find(addr_str) == addrs_.end()) { addrs_.insert(addr_str); break; } } return addr_str; } void Release(const std::string &addr) { std::lock_guard lock(mutex_); addrs_.erase(addr); } private: std::mutex mutex_; std::unordered_set addrs_; }; } #endif ================================================ FILE: rtsp-server/xop/MediaSource.h ================================================ // PHZ // 2018-6-8 #ifndef XOP_MEDIA_SOURCE_H #define XOP_MEDIA_SOURCE_H #include "media.h" #include "rtp.h" #include #include #include namespace xop { class MediaSource { public: using SendFrameCallback = std::function; MediaSource() = default; virtual ~MediaSource() = default; virtual MediaType GetMediaType() const { return media_type_; } virtual std::string GetMediaDescription(uint16_t port = 0) = 0; virtual std::string GetAttribute() = 0; virtual bool HandleFrame(MediaChannelId channelId, AVFrame frame) = 0; virtual void SetSendFrameCallback(const SendFrameCallback callback) { send_frame_callback_ = callback; } virtual uint32_t GetPayloadType() const { return payload_; } virtual uint32_t GetClockRate() const { return clock_rate_; } protected: MediaType media_type_ = MediaType::NONE; uint32_t payload_ = 0; uint32_t clock_rate_ = 0; SendFrameCallback send_frame_callback_; }; } #endif ================================================ FILE: rtsp-server/xop/Nal.cpp ================================================ #include "Nal.h" using namespace std; using namespace xop; bool NalHelper::NalUnitWhile(const uint8_t *data, size_t dataSize, NalUnitWhileCallback callback) { if (dataSize == 0) return true; const uint8_t *end = data + dataSize; const uint8_t *nal_start = FindStartCode(data, end); while (true) { while (nal_start < end && !*nal_start++) ; if (nal_start == end) break; const uint8_t *nalEnd = FindStartCode(nal_start, end); if (!callback(const_cast(nal_start), nalEnd - nal_start)) return false; nal_start = nalEnd; } return true; } uint32_t NalHelper::GetNalUnitCount(const uint8_t *data, size_t dataSize) { uint32_t count = 0; NalUnitWhile(data, dataSize, [&count](const uint8_t *, size_t) { count++; return true; }); return count; } const uint8_t *NalHelper::FindStartCode(const uint8_t *p, const uint8_t *end) { const uint8_t *out = FFmpegFindStartcodeInternal(p, end); if (p < out && out < end && !out[-1]) --out; return out; } /* NOTE: I noticed that FFmpeg does some unusual special handling of certain * scenarios that I was unaware of, so instead of just searching for {0, 0, 1} * we'll just use the code from FFmpeg - http://www.ffmpeg.org/ */ const uint8_t *NalHelper::FFmpegFindStartcodeInternal(const uint8_t *p, const uint8_t *end) { const uint8_t *a = p + 4 - (reinterpret_cast(p) & 3); for (end -= 3; p < a && p < end; p++) { if (p[0] == 0 && p[1] == 0 && p[2] == 1) return p; } for (end -= 3; p < end; p += 4) { const uint32_t x = *reinterpret_cast(p); if (x - 0x01010101 & ~x & 0x80808080) { if (p[1] == 0) { if (p[0] == 0 && p[2] == 1) return p; if (p[2] == 0 && p[3] == 1) return p + 1; } if (p[3] == 0) { if (p[2] == 0 && p[4] == 1) return p + 2; if (p[4] == 0 && p[5] == 1) return p + 3; } } } for (end += 3; p < end; p++) { if (p[0] == 0 && p[1] == 0 && p[2] == 1) return p; } return end + 3; } ================================================ FILE: rtsp-server/xop/Nal.h ================================================ #ifndef _XOP_NAL_H #define _XOP_NAL_H #include #include #include #include #include #include #include #include "NalUnit.h" namespace xop { template>> class Nal { public: Nal(const std::vector &data); Nal(const uint8_t *data, size_t dataSize); size_t GetSize() const; size_t GetData(uint8_t **data) const; std::vector GetData() const; std::shared_ptr operator[](size_t index); size_t GetCount(); size_t CopyData(uint8_t *start, size_t size) const; std::shared_ptr GetNalUnitByType(uint8_t nalUnitType); private: const uint8_t *data_; size_t data_size_; std::vector> nal_unit_list_; }; class NalHelper { public: using NalUnitWhileCallback = std::function; static bool NalUnitWhile(const uint8_t *data, size_t dataSize, NalUnitWhileCallback callback); static uint32_t GetNalUnitCount(const uint8_t *data, size_t dataSize); static const uint8_t *FindStartCode(const uint8_t *p, const uint8_t *end); private: NalHelper() = default; static const uint8_t *FFmpegFindStartcodeInternal(const uint8_t *p, const uint8_t *end); }; template Nal::Nal(const std::vector &data) : Nal(data.data(), data.size()) { } template Nal::Nal(const uint8_t *data, size_t dataSize) : data_(data), data_size_(dataSize) { nal_unit_list_.resize(NalHelper::GetNalUnitCount(data, dataSize)); auto it = nal_unit_list_.begin(); auto end = nal_unit_list_.end(); NalHelper::NalUnitWhile( data, dataSize, [&it, end](const uint8_t *unitData, size_t unitDataSize) { *it = std::shared_ptr(static_cast( T::GetNalUnit(unitData, unitDataSize))); if (it++ == end) return false; return true; }); } template size_t Nal::GetSize() const { return data_size_; } template size_t Nal::GetData(uint8_t **data) const { *data = const_cast(data_); return data_size_; } template std::vector Nal::GetData() const { uint8_t *data = nullptr; const auto size = GetData(&data); return std::vector(data, data + size); } template std::shared_ptr Nal::operator[](size_t index) { return nal_unit_list_[index]; } template size_t Nal::GetCount() { return nal_unit_list_.size(); } template size_t Nal::CopyData(uint8_t *start, size_t size) const { if (size > data_size_) size = data_size_; memcpy(start, data_, size); return size; } template std::shared_ptr Nal::GetNalUnitByType(uint8_t nalUnitType) { for (auto iter = nal_unit_list_.begin(); iter != nal_unit_list_.end(); ++iter) { if (iter->get()->GetType() == nalUnitType) return *iter; } return nullptr; } } #endif ================================================ FILE: rtsp-server/xop/NalUnit.cpp ================================================ #include "NalUnit.h" #include using namespace xop; using namespace std; NalUnit::NalUnit(const uint8_t *data, size_t dataSize) : data_(data), data_size_(dataSize) { } size_t NalUnit::GetData(uint8_t **data) const { *data = const_cast(data_); return data_size_; } vector NalUnit::GetData() const { uint8_t *data = nullptr; const auto size = GetData(&data); return vector(data, data + size); } size_t NalUnit::CopyData(uint8_t *start, size_t size, size_t skip) const { if (skip > data_size_) return 0; if (skip + size > data_size_) size = data_size_ - skip; memcpy(start, data_ + skip, size); return size; } size_t NalUnit::GetSize() const { return data_size_; } std::vector NalUnit::GetHeader() { uint8_t *data = nullptr; const auto size = GetHeader(&data); return vector(data, data + size); } std::vector NalUnit::GetBody() { uint8_t *data = nullptr; const auto size = GetBody(&data); return vector(data, data + size); } NalUnit *NalUnit::GetNalUnit(const uint8_t *, size_t) { return nullptr; } ================================================ FILE: rtsp-server/xop/NalUnit.h ================================================ #ifndef _XOP_NALUNIT_H #define _XOP_NALUNIT_H #include #include #include namespace xop { class NalUnit { public: size_t GetData(uint8_t **data) const; std::vector GetData() const; size_t CopyData(uint8_t *start, size_t size, size_t skip = 0) const; size_t GetSize() const; virtual uint8_t GetType() = 0; virtual size_t GetHeader(uint8_t **data) = 0; std::vector GetHeader(); virtual size_t CopyHeader(uint8_t *start, size_t size) = 0; virtual size_t GetHeaderSize() = 0; virtual size_t GetBody(uint8_t **data) = 0; std::vector GetBody(); virtual size_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) = 0; virtual size_t GetBodySize() = 0; virtual bool IsIdrFrame() = 0; virtual bool IsFrame() = 0; static NalUnit* GetNalUnit(const uint8_t *data, size_t dataSize); protected: NalUnit(const uint8_t *data, size_t dataSize); private: const uint8_t *data_; size_t data_size_; }; } #endif ================================================ FILE: rtsp-server/xop/RtpConnection.cpp ================================================ // PHZ // 2018-9-30 // Scott Xu // 2020-12-5 Add IPv6 Support. #include "RtpConnection.h" #include "RtspConnection.h" #include "net/SocketUtil.h" #include "net/TaskScheduler.h" using namespace std; using namespace xop; RtpConnection::RtpConnection(std::weak_ptr rtsp_connection, const uint8_t max_channel_count) : RtpConnection(max_channel_count, rtsp_connection.lock()->GetTaskScheduler(), rtsp_connection.lock()->IsIpv6()) { rtsp_connection_ = rtsp_connection; } RtpConnection::RtpConnection(const uint8_t max_channel_count, std::weak_ptr task_scheduler, bool ipv6) : max_channel_count_(max_channel_count), task_scheduler_(task_scheduler), transport_mode_(TransportMode::NONE), local_rtp_port_(max_channel_count), local_rtcp_port_(max_channel_count), rtpfd_(max_channel_count, 0), rtcpfd_(max_channel_count, 0), peer_rtp_addr_(max_channel_count), peer_rtcp_sddr_(max_channel_count), media_channel_info_(max_channel_count), ipv6_(ipv6) { std::random_device rd; for (uint8_t chn = 0; chn < max_channel_count; chn++) { memset(&media_channel_info_[chn], 0, sizeof media_channel_info_[chn]); media_channel_info_[chn].rtp_header.version = RTP_VERSION; media_channel_info_[chn].packet_seq = rd() & 0xffff; media_channel_info_[chn].rtp_header.seq = 0; //htons(1); media_channel_info_[chn].rtp_header.ts = htonl(rd()); media_channel_info_[chn].rtp_header.ssrc = htonl(rd()); } } RtpConnection::~RtpConnection() { for (uint8_t chn = 0; chn < max_channel_count_; chn++) { if (rtpfd_[chn] > 0) { SocketUtil::Close(rtpfd_[chn]); } if (rtcpfd_[chn] > 0) { SocketUtil::Close(rtcpfd_[chn]); } } } int RtpConnection::GetId() const { return task_scheduler_.lock()->GetId(); } bool RtpConnection::SetupRtpOverTcp(MediaChannelId channel_id, const uint8_t rtp_channel, const uint8_t rtcp_channel) { const auto conn = rtsp_connection_.lock(); if (!conn) { return false; } media_channel_info_[static_cast(channel_id)].rtp_channel = rtp_channel; media_channel_info_[static_cast(channel_id)].rtcp_channel = rtcp_channel; rtpfd_[static_cast(channel_id)] = conn->GetSocket(); rtcpfd_[static_cast(channel_id)] = conn->GetSocket(); media_channel_info_[static_cast(channel_id)].is_setup = true; transport_mode_ = TransportMode::RTP_OVER_TCP; return true; } bool RtpConnection::SetupRtpOverUdp(MediaChannelId channel_id, const uint16_t rtp_port, const uint16_t rtcp_port) { const auto conn = rtsp_connection_.lock(); if (!conn) { return false; } if (ipv6_ ? SocketUtil::GetPeerAddr6(conn->GetSocket(), &peer_addr_) : SocketUtil::GetPeerAddr( conn->GetSocket(), reinterpret_cast(&peer_addr_)) < 0) { return false; } media_channel_info_[static_cast(channel_id)].rtp_port = rtp_port; media_channel_info_[static_cast(channel_id)].rtcp_port = rtcp_port; std::random_device rd; for (int n = 0; n <= 10; n++) { if (n == 10) { return false; } local_rtp_port_[static_cast(channel_id)] = rd() & 0xfffe; local_rtcp_port_[static_cast(channel_id)] = local_rtp_port_[static_cast(channel_id)] + 1; rtpfd_[static_cast(channel_id)] = socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); if (!SocketUtil::Bind( rtpfd_[static_cast(channel_id)], ipv6_ ? "::0" : "0.0.0.0", //TODO: Bing all address? local_rtp_port_[static_cast(channel_id)], ipv6_)) { SocketUtil::Close( rtpfd_[static_cast(channel_id)]); continue; } rtcpfd_[static_cast(channel_id)] = socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); if (!SocketUtil::Bind( rtcpfd_[static_cast(channel_id)], ipv6_ ? "::0" : "0.0.0.0", //TODO: Bing all address? local_rtcp_port_[static_cast(channel_id)], ipv6_)) { SocketUtil::Close( rtpfd_[static_cast(channel_id)]); SocketUtil::Close( rtcpfd_[static_cast(channel_id)]); continue; } break; } SocketUtil::SetSendBufSize(rtpfd_[static_cast(channel_id)], 50 * 1024); if (ipv6_) { const auto peer_addr = &peer_addr_; const auto peer_rtp_addr = &peer_rtp_addr_[static_cast(channel_id)]; const auto peer_rtcp_sddr = &peer_rtcp_sddr_[static_cast(channel_id)]; peer_rtp_addr->sin6_family = AF_INET6; peer_rtp_addr->sin6_addr = peer_addr->sin6_addr; peer_rtp_addr->sin6_port = htons( media_channel_info_[static_cast(channel_id)] .rtp_port); peer_rtcp_sddr->sin6_family = AF_INET6; peer_rtcp_sddr->sin6_addr = peer_addr->sin6_addr; peer_rtcp_sddr->sin6_port = htons( media_channel_info_[static_cast(channel_id)] .rtcp_port); } else { const auto peer_addr = reinterpret_cast(&peer_addr_); const auto peer_rtp_addr = reinterpret_cast( &peer_rtp_addr_[static_cast(channel_id)]); const auto peer_rtcp_sddr = reinterpret_cast( &peer_rtcp_sddr_[static_cast(channel_id)]); peer_rtp_addr->sin_family = AF_INET; peer_rtp_addr->sin_addr = peer_addr->sin_addr; peer_rtp_addr->sin_port = htons( media_channel_info_[static_cast(channel_id)] .rtp_port); peer_rtcp_sddr->sin_family = AF_INET; peer_rtcp_sddr->sin_addr = peer_addr->sin_addr; peer_rtcp_sddr->sin_port = htons( media_channel_info_[static_cast(channel_id)] .rtcp_port); } media_channel_info_[static_cast(channel_id)].is_setup = true; transport_mode_ = TransportMode::RTP_OVER_UDP; return true; } bool RtpConnection::SetupRtpOverMulticast(MediaChannelId channel_id, const std::string &ip, const uint16_t port) { std::random_device rd; for (int n = 0; n <= 10; n++) { if (n == 10) { return false; } local_rtp_port_[static_cast(channel_id)] = rd() & 0xfffe; rtpfd_[static_cast(channel_id)] = ::socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0); if (!SocketUtil::Bind( rtpfd_[static_cast(channel_id)], ipv6_ ? "::0" : "0.0.0.0", local_rtp_port_[static_cast(channel_id)], ipv6_)) { SocketUtil::Close( rtpfd_[static_cast(channel_id)]); continue; } break; } media_channel_info_[static_cast(channel_id)].rtp_port = port; if (ipv6_) { const auto peer_rtp_addr = &peer_rtp_addr_[static_cast(channel_id)]; peer_rtp_addr->sin6_family = AF_INET6; peer_rtp_addr->sin6_port = htons(port); inet_pton(AF_INET6, ip.c_str(), &peer_rtp_addr->sin6_addr); } else { const auto peer_rtp_addr = reinterpret_cast( &peer_rtp_addr_[static_cast(channel_id)]); peer_rtp_addr->sin_family = AF_INET; peer_rtp_addr->sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &peer_rtp_addr->sin_addr); } media_channel_info_[static_cast(channel_id)].is_setup = true; transport_mode_ = TransportMode::RTP_OVER_MULTICAST; is_multicast_ = true; return true; } void RtpConnection::Play() { for (uint8_t chn = 0; chn < max_channel_count_; chn++) { if (media_channel_info_[chn].is_setup) { media_channel_info_[chn].is_play = true; } } } void RtpConnection::Record() { for (uint8_t chn = 0; chn < max_channel_count_; chn++) { if (media_channel_info_[chn].is_setup) { media_channel_info_[chn].is_record = true; media_channel_info_[chn].is_play = true; } } } void RtpConnection::Teardown() { if (!is_closed_ && transport_mode_ != TransportMode::RTP_OVER_MULTICAST) { is_closed_ = true; for (uint8_t chn = 0; chn < max_channel_count_; chn++) { media_channel_info_[chn].is_play = false; media_channel_info_[chn].is_record = false; } } } string RtpConnection::GetMulticastIp(MediaChannelId channel_id) { if (ipv6_) { const auto peer_rtp_addr = &peer_rtp_addr_[static_cast(channel_id)]; char str[INET6_ADDRSTRLEN] = "::0"; inet_ntop(AF_INET6, &peer_rtp_addr->sin6_addr, str, sizeof str); return str; } else { const auto peer_rtp_addr = reinterpret_cast( &peer_rtp_addr_[static_cast(channel_id)]); char str[INET_ADDRSTRLEN] = "0.0.0.0"; inet_ntop(AF_INET, &peer_rtp_addr->sin_addr, str, sizeof str); return str; } } string RtpConnection::GetRtpInfo(const std::string &rtsp_url) const { char buf[2048] = {0}; snprintf(buf, 1024, "RTP-Info: "); int num_channel = 0; const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); const auto ts = time_point.time_since_epoch().count(); for (int chn = 0; chn < max_channel_count_; chn++) { const auto rtpTime = static_cast( ts * media_channel_info_[chn].clock_rate / 1000); if (media_channel_info_[chn].is_setup) { if (num_channel != 0) { snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ","); } snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "url=%s/track%d;seq=0;rtptime=%u", rtsp_url.c_str(), chn, rtpTime); num_channel++; } } return buf; } void RtpConnection::SetFrameType(const FrameType frame_type) { frame_type_ = frame_type; if (!has_key_frame_ && (frame_type == FrameType::NONE || frame_type == FrameType::VIDEO_FRAME_IDR)) { has_key_frame_ = true; } } void RtpConnection::SetRtpHeader(MediaChannelId channel_id, const RtpPacket &pkt) { if ((media_channel_info_[static_cast(channel_id)].is_play || media_channel_info_[static_cast(channel_id)].is_record) && has_key_frame_) { media_channel_info_[static_cast(channel_id)] .rtp_header.marker = pkt.last; media_channel_info_[static_cast(channel_id)] .rtp_header.ts = htonl(pkt.timestamp); media_channel_info_[static_cast(channel_id)] .rtp_header.seq = htons( media_channel_info_[static_cast(channel_id)] .packet_seq++); memcpy(pkt.data.get() + 4, &media_channel_info_[static_cast(channel_id)] .rtp_header, RTP_HEADER_SIZE); } } int RtpConnection::SendRtpPacket(MediaChannelId channel_id, const RtpPacket &pkt) { if (is_closed_) { return -1; } const bool ret = task_scheduler_.lock()->AddTriggerEvent([this, channel_id, pkt] { this->SetFrameType(pkt.type); this->SetRtpHeader(channel_id, pkt); if ((media_channel_info_[static_cast(channel_id)] .is_play || media_channel_info_[static_cast(channel_id)] .is_record) && has_key_frame_) { if (transport_mode_ == TransportMode::RTP_OVER_TCP) { SendRtpOverTcp(channel_id, pkt); } else if (transport_mode_ == TransportMode::RTP_OVER_UDP || transport_mode_ == TransportMode::RTP_OVER_MULTICAST) { SendRtpOverUdp(channel_id, pkt); } //media_channel_info_[channel_id].octetCount += pkt.size; //media_channel_info_[channel_id].packetCount += 1; } }); return ret ? 0 : -1; } int RtpConnection::SendRtpOverTcp(MediaChannelId channel_id, const RtpPacket &pkt) const { const auto conn = rtsp_connection_.lock(); if (!conn) { return -1; } uint8_t *rtpPktPtr = pkt.data.get(); rtpPktPtr[0] = '$'; rtpPktPtr[1] = media_channel_info_[static_cast(channel_id)] .rtp_channel; rtpPktPtr[2] = static_cast((pkt.size - 4 & 0xFF00) >> 8); rtpPktPtr[3] = static_cast(pkt.size - 4 & 0xFF); conn->Send(reinterpret_cast(rtpPktPtr), pkt.size); return pkt.size; } int RtpConnection::SendRtpOverUdp(MediaChannelId channel_id, const RtpPacket &pkt) { const int ret = sendto( rtpfd_[static_cast(channel_id)], reinterpret_cast(pkt.data.get()) + 4, pkt.size - 4, 0, reinterpret_cast( &peer_rtp_addr_[static_cast(channel_id)]), ipv6_ ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); if (ret < 0) { Teardown(); return -1; } return ret; } ================================================ FILE: rtsp-server/xop/RtpConnection.h ================================================ // PHZ // 2018-6-8 // Scott Xu // 2020-12-5 Add IPv6 Support. #ifndef XOP_RTP_CONNECTION_H #define XOP_RTP_CONNECTION_H #include #include #include #include #include #include "rtp.h" #include "media.h" #include "net/Socket.h" namespace xop { class RtspConnection; class TaskScheduler; class RtpConnection { public: RtpConnection(std::weak_ptr rtsp_connection, uint8_t max_channel_count); RtpConnection(uint8_t max_channel_count, std::weak_ptr task_scheduler, bool ipv6 = false); virtual ~RtpConnection(); void SetClockRate(MediaChannelId channel_id, const uint32_t clock_rate) { media_channel_info_[static_cast(channel_id)] .clock_rate = clock_rate; } void SetPayloadType(MediaChannelId channel_id, const uint32_t payload) { media_channel_info_[static_cast(channel_id)] .rtp_header.payload = payload; } bool SetupRtpOverTcp(MediaChannelId channel_id, uint8_t rtp_channel, uint8_t rtcp_channel); bool SetupRtpOverUdp(MediaChannelId channel_id, uint16_t rtp_port, uint16_t rtcp_port); bool SetupRtpOverMulticast(MediaChannelId channel_id, const std::string &ip, uint16_t port); uint16_t GetRtpSessionId() const { return static_cast(reinterpret_cast(this)); } uint16_t GetRtpPort(MediaChannelId channel_id) const { return local_rtp_port_[static_cast(channel_id)]; } uint16_t GetRtcpPort(MediaChannelId channel_id) const { return local_rtcp_port_[static_cast(channel_id)]; } SOCKET GetRtcpfd(MediaChannelId channel_id) const { return rtcpfd_[static_cast(channel_id)]; } bool IsMulticast() const { return is_multicast_; } bool IsSetup(MediaChannelId channel_id) const { return media_channel_info_[static_cast(channel_id)] .is_setup; } std::string GetMulticastIp(MediaChannelId channel_id); void Play(); void Record(); void Teardown(); std::string GetRtpInfo(const std::string &rtsp_url) const; int SendRtpPacket(MediaChannelId channel_id, const RtpPacket &pkt); bool IsClosed() const { return is_closed_; } int GetId() const; bool HasKeyFrame() const { return has_key_frame_; } private: friend class RtspConnection; friend class MediaSession; void SetFrameType(FrameType frameType = FrameType::NONE); void SetRtpHeader(MediaChannelId channel_id, const RtpPacket &pkt); int SendRtpOverTcp(MediaChannelId channel_id, const RtpPacket &pkt) const; int SendRtpOverUdp(MediaChannelId channel_id, const RtpPacket &pkt); uint8_t max_channel_count_ = 0; std::weak_ptr rtsp_connection_; std::weak_ptr task_scheduler_; TransportMode transport_mode_; bool is_multicast_ = false; bool is_closed_ = false; bool has_key_frame_ = false; FrameType frame_type_ = FrameType::NONE; std::vector local_rtp_port_; std::vector local_rtcp_port_; std::vector rtpfd_; std::vector rtcpfd_; sockaddr_in6 peer_addr_{}; std::vector peer_rtp_addr_; std::vector peer_rtcp_sddr_; std::vector media_channel_info_; bool ipv6_; }; } #endif ================================================ FILE: rtsp-server/xop/RtspConnection.cpp ================================================ // PHZ // 2018-6-10 // Scott Xu // 2020-12-5 Add IPv6 Support. #include #include "RtspConnection.h" #include "RtspServer.h" #include "MediaSession.h" #include "MediaSource.h" #include "net/SocketUtil.h" #define USER_AGENT "-_-" #define RTSP_DEBUG 0 #define MAX_RTSP_MESSAGE_SIZE 2048 using namespace xop; using namespace std; RtspConnection::RtspConnection(const SOCKET sockfd, std::shared_ptr task_scheduler, const std::shared_ptr &rtsp) : TcpConnection(sockfd, std::move(task_scheduler)), alive_count_(1), rtsp_(rtsp) //, rtp_channel_(new Channel(sockfd)) , rtsp_request_(new RtspRequest), rtsp_response_(new RtspResponse) { this->SetReadCallback([this](Weak /*conn*/, BufferReader &buffer) { return this->OnRead(buffer); }); this->SetCloseCallback([this](const Weak & /*conn*/) { this->OnClose(); }); /*rtp_channel_->SetReadCallback([this]() { this->HandleRead(); }); rtp_channel_->SetWriteCallback([this]() { this->HandleWrite(); }); rtp_channel_->SetCloseCallback([this]() { this->HandleClose(); }); rtp_channel_->SetErrorCallback([this]() { this->HandleError(); });*/ if (rtsp->has_auth_info_) { has_auth_ = false; auth_info_.reset(new DigestAuthentication( rtsp->realm_, rtsp->username_, rtsp->password_)); } } RtspConnection::~RtspConnection() = default; bool RtspConnection::OnRead(BufferReader &buffer) { KeepAlive(); if (const size_t size = buffer.ReadableBytes(); size <= 0) { return false; //close } if (conn_mode_ == ConnectionMode::RTSP_SERVER) { if (!HandleRtspRequest(buffer)) { return false; } } else if (conn_mode_ == ConnectionMode::RTSP_PUSHER) { if (!HandleRtspResponse(buffer)) { return false; } } if (buffer.ReadableBytes() > MAX_RTSP_MESSAGE_SIZE) { buffer.RetrieveAll(); } return true; } void RtspConnection::OnClose() { if (session_id_ != 0) { if (const auto rtsp = rtsp_.lock()) { if (const MediaSession::Ptr media_session = rtsp->LookMediaSession(session_id_)) { media_session->RemoveClient(this->GetSocket(), GetIp(), GetPort()); } } } for (auto iter = rtcp_channels_.begin(); iter != rtcp_channels_.end();) { if (auto channel = iter->second; !channel->IsNoneEvent()) { GetTaskScheduler()->RemoveChannel(channel); rtcp_channels_.erase(iter++); } else ++iter; } } bool RtspConnection::HandleRtspRequest(BufferReader &buffer) { #if RTSP_DEBUG string str(buffer.Peek(), buffer.ReadableBytes()); if (str.find("rtsp") != string::npos || str.find("RTSP") != string::npos) { std::cout << str << std::endl; } #endif if (rtsp_request_->ParseRequest(&buffer)) { const RtspRequest::Method method = rtsp_request_->GetMethod(); if (method == RtspRequest::Method::RTCP) { HandleRtcp(buffer); return true; } if (!rtsp_request_->GotAll()) { return true; } switch (method) { case RtspRequest::Method::OPTIONS: HandleCmdOption(); break; case RtspRequest::Method::DESCRIBE: HandleCmdDescribe(); break; case RtspRequest::Method::SETUP: HandleCmdSetup(); break; case RtspRequest::Method::PLAY: HandleCmdPlay(); break; case RtspRequest::Method::TEARDOWN: HandleCmdTeardown(); break; case RtspRequest::Method::GET_PARAMETER: HandleCmdGetParamter(); break; default: break; } if (rtsp_request_->GotAll()) { rtsp_request_->Reset(); } } else { return false; } return true; } bool RtspConnection::HandleRtspResponse(BufferReader &buffer) { #if RTSP_DEBUG string str(buffer.Peek(), buffer.ReadableBytes()); if (str.find("rtsp") != string::npos || str.find("RTSP") != string::npos) { cout << str << endl; } #endif if (rtsp_response_->ParseResponse(&buffer)) { switch (rtsp_response_->GetMethod()) { case RtspResponse::Method::OPTIONS: if (conn_mode_ == ConnectionMode::RTSP_PUSHER) { SendAnnounce(); } break; case RtspResponse::Method::ANNOUNCE: case RtspResponse::Method::DESCRIBE: SendSetup(); break; case RtspResponse::Method::SETUP: SendSetup(); break; case RtspResponse::Method::RECORD: HandleRecord(); break; default: break; } } else { return false; } return true; } void RtspConnection::SendRtspMessage(const std::shared_ptr buf, const uint32_t size) { #if RTSP_DEBUG cout << buf.get() << endl; #endif this->Send(buf, size); return; } void RtspConnection::HandleRtcp(BufferReader &buffer) { if (const char *peek = buffer.Peek(); peek[0] == '$' && buffer.ReadableBytes() > 4) { if (const size_t pkt_size = peek[2] << 8 | peek[3]; pkt_size + 4 >= buffer.ReadableBytes()) { buffer.Retrieve(pkt_size + 4); } } } void RtspConnection::HandleRtcp(const SOCKET sockfd) { if (char buf[1024] = {0}; recv(sockfd, buf, 1024, 0) > 0) { KeepAlive(); } } void RtspConnection::HandleCmdOption() { const std::shared_ptr res(new char[2048], std::default_delete()); const int size = rtsp_request_->BuildOptionRes(res.get(), 2048); this->SendRtspMessage(res, size); } void RtspConnection::HandleCmdDescribe() { if (auth_info_ != nullptr && !HandleAuthentication()) { return; } int size; const std::shared_ptr res(new char[4096], std::default_delete()); MediaSession::Ptr media_session = nullptr; const auto rtsp = rtsp_.lock(); if (rtsp) { media_session = rtsp->LookMediaSession( rtsp_request_->GetRtspUrlSuffix()); } if (!rtp_conn_ && media_session) { if (media_session->IsMulticast()) rtp_conn_ = media_session->GetMulticastRtpConnection( IsIpv6()); else rtp_conn_ = make_shared( std::dynamic_pointer_cast( shared_from_this()), media_session->GetMaxChannelCount()); } if (!rtsp || !media_session) { size = rtsp_request_->BuildNotFoundRes(res.get(), 4096); } else { session_id_ = media_session->GetMediaSessionId(); media_session->AddClient(this->GetSocket(), rtp_conn_, GetIp(), GetPort()); for (uint16_t chn = 0; chn < media_session->GetMaxChannelCount(); chn++) { if (MediaSource *source = media_session->GetMediaSource( static_cast(chn)); source != nullptr) { rtp_conn_->SetClockRate( static_cast(chn), source->GetClockRate()); rtp_conn_->SetPayloadType( static_cast(chn), source->GetPayloadType()); } } const auto sdp = media_session->GetSdpMessage( SocketUtil::GetSocketIp(GetSocket(), IsIpv6()), rtsp->GetVersion(), IsIpv6()); if (sdp.empty()) { size = rtsp_request_->BuildServerErrorRes(res.get(), 4096); } else { size = rtsp_request_->BuildDescribeRes(res.get(), 4096, sdp.c_str()); } } SendRtspMessage(res, size); } void RtspConnection::HandleCmdSetup() { if (auth_info_ != nullptr && !HandleAuthentication()) { return; } int size; const std::shared_ptr res(new char[4096], std::default_delete()); MediaChannelId channel_id = rtsp_request_->GetChannelId(); MediaSession::Ptr media_session = nullptr; const auto rtsp = rtsp_.lock(); if (rtsp && session_id_) { media_session = rtsp->LookMediaSession(session_id_); } if (!rtsp || !media_session) { goto server_error; } if (media_session->IsMulticast()) { if (rtsp_request_->GetTransportMode() == TransportMode::RTP_OVER_MULTICAST) { const std::string multicast_ip = media_session->GetMulticastIp(IsIpv6()); const uint16_t port = media_session->GetMulticastPort(channel_id); const uint16_t session_id = rtp_conn_->GetRtpSessionId(); /*if (!rtp_conn_->SetupRtpOverMulticast( channel_id, multicast_ip, port)) { goto server_error; }*/ if (!rtp_conn_->IsSetup(channel_id)) goto server_error; size = rtsp_request_->BuildSetupMulticastRes( res.get(), 4096, multicast_ip.c_str(), port, session_id); } else { goto transport_unsupport; } } else { if (rtsp_request_->GetTransportMode() == TransportMode::RTP_OVER_TCP) { const uint8_t rtp_channel = rtsp_request_->GetRtpChannel(); const uint8_t rtcp_channel = rtsp_request_->GetRtcpChannel(); const uint16_t session_id = rtp_conn_->GetRtpSessionId(); rtp_conn_->SetupRtpOverTcp(channel_id, rtp_channel, rtcp_channel); size = rtsp_request_->BuildSetupTcpRes(res.get(), 4096, rtp_channel, rtcp_channel, session_id); } else if (rtsp_request_->GetTransportMode() == TransportMode::RTP_OVER_UDP) { const uint16_t peer_rtp_port = rtsp_request_->GetRtpPort(); const uint16_t peer_rtcp_port = rtsp_request_->GetRtcpPort(); const uint16_t session_id = rtp_conn_->GetRtpSessionId(); if (rtp_conn_->SetupRtpOverUdp(channel_id, peer_rtp_port, peer_rtcp_port)) { auto rtcp_fd = rtp_conn_->GetRtcpfd(channel_id); const auto channel = make_shared(rtcp_fd); channel->SetReadCallback([rtcp_fd, this]() { this->HandleRtcp(rtcp_fd); }); channel->EnableReading(); GetTaskScheduler()->UpdateChannel(channel); rtcp_channels_[static_cast(channel_id)] = channel; } else { goto server_error; } const uint16_t serRtpPort = rtp_conn_->GetRtpPort(channel_id); const uint16_t serRtcpPort = rtp_conn_->GetRtcpPort(channel_id); size = rtsp_request_->BuildSetupUdpRes(res.get(), 4096, serRtpPort, serRtcpPort, session_id); } else { goto transport_unsupport; } } SendRtspMessage(res, size); return; transport_unsupport: size = rtsp_request_->BuildUnsupportedRes(res.get(), 4096); SendRtspMessage(res, size); return; server_error: size = rtsp_request_->BuildServerErrorRes(res.get(), 4096); SendRtspMessage(res, size); } void RtspConnection::HandleCmdPlay() { if (auth_info_ != nullptr) { if (!HandleAuthentication()) { return; } } if (rtp_conn_ == nullptr) { return; } conn_state_ = ConnectionState::START_PLAY; rtp_conn_->Play(); const uint16_t session_id = rtp_conn_->GetRtpSessionId(); const std::shared_ptr res(new char[2048], std::default_delete()); const int size = rtsp_request_->BuildPlayRes(res.get(), 2048, nullptr, session_id); SendRtspMessage(res, size); } void RtspConnection::HandleCmdTeardown() { if (rtp_conn_ == nullptr) { return; } rtp_conn_->Teardown(); const uint16_t session_id = rtp_conn_->GetRtpSessionId(); const std::shared_ptr res(new char[2048], std::default_delete()); const int size = rtsp_request_->BuildTeardownRes(res.get(), 2048, session_id); SendRtspMessage(res, size); //HandleClose(); } void RtspConnection::HandleCmdGetParamter() { if (rtp_conn_ == nullptr) { return; } const uint16_t session_id = rtp_conn_->GetRtpSessionId(); const std::shared_ptr res(new char[2048], std::default_delete()); const int size = rtsp_request_->BuildGetParamterRes(res.get(), 2048, session_id); SendRtspMessage(res, size); } bool RtspConnection::HandleAuthentication() { if (auth_info_ != nullptr && !has_auth_) { const std::string cmd = rtsp_request_->MethodToString[static_cast( rtsp_request_->GetMethod())]; if (const std::string url = rtsp_request_->GetRtspUrl(); !nonce_.empty() && (auth_info_->GetResponse(nonce_, cmd, url) == rtsp_request_->GetAuthResponse())) { nonce_.clear(); has_auth_ = true; } else { const std::shared_ptr res( new char[4096], std::default_delete()); nonce_ = auth_info_->GetNonce(); const int size = rtsp_request_->BuildUnauthorizedRes( res.get(), 4096, auth_info_->GetRealm().c_str(), nonce_.c_str()); SendRtspMessage(res, size); return false; } } return true; } void RtspConnection::SendOptions(const ConnectionMode mode) { const auto rtsp = rtsp_.lock(); if (!rtsp) { HandleClose(); return; } const auto media_session = rtsp->LookMediaSession(1); if (rtp_conn_ == nullptr) { if (media_session->IsMulticast()) rtp_conn_ = media_session->GetMulticastRtpConnection(IsIpv6()); else rtp_conn_ = make_shared( std::dynamic_pointer_cast( shared_from_this()), media_session->GetMaxChannelCount()); } conn_mode_ = mode; rtsp_response_->SetUserAgent(USER_AGENT); rtsp_response_->SetRtspUrl(rtsp->GetRtspUrl().c_str()); const std::shared_ptr req(new char[2048], std::default_delete()); const int size = rtsp_response_->BuildOptionReq(req.get(), 2048); SendRtspMessage(req, size); } void RtspConnection::SendAnnounce() { MediaSession::Ptr media_session = nullptr; const auto rtsp = rtsp_.lock(); if (rtsp) { media_session = rtsp->LookMediaSession(1); } if (!rtsp || !media_session) { HandleClose(); return; } session_id_ = media_session->GetMediaSessionId(); media_session->AddClient(this->GetSocket(), rtp_conn_, GetIp(), GetPort()); for (uint16_t chn = 0; chn < media_session->GetMaxChannelCount(); chn++) { if (MediaSource *source = media_session->GetMediaSource( static_cast(chn)); source != nullptr) { rtp_conn_->SetClockRate( static_cast(chn), source->GetClockRate()); rtp_conn_->SetPayloadType( static_cast(chn), source->GetPayloadType()); } } const auto sdp = media_session->GetSdpMessage( SocketUtil::GetSocketIp(GetSocket(), IsIpv6()), rtsp->GetVersion(), IsIpv6()); if (sdp.empty()) { HandleClose(); return; } const std::shared_ptr req(new char[4096], std::default_delete()); const int size = rtsp_response_->BuildAnnounceReq(req.get(), 4096, sdp.c_str()); SendRtspMessage(req, size); } void RtspConnection::SendDescribe() { const std::shared_ptr req(new char[2048], std::default_delete()); const int size = rtsp_response_->BuildDescribeReq(req.get(), 2048); SendRtspMessage(req, size); } void RtspConnection::SendSetup() { int size = 0; const std::shared_ptr buf(new char[2048], std::default_delete()); MediaSession::Ptr media_session = nullptr; const auto rtsp = rtsp_.lock(); if (rtsp) { media_session = rtsp->LookMediaSession(session_id_); } if (!rtsp || !media_session) { HandleClose(); return; } for (uint16_t chn = 0; chn < media_session->GetMaxChannelCount(); chn++) { if (const auto mediaChannelId = static_cast(chn); media_session->GetMediaSource(mediaChannelId) && !rtp_conn_->IsSetup(mediaChannelId)) { rtp_conn_->SetupRtpOverTcp(mediaChannelId, chn * 2, chn * 2 + 1); size = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048, chn); } else { size = rtsp_response_->BuildRecordReq(buf.get(), 2048); } } /*if (media_session->GetMediaSource(MediaChannelId::channel_0) && !rtp_conn_->IsSetup(MediaChannelId::channel_0)) { rtp_conn_->SetupRtpOverTcp(MediaChannelId::channel_0, 0, 1); size = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048, static_cast(MediaChannelId::channel_0)); } else if (media_session->GetMediaSource(MediaChannelId::channel_1) && !rtp_conn_->IsSetup(MediaChannelId::channel_1)) { rtp_conn_->SetupRtpOverTcp(MediaChannelId::channel_1, 2, 3); size = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048, static_cast(MediaChannelId::channel_1)); } else { size = rtsp_response_->BuildRecordReq(buf.get(), 2048); }*/ SendRtspMessage(buf, size); } void RtspConnection::HandleRecord() { conn_state_ = ConnectionState::START_PUSH; rtp_conn_->Record(); } ================================================ FILE: rtsp-server/xop/RtspConnection.h ================================================ // PHZ // 2018-6-8 // Scott Xu // 2020-12-5 Add IPv6 Support. #ifndef _RTSP_CONNECTION_H #define _RTSP_CONNECTION_H #include "net/TcpConnection.h" #include "RtpConnection.h" #include "RtspMessage.h" #include "DigestAuthentication.h" #include "rtsp.h" #include #include #include namespace xop { class RtspConnection : public TcpConnection { public: using CloseCallback = std::function; enum class ConnectionMode { RTSP_SERVER, RTSP_PUSHER, //RTSP_CLIENT, }; enum class ConnectionState { START_CONNECT, START_PLAY, START_PUSH }; RtspConnection() = delete; RtspConnection(SOCKET sockfd, std::shared_ptr task_scheduler, const std::shared_ptr &rtsp); ~RtspConnection() override; MediaSessionId GetMediaSessionId() const { return session_id_; } void KeepAlive() { ++alive_count_; } bool IsAlive() const { if (IsClosed()) { return false; } if (rtp_conn_ != nullptr) { if (rtp_conn_->IsMulticast()) { return true; } } return (alive_count_ > 0); } void ResetAliveCount() { alive_count_ = 0; } int GetId() const { return GetTaskScheduler()->GetId(); } bool IsPlay() const { return conn_state_ == ConnectionState::START_PLAY; } bool IsRecord() const { return conn_state_ == ConnectionState::START_PUSH; } private: friend class RtpConnection; friend class MediaSession; friend class RtspServer; friend class RtspPusher; bool OnRead(BufferReader &buffer); void OnClose(); void HandleRtcp(SOCKET sockfd); static void HandleRtcp(BufferReader &buffer); bool HandleRtspRequest(BufferReader &buffer); bool HandleRtspResponse(BufferReader &buffer); void SendRtspMessage(std::shared_ptr buf, uint32_t size); void HandleCmdOption(); void HandleCmdDescribe(); void HandleCmdSetup(); void HandleCmdPlay(); void HandleCmdTeardown(); void HandleCmdGetParamter(); bool HandleAuthentication(); void SendOptions(ConnectionMode mode = ConnectionMode::RTSP_SERVER); void SendDescribe(); void SendAnnounce(); void SendSetup(); void HandleRecord(); std::atomic_int alive_count_; std::weak_ptr rtsp_; ConnectionMode conn_mode_ = ConnectionMode::RTSP_SERVER; ConnectionState conn_state_ = ConnectionState::START_CONNECT; MediaSessionId session_id_ = 0; bool has_auth_ = true; std::string nonce_; std::unique_ptr auth_info_; //std::shared_ptr rtp_channel_; std::map> rtcp_channels_; std::unique_ptr rtsp_request_; std::unique_ptr rtsp_response_; std::shared_ptr rtp_conn_; }; } #endif ================================================ FILE: rtsp-server/xop/RtspMessage.cpp ================================================ // PHZ // 2018-5-16 // Scott Xu // 2020-12-5 Add IPv6 Support. #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "RtspMessage.h" #include "media.h" using namespace std; using namespace xop; bool RtspRequest::ParseRequest(BufferReader *buffer) { if (buffer->Peek()[0] == '$') { method_ = Method::RTCP; return true; } bool ret = true; while (true) { if (state_ == RtspRequestParseState::kParseRequestLine) { if (const char *firstCrlf = buffer->FindFirstCrlf(); firstCrlf != nullptr) { ret = ParseRequestLine(buffer->Peek(), firstCrlf); buffer->RetrieveUntil(firstCrlf + 2); } if (state_ == RtspRequestParseState::kParseHeadersLine) { continue; } break; } if (state_ == RtspRequestParseState::kParseHeadersLine) { if (const char *lastCrlf = buffer->FindLastCrlf(); lastCrlf != nullptr) { ret = ParseHeadersLine(buffer->Peek(), lastCrlf); buffer->RetrieveUntil(lastCrlf + 2); } break; } if (state_ == RtspRequestParseState::kGotAll) { buffer->RetrieveAll(); return true; } } return ret; } bool RtspRequest::ParseRequestLine(const char *begin, const char *end) { const string message(begin, end); char method[64] = {0}; char url[512] = {0}; char version[64] = {0}; if (sscanf(message.c_str(), "%s %s %s", method, url, version) != 3) { return true; } string method_str(method); if (method_str == "OPTIONS") { method_ = Method::OPTIONS; } else if (method_str == "DESCRIBE") { method_ = Method::DESCRIBE; } else if (method_str == "SETUP") { method_ = Method::SETUP; } else if (method_str == "PLAY") { method_ = Method::PLAY; } else if (method_str == "TEARDOWN") { method_ = Method::TEARDOWN; } else if (method_str == "GET_PARAMETER") { method_ = Method::GET_PARAMETER; } else { method_ = Method::NONE; return false; } if (strncmp(url, "rtsp://", 7) != 0) { return false; } // parse url uint16_t port = 0; char host[64] = {0}; char suffix[64] = {0}; if (sscanf(url + 7, "[%[^]]]:%hu/%s", host, &port, suffix) >= 2) { //IPv6 } else if (sscanf(url + 7, "[%[^]]]/%s", host, suffix) >= 1) { port = 554; } else if (sscanf(url + 7, "%[^:]:%hu/%s", host, &port, suffix) >= 2) { //IPv4, domain } else if (sscanf(url + 7, "%[^/]/%s", host, suffix) >= 1) { port = 554; } else { return false; } request_line_param_.emplace("url", make_pair(string(url), 0)); request_line_param_.emplace("url_host", make_pair(string(host), 0)); request_line_param_.emplace("url_port", make_pair("", static_cast(port))); request_line_param_.emplace("url_suffix", make_pair(string(suffix), 0)); request_line_param_.emplace("version", make_pair(string(version), 0)); request_line_param_.emplace("method", make_pair(move(method_str), 0)); state_ = RtspRequestParseState::kParseHeadersLine; return true; } bool RtspRequest::ParseHeadersLine(const char *begin, const char *end) { string message(begin, end); if (!ParseCSeq(message)) { if (header_line_param_.find("cseq") == header_line_param_.end()) { return false; } } if (method_ == Method::DESCRIBE || method_ == Method::SETUP || method_ == Method::PLAY) { ParseAuthorization(message); } if (method_ == Method::OPTIONS) { state_ = RtspRequestParseState::kGotAll; return true; } if (method_ == Method::DESCRIBE) { if (ParseAccept(message)) { state_ = RtspRequestParseState::kGotAll; } return true; } if (method_ == Method::SETUP) { if (ParseTransport(message) && ParseMediaChannel(message)) { state_ = RtspRequestParseState::kGotAll; } return true; } if (method_ == Method::PLAY) { if (ParseSessionId(message)) { state_ = RtspRequestParseState::kGotAll; } return true; } if (method_ == Method::TEARDOWN) { state_ = RtspRequestParseState::kGotAll; return true; } if (method_ == Method::GET_PARAMETER) { state_ = RtspRequestParseState::kGotAll; return true; } return true; } bool RtspRequest::ParseCSeq(std::string &message) { if (const std::size_t pos = message.find("CSeq"); pos != std::string::npos) { uint32_t cseq = 0; sscanf(message.c_str() + pos, "%*[^:]: %u", &cseq); //TODO header_line_param_.emplace("cseq", make_pair("", cseq)); return true; } return false; } bool RtspRequest::ParseAccept(const std::string &message) { if (message.rfind("Accept") == std::string::npos || message.rfind("sdp") == std::string::npos) { return false; } return true; } bool RtspRequest::ParseTransport(std::string &message) { if (std::size_t pos = message.find("Transport"); pos != std::string::npos) { if ((pos = message.find("RTP/AVP/TCP")) != std::string::npos) { transport_ = TransportMode::RTP_OVER_TCP; uint16_t rtpChannel = 0, rtcpChannel = 0; if (sscanf(message.c_str() + pos, //TODO "%*[^;];%*[^;];%*[^=]=%hu-%hu", &rtpChannel, &rtcpChannel) != 2) { return false; } header_line_param_.emplace("rtp_channel", make_pair("", rtpChannel)); header_line_param_.emplace("rtcp_channel", make_pair("", rtcpChannel)); } else if ((pos = message.find("RTP/AVP")) != std::string::npos) { uint16_t rtp_port = 0, rtcpPort = 0; if (((message.find("unicast", pos)) != std::string::npos)) { transport_ = TransportMode::RTP_OVER_UDP; if (sscanf(message.c_str() + pos, //TODO "%*[^;];%*[^;];%*[^=]=%hu-%hu", &rtp_port, &rtcpPort) != 2) { return false; } } else if ((message.find("multicast", pos)) != std::string::npos) { transport_ = TransportMode::RTP_OVER_MULTICAST; } else { return false; } header_line_param_.emplace("rtp_port", make_pair("", rtp_port)); header_line_param_.emplace("rtcp_port", make_pair("", rtcpPort)); } else { return false; } return true; } return false; } bool RtspRequest::ParseSessionId(std::string &message) { const std::size_t pos = message.find("Session"); if (pos != std::string::npos) { uint32_t session_id = 0; if (sscanf(message.c_str() + pos, "%*[^:]: %u", &session_id) != //TODO 1) { return false; } return true; } return false; } bool RtspRequest::ParseMediaChannel(std::string &message) { if (const auto iter = request_line_param_.find("url"); iter != request_line_param_.end()) { const std::string url = iter->second.first; const std::size_t pos = url.rfind("/track"); if (pos != std::string::npos) { int channel_index; if (sscanf(url.c_str() + pos, "/track%d", //TODO &channel_index) != 1) { return false; } channel_id_ = static_cast(channel_index); return true; } } return false; } bool RtspRequest::ParseAuthorization(std::string &message) { if (std::size_t pos = message.find("Authorization"); pos != std::string::npos) { if ((pos = message.find("response=")) != std::string::npos) { auth_response_ = message.substr(pos + 10, 32); if (auth_response_.size() == 32) { return true; } } } auth_response_.clear(); return false; } uint32_t RtspRequest::GetCSeq() const { uint32_t cseq = 0; if (const auto iter = header_line_param_.find("cseq"); iter != header_line_param_.end()) { cseq = iter->second.second; } return cseq; } std::string RtspRequest::GetHost() const { if (const auto iter = request_line_param_.find("url_ip"); iter != request_line_param_.end()) { return iter->second.first; } return ""; } std::string RtspRequest::GetRtspUrl() const { if (const auto iter = request_line_param_.find("url"); iter != request_line_param_.end()) { return iter->second.first; } return ""; } std::string RtspRequest::GetRtspUrlSuffix() const { if (const auto iter = request_line_param_.find("url_suffix"); iter != request_line_param_.end()) { return iter->second.first; } return ""; } std::string RtspRequest::GetAuthResponse() const { return auth_response_; } uint8_t RtspRequest::GetRtpChannel() const { if (const auto iter = header_line_param_.find("rtp_channel"); iter != header_line_param_.end()) { return iter->second.second; } return 0; } uint8_t RtspRequest::GetRtcpChannel() const { if (const auto iter = header_line_param_.find("rtcp_channel"); iter != header_line_param_.end()) { return iter->second.second; } return 0; } uint16_t RtspRequest::GetRtpPort() const { if (const auto iter = header_line_param_.find("rtp_port"); iter != header_line_param_.end()) { return iter->second.second; } return 0; } uint16_t RtspRequest::GetRtcpPort() const { if (const auto iter = header_line_param_.find("rtcp_port"); iter != header_line_param_.end()) { return iter->second.second; } return 0; } int RtspRequest::BuildOptionRes(const char *buf, int buf_size) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\r\n" "\r\n", this->GetCSeq()); return static_cast(strlen(buf)); } int RtspRequest::BuildDescribeRes(const char *buf, const int buf_size, const char *sdp) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "Content-Length: %d\r\n" "Content-Type: application/sdp\r\n" "\r\n" "%s", this->GetCSeq(), static_cast(strlen(sdp)), sdp); return static_cast(strlen(buf)); } int RtspRequest::BuildSetupMulticastRes(const char *buf, const int buf_size, const char *multicast_ip, const uint16_t port, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf( const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%u-0;ttl=255\r\n" "Session: %u\r\n" "\r\n", this->GetCSeq(), multicast_ip, this->GetHost().c_str(), port, session_id); return static_cast(strlen(buf)); } int RtspRequest::BuildSetupUdpRes(const char *buf, const int buf_size, const uint16_t rtp_chn, const uint16_t rtcp_chn, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf( const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "Transport: RTP/AVP;unicast;client_port=%hu-%hu;server_port=%hu-%hu\r\n" "Session: %u\r\n" "\r\n", this->GetCSeq(), this->GetRtpPort(), this->GetRtcpPort(), rtp_chn, rtcp_chn, session_id); return static_cast(strlen(buf)); } int RtspRequest::BuildSetupTcpRes(const char *buf, const int buf_size, const uint16_t rtp_chn, const uint16_t rtcp_chn, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %u\r\n" "Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\r\n" "Session: %u\r\n" "\r\n", this->GetCSeq(), rtp_chn, rtcp_chn, session_id); return static_cast(strlen(buf)); } int RtspRequest::BuildPlayRes(const char *buf, const int buf_size, const char *rtp_info, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Range: npt=0.000-\r\n" "Session: %u; timeout=60\r\n", this->GetCSeq(), session_id); if (rtp_info != nullptr) { snprintf(const_cast(buf) + strlen(buf), buf_size - strlen(buf), "%s\r\n", rtp_info); } snprintf(const_cast(buf) + strlen(buf), buf_size - strlen(buf), "\r\n"); return static_cast(strlen(buf)); } int RtspRequest::BuildTeardownRes(const char *buf, const int buf_size, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Session: %u\r\n" "\r\n", this->GetCSeq(), session_id); return static_cast(strlen(buf)); } int RtspRequest::BuildGetParamterRes(const char *buf, const int buf_size, const uint32_t session_id) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 200 OK\r\n" "CSeq: %d\r\n" "Session: %u\r\n" "\r\n", this->GetCSeq(), session_id); return static_cast(strlen(buf)); } int RtspRequest::BuildNotFoundRes(const char *buf, const int buf_size) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 404 Not Found\r\n" "CSeq: %u\r\n" "\r\n", this->GetCSeq()); return static_cast(strlen(buf)); } int RtspRequest::BuildServerErrorRes(const char *buf, const int buf_size) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 500 Internal Server Error\r\n" "CSeq: %u\r\n" "\r\n", this->GetCSeq()); return static_cast(strlen(buf)); } int RtspRequest::BuildUnsupportedRes(const char *buf, const int buf_size) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 461 Unsupported transport\r\n" "CSeq: %d\r\n" "\r\n", this->GetCSeq()); return static_cast(strlen(buf)); } int RtspRequest::BuildUnauthorizedRes(const char *buf, const int buf_size, const char *realm, const char *nonce) const { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RTSP/1.0 401 Unauthorized\r\n" "CSeq: %d\r\n" "WWW-Authenticate: Digest realm=\"%s\", nonce=\"%s\"\r\n" "\r\n", this->GetCSeq(), realm, nonce); return static_cast(strlen(buf)); } bool RtspResponse::ParseResponse(BufferReader *buffer) { if (strstr(buffer->Peek(), "\r\n\r\n") != nullptr) { if (strstr(buffer->Peek(), "OK") == nullptr) { return false; } if (char *ptr = strstr(buffer->Peek(), "Session"); ptr != nullptr) { if (char session_id[50] = {0}; sscanf(ptr, "%*[^:]: %s", session_id) == 1) session_ = session_id; } cseq_++; buffer->RetrieveUntil("\r\n\r\n"); } return true; } int RtspResponse::BuildOptionReq(const char *buf, const int buf_size) { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "OPTIONS %s RTSP/1.0\r\n" "CSeq: %u\r\n" "User-Agent: %s\r\n" "\r\n", rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str()); method_ = Method::OPTIONS; return static_cast(strlen(buf)); } int RtspResponse::BuildAnnounceReq(const char *buf, const int buf_size, const char *sdp) { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "ANNOUNCE %s RTSP/1.0\r\n" "Content-Type: application/sdp\r\n" "CSeq: %u\r\n" "User-Agent: %s\r\n" "Session: %s\r\n" "Content-Length: %d\r\n" "\r\n" "%s", rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str(), this->GetSession().c_str(), static_cast(strlen(sdp)), sdp); method_ = Method::ANNOUNCE; return static_cast(strlen(buf)); } int RtspResponse::BuildDescribeReq(const char *buf, const int buf_size) { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "DESCRIBE %s RTSP/1.0\r\n" "CSeq: %u\r\n" "Accept: application/sdp\r\n" "User-Agent: %s\r\n" "\r\n", rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str()); method_ = Method::DESCRIBE; return static_cast(strlen(buf)); } int RtspResponse::BuildSetupTcpReq(const char *buf, const int buf_size, const int trackId) { int interleaved[2] = {0, 1}; if (trackId == 1) { interleaved[0] = 2; interleaved[1] = 3; } memset((void *)buf, 0, buf_size); //TODO snprintf( const_cast(buf), buf_size, "SETUP %s/track%d RTSP/1.0\r\n" "Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=%d-%d\r\n" "CSeq: %u\r\n" "User-Agent: %s\r\n" "Session: %s\r\n" "\r\n", rtsp_url_.c_str(), trackId, interleaved[0], interleaved[1], this->GetCSeq() + 1, user_agent_.c_str(), this->GetSession().c_str()); method_ = Method::SETUP; return static_cast(strlen(buf)); } int RtspResponse::BuildRecordReq(const char *buf, const int buf_size) { memset((void *)buf, 0, buf_size); //TODO snprintf(const_cast(buf), buf_size, "RECORD %s RTSP/1.0\r\n" "Range: npt=0.000-\r\n" "CSeq: %u\r\n" "User-Agent: %s\r\n" "Session: %s\r\n" "\r\n", rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str(), this->GetSession().c_str()); method_ = Method::RECORD; return static_cast(strlen(buf)); } ================================================ FILE: rtsp-server/xop/RtspMessage.h ================================================ // PHZ // 2018-6-8 // Scott Xu // 2020-12-5 Add IPv6 Support. #ifndef XOP_RTSP_MESSAGE_H #define XOP_RTSP_MESSAGE_H #include #include #include #include "rtp.h" #include "media.h" #include "net/BufferReader.h" namespace xop { class RtspRequest { public: enum class Method { OPTIONS = 0, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER, RTCP, NONE, }; const char *MethodToString[8] = {"OPTIONS", "DESCRIBE", "SETUP", "PLAY", "TEARDOWN", "GET_PARAMETER", "RTCP", "NONE"}; enum class RtspRequestParseState { kParseRequestLine, kParseHeadersLine, //kParseBody, kGotAll, }; bool ParseRequest(xop::BufferReader *buffer); bool GotAll() const { return state_ == RtspRequestParseState::kGotAll; } void Reset() { state_ = RtspRequestParseState::kParseRequestLine; request_line_param_.clear(); header_line_param_.clear(); } Method GetMethod() const { return method_; } uint32_t GetCSeq() const; std::string GetRtspUrl() const; std::string GetRtspUrlSuffix() const; std::string GetHost() const; std::string GetAuthResponse() const; TransportMode GetTransportMode() const { return transport_; } MediaChannelId GetChannelId() const { return channel_id_; } uint8_t GetRtpChannel() const; uint8_t GetRtcpChannel() const; uint16_t GetRtpPort() const; uint16_t GetRtcpPort() const; int BuildOptionRes(const char *buf, int buf_size) const; int BuildDescribeRes(const char *buf, int buf_size, const char *sdp) const; int BuildSetupMulticastRes(const char *buf, int buf_size, const char *multicast_ip, uint16_t port, uint32_t session_id) const; int BuildSetupTcpRes(const char *buf, int buf_size, uint16_t rtp_chn, uint16_t rtcp_chn, uint32_t session_id) const; int BuildSetupUdpRes(const char *buf, int buf_size, uint16_t rtp_chn, uint16_t rtcp_chn, uint32_t session_id) const; int BuildPlayRes(const char *buf, int buf_size, const char *rtp_info, uint32_t session_id) const; int BuildTeardownRes(const char *buf, int buf_size, uint32_t session_id) const; int BuildGetParamterRes(const char *buf, int buf_size, uint32_t session_id) const; int BuildNotFoundRes(const char *buf, int buf_size) const; int BuildServerErrorRes(const char *buf, int buf_size) const; int BuildUnsupportedRes(const char *buf, int buf_size) const; int BuildUnauthorizedRes(const char *buf, int buf_size, const char *realm, const char *nonce) const; private: bool ParseRequestLine(const char *begin, const char *end); bool ParseHeadersLine(const char *begin, const char *end); bool ParseCSeq(std::string &message); static bool ParseAccept(const std::string &message); bool ParseTransport(std::string &message); static bool ParseSessionId(std::string &message); bool ParseMediaChannel(std::string &message); bool ParseAuthorization(std::string &message); Method method_; MediaChannelId channel_id_; TransportMode transport_; std::string auth_response_; std::unordered_map> request_line_param_; std::unordered_map> header_line_param_; RtspRequestParseState state_ = RtspRequestParseState::kParseRequestLine; }; class RtspResponse { public: enum class Method { OPTIONS = 0, DESCRIBE, ANNOUNCE, SETUP, RECORD, RTCP, NONE, }; bool ParseResponse(xop::BufferReader *buffer); Method GetMethod() const { return method_; } uint32_t GetCSeq() const { return cseq_; } std::string GetSession() const { return session_; } void SetUserAgent(const char *user_agent) { user_agent_ = std::string(user_agent); } void SetRtspUrl(const char *url) { rtsp_url_ = std::string(url); } int BuildOptionReq(const char *buf, int buf_size); int BuildDescribeReq(const char *buf, int buf_size); int BuildAnnounceReq(const char *buf, int buf_size, const char *sdp); int BuildSetupTcpReq(const char *buf, int buf_size, int channel); int BuildRecordReq(const char *buf, int buf_size); private: Method method_; uint32_t cseq_ = 0; std::string user_agent_; std::string rtsp_url_; std::string session_; }; } #endif ================================================ FILE: rtsp-server/xop/RtspPusher.cpp ================================================ #include "RtspPusher.h" #include "RtspConnection.h" #include "net/Logger.h" #include "net/TcpSocket.h" #include "net/Timestamp.h" #include using namespace xop; RtspPusher::RtspPusher(EventLoop *event_loop) : event_loop_(event_loop) {} RtspPusher::~RtspPusher() { this->Close(); } std::shared_ptr RtspPusher::Create(EventLoop *loop) { std::shared_ptr pusher(new RtspPusher(loop)); return pusher; } void RtspPusher::AddSession(MediaSession *session) { std::lock_guard locker(mutex_); media_session_.reset(session); } void RtspPusher::RemoveSession(MediaSessionId session_id) { std::lock_guard locker(mutex_); media_session_ = nullptr; //TODO } MediaSession::Ptr RtspPusher::LookMediaSession(MediaSessionId session_id) { return media_session_; //TODO } int RtspPusher::OpenUrl(const std::string &url, const int msec) { std::lock_guard lock(mutex_); static Timestamp timestamp; int timeout = msec; if (timeout <= 0) { timeout = 10000; } timestamp.reset(); if (!this->ParseRtspUrl(url)) { LOG_ERROR("rtsp url(%s) was illegal.\n", url.c_str()); return -1; } if (rtsp_conn_ != nullptr) { std::shared_ptr rtspConn = rtsp_conn_; SOCKET sockfd = rtspConn->GetSocket(); task_scheduler_->AddTriggerEvent( [sockfd, rtspConn]() { rtspConn->Disconnect(); }); rtsp_conn_ = nullptr; } TcpSocket tcpSocket; tcpSocket.Create(); if (!tcpSocket.Connect(rtsp_url_info_.ip, rtsp_url_info_.port, timeout)) { tcpSocket.Close(); return -1; } task_scheduler_ = event_loop_->GetTaskScheduler(); rtsp_conn_ = std::make_shared( tcpSocket.GetSocket(), task_scheduler_, shared_from_this()); event_loop_->AddTriggerEvent([this]() { rtsp_conn_->SendOptions( RtspConnection::ConnectionMode::RTSP_PUSHER); }); timeout -= static_cast(timestamp.Elapsed()); if (timeout < 0) { timeout = 1000; } do { Timer::Sleep(100); timeout -= 100; } while (!rtsp_conn_->IsRecord() && timeout > 0); if (!rtsp_conn_->IsRecord()) { std::shared_ptr rtspConn = rtsp_conn_; SOCKET sockfd = rtspConn->GetSocket(); task_scheduler_->AddTriggerEvent( [sockfd, rtspConn]() { rtspConn->Disconnect(); }); rtsp_conn_ = nullptr; return -1; } return 0; } void RtspPusher::Close() { std::lock_guard lock(mutex_); if (rtsp_conn_ != nullptr) { std::shared_ptr rtsp_conn = rtsp_conn_; SOCKET sockfd = rtsp_conn->GetSocket(); task_scheduler_->AddTriggerEvent( [sockfd, rtsp_conn]() { rtsp_conn->Disconnect(); }); rtsp_conn_ = nullptr; } } bool RtspPusher::IsConnected() { std::lock_guard lock(mutex_); if (rtsp_conn_ != nullptr) { return (!rtsp_conn_->IsClosed()); } return false; } bool RtspPusher::PushFrame(const MediaChannelId channelId, const AVFrame &frame) { std::lock_guard locker(mutex_); if (!media_session_ || !rtsp_conn_) { return false; } return media_session_->HandleFrame(channelId, frame); } ================================================ FILE: rtsp-server/xop/RtspPusher.h ================================================ #ifndef XOP_RTSP_PUSHER_H #define XOP_RTSP_PUSHER_H #include #include "rtsp.h" #include "net/EventLoop.h" namespace xop { class RtspConnection; class RtspPusher : public Rtsp { public: static std::shared_ptr Create(EventLoop *loop); ~RtspPusher() override; void AddSession(MediaSession *session); void RemoveSession(MediaSessionId session_id); int OpenUrl(const std::string &url, int msec = 3000); void Close(); bool IsConnected(); bool PushFrame(MediaChannelId channelId, const AVFrame &frame); private: friend class RtspConnection; explicit RtspPusher(EventLoop *event_loop); MediaSession::Ptr LookMediaSession(MediaSessionId session_id) override; EventLoop *event_loop_ = nullptr; std::shared_ptr task_scheduler_ = nullptr; std::mutex mutex_; std::shared_ptr rtsp_conn_; std::shared_ptr media_session_; }; } #endif ================================================ FILE: rtsp-server/xop/RtspServer.cpp ================================================ #include "RtspServer.h" #include "RtspConnection.h" using namespace xop; using namespace std; RtspServer::RtspServer(EventLoop *loop) : TcpServer(loop) {} RtspServer::~RtspServer() = default; std::shared_ptr RtspServer::Create(EventLoop *loop) { std::shared_ptr server(new RtspServer(loop)); return server; } MediaSessionId RtspServer::AddSession(MediaSession *session) { std::lock_guard locker(mutex_); if (rtsp_suffix_map_.find(session->GetRtspUrlSuffix()) != rtsp_suffix_map_.end()) return 0; if (session->task_scheduler_.lock() != nullptr) return 0; session->task_scheduler_ = event_loop_->GetTaskScheduler(); std::shared_ptr media_session(session); MediaSessionId sessionId = media_session->GetMediaSessionId(); rtsp_suffix_map_.emplace(media_session->GetRtspUrlSuffix(), sessionId); media_sessions_.emplace(sessionId, std::move(media_session)); return sessionId; } void RtspServer::RemoveSession(const MediaSessionId sessionId) { std::lock_guard locker(mutex_); if (const auto iter = media_sessions_.find(sessionId); iter != media_sessions_.end()) { rtsp_suffix_map_.erase(iter->second->GetRtspUrlSuffix()); media_sessions_.erase(sessionId); } } MediaSession::Ptr RtspServer::LookMediaSession(const std::string &suffix) { std::lock_guard locker(mutex_); if (const auto iter = rtsp_suffix_map_.find(suffix); iter != rtsp_suffix_map_.end()) { const MediaSessionId id = iter->second; return media_sessions_[id]; } return nullptr; } MediaSession::Ptr RtspServer::LookMediaSession(const MediaSessionId session_id) { std::lock_guard locker(mutex_); if (const auto iter = media_sessions_.find(session_id); iter != media_sessions_.end()) { return iter->second; } return nullptr; } bool RtspServer::PushFrame(const MediaSessionId session_id, const MediaChannelId channel_id, const AVFrame &frame) { std::shared_ptr sessionPtr; { std::lock_guard locker(mutex_); if (const auto iter = media_sessions_.find(session_id); iter != media_sessions_.end()) { sessionPtr = iter->second; } else { return false; } } if (sessionPtr != nullptr && sessionPtr->GetNumClient() != 0) { return sessionPtr->HandleFrame(channel_id, frame); } return false; } TcpConnection::Ptr RtspServer::OnConnect(SOCKET sockfd) { return std::make_shared( sockfd, event_loop_->GetTaskScheduler(), shared_from_this()); } ================================================ FILE: rtsp-server/xop/RtspServer.h ================================================ // PHZ // 2020-4-2 #ifndef XOP_RTSP_SERVER_H #define XOP_RTSP_SERVER_H #include #include #include #include #include "net/TcpServer.h" #include "rtsp.h" namespace xop { class RtspConnection; class RtspServer : public Rtsp, public TcpServer { public: static std::shared_ptr Create(EventLoop *loop); ~RtspServer() override; MediaSessionId AddSession(MediaSession *session); void RemoveSession(MediaSessionId sessionId); bool PushFrame(MediaSessionId session_id, MediaChannelId channel_id, const AVFrame &frame); private: friend class RtspConnection; explicit RtspServer(EventLoop *loop); MediaSession::Ptr LookMediaSession(const std::string &suffix) override; MediaSession::Ptr LookMediaSession(MediaSessionId session_id) override; TcpConnection::Ptr OnConnect(SOCKET sockfd) override; std::mutex mutex_; std::unordered_map> media_sessions_; std::unordered_map rtsp_suffix_map_; }; } #endif ================================================ FILE: rtsp-server/xop/VP8Source.cpp ================================================ // PHZ // 2021-8-26 #if defined(WIN32) || defined(_WIN32) #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #endif #include "VP8Source.h" #include #include #include #if defined(WIN32) || defined(_WIN32) #else #include #endif using namespace xop; using namespace std; VP8Source::VP8Source(const uint32_t framerate) : framerate_(framerate) { payload_ = 96; clock_rate_ = 90000; } VP8Source *VP8Source::CreateNew(const uint32_t framerate) { return new VP8Source(framerate); } VP8Source::~VP8Source() = default; string VP8Source::GetMediaDescription(const uint16_t port) { char buf[100]; snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); return buf; } string VP8Source::GetAttribute() { return "a=rtpmap:96 VP8/90000"; } bool VP8Source::HandleFrame(const MediaChannelId channel_id, AVFrame frame) { uint8_t *frame_buf = frame.buffer.get(); size_t frame_size = frame.size; if (frame.timestamp == 0) { frame.timestamp = GetTimestamp(); } // X = R = N = 0; PartID = 0; // S = 1 if this is the first (or only) fragment of the frame uint8_t vp8_payload_descriptor = 0x10; while (frame_size > 0) { size_t payload_size = MAX_RTP_PAYLOAD_SIZE; RtpPacket rtp_pkt; rtp_pkt.timestamp = frame.timestamp; rtp_pkt.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + RTP_VPX_HEAD_SIZE + MAX_RTP_PAYLOAD_SIZE; rtp_pkt.last = 0; if (frame_size < MAX_RTP_PAYLOAD_SIZE) { payload_size = frame_size; rtp_pkt.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + RTP_VPX_HEAD_SIZE + static_cast(frame_size); rtp_pkt.last = 1; } rtp_pkt.data.get()[RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + 0] = vp8_payload_descriptor; memcpy(rtp_pkt.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + RTP_VPX_HEAD_SIZE, frame_buf, payload_size); if (send_frame_callback_) { if (!send_frame_callback_(channel_id, rtp_pkt)) return false; } frame_buf += payload_size; frame_size -= payload_size; vp8_payload_descriptor = 0x00; } return true; } uint32_t VP8Source::GetTimestamp() { const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( (time_point.time_since_epoch().count() + 500) / 1000 * 90); } ================================================ FILE: rtsp-server/xop/VP8Source.h ================================================ // PHZ // 2021-8-26 #ifndef XOP_VP8_SOURCE_H #define XOP_VP8_SOURCE_H #include "MediaSource.h" #include "rtp.h" namespace xop { class VP8Source : public MediaSource { public: static VP8Source *CreateNew(uint32_t framerate = 25); ~VP8Source() override; void Setframerate(const uint32_t framerate) { framerate_ = framerate; } uint32_t GetFramerate() const { return framerate_; } std::string GetMediaDescription(uint16_t port = 0) override; std::string GetAttribute() override; bool HandleFrame(MediaChannelId channelId, AVFrame frame) override; static uint32_t GetTimestamp(); private: explicit VP8Source(uint32_t framerate); uint32_t framerate_ = 25; }; } #endif ================================================ FILE: rtsp-server/xop/media.h ================================================ // PHZ // 2018-5-16 #ifndef XOP_MEDIA_H #define XOP_MEDIA_H #include namespace xop { /* RTSP服务支持的媒体类型 */ enum class MediaType { //PCMU = 0, PCMA = 8, H264 = 96, AAC = 37, H265 = 265, NONE }; enum class FrameType : uint8_t { VIDEO_FRAME_IDR = 0x01, VIDEO_FRAME_NOTIDR = 0x02, AUDIO_FRAME = 0x11, NONE = 0x00 }; struct AVFrame { explicit AVFrame(const size_t size = 0) : buffer(new uint8_t[size], std::default_delete()), size(size), timestamp(0) { } std::shared_ptr buffer; /* 帧数据 */ size_t size; /* 帧大小 */ uint32_t timestamp; /* 时间戳 */ }; enum class MediaChannelId : uint8_t { channel_0 = 0, channel_1 = 1, channel_2 = 2, channel_3 = 3, channel_4 = 4, channel_5 = 5, channel_6 = 6, channel_7 = 7, channel_8 = 8, channel_9 = 9 }; typedef uint32_t MediaSessionId; } #endif ================================================ FILE: rtsp-server/xop/rtp.h ================================================ // PHZ // 2021-9-2 #ifndef XOP_RTP_H #define XOP_RTP_H #include #include #include "media.h" #define RTP_HEADER_SIZE 12 #define MAX_RTP_PAYLOAD_SIZE 1420 //1460 1500-20-12-8 #define RTP_VERSION 2 #define RTP_TCP_HEAD_SIZE 4 #define RTP_VPX_HEAD_SIZE 1 #define RTP_HEADER_BIG_ENDIAN 0 namespace xop { enum class TransportMode { NONE = 0, RTP_OVER_TCP = 1, RTP_OVER_UDP = 2, RTP_OVER_MULTICAST = 3, }; typedef struct _RTP_header { #if RTP_HEADER_BIG_ENDIAN /* 大端序 */ unsigned char version : 2; unsigned char padding : 1; unsigned char extension : 1; unsigned char csrc : 4; unsigned char marker : 1; unsigned char payload : 7; #else /* 小端序 */ unsigned char csrc : 4; unsigned char extension : 1; unsigned char padding : 1; unsigned char version : 2; unsigned char payload : 7; unsigned char marker : 1; #endif unsigned short seq; unsigned int ts; unsigned int ssrc; } RtpHeader; struct MediaChannelInfo { RtpHeader rtp_header; // tcp uint8_t rtp_channel; uint8_t rtcp_channel; // udp uint16_t rtp_port; uint16_t rtcp_port; uint16_t packet_seq; uint32_t clock_rate; // rtcp uint64_t packet_count; uint64_t octet_count; uint64_t last_rtcp_ntp_time; bool is_setup; bool is_play; bool is_record; }; struct RtpPacket { RtpPacket() : data(new uint8_t[1600], std::default_delete()), size(0), timestamp(0), type(FrameType::NONE), last(0) { } std::shared_ptr data; uint16_t size; uint32_t timestamp; FrameType type; uint8_t last; }; } #endif ================================================ FILE: rtsp-server/xop/rtsp.h ================================================ // PHZ // 2018-6-8 // Scott Xu // 2020-12-5 Add IPv6 Support. #ifndef XOP_RTSP_H #define XOP_RTSP_H #include #include #include "MediaSession.h" #include "net/Logger.h" namespace xop { struct RtspUrlInfo { std::string url; std::string ip; uint16_t port; std::string suffix; }; class Rtsp : public std::enable_shared_from_this { public: Rtsp() = default; virtual ~Rtsp() = default; virtual void SetAuthConfig(const std::string realm, const std::string username, const std::string password) { realm_ = realm; username_ = username; password_ = password; has_auth_info_ = true; if (realm_.empty() || username.empty()) { has_auth_info_ = false; } } virtual void SetVersion(std::string version) // SDP Session Name { version_ = std::move(version); } virtual std::string GetVersion() { return version_; } virtual std::string GetRtspUrl() { return rtsp_url_info_.url; } bool ParseRtspUrl(const std::string &url) { char ip[100] = {0}; char suffix[100] = {0}; uint16_t port = 0; #if defined(WIN32) || defined(_WIN32) if (sscanf_s(url.c_str() + 7, "[%[^]]]:%hu/%s", ip, 100, &port, suffix, 100) == 3) //IPv6 #else if (sscanf(url.c_str() + 7, "[%[^]]]:%hu/%s", ip, &port, suffix) == 3) #endif { rtsp_url_info_.port = port; } #if defined(WIN32) || defined(_WIN32) else if (sscanf_s(url.c_str() + 7, "[%[^]]]/%s", ip, 100, suffix, 100) == 2) #else else if (sscanf(url.c_str() + 7, "[%[^]]]/%s", ip, suffix) == 2) #endif { rtsp_url_info_.port = 554; } #if defined(WIN32) || defined(_WIN32) else if (sscanf_s(url.c_str() + 7, "%[^:]:%hu/%s", ip, 100, &port, suffix, 100) == 3) //IPv4, domain #else else if (sscanf(url.c_str() + 7, "%[^:]:%hu/%s", ip, &port, suffix) == 3) #endif { rtsp_url_info_.port = port; } #if defined(WIN32) || defined(_WIN32) else if (sscanf_s(url.c_str() + 7, "%[^/]/%s", ip, 100, suffix, 100) == 2) #else else if (sscanf(url.c_str() + 7, "%[^/]/%s", ip, suffix) == 2) #endif { rtsp_url_info_.port = 554; } else { LOG_ERROR("%s was illegal.\n", url.c_str()); return false; } rtsp_url_info_.ip = ip; rtsp_url_info_.suffix = suffix; rtsp_url_info_.url = url; return true; } protected: friend class RtspConnection; virtual MediaSession::Ptr LookMediaSession([[maybe_unused]] const std::string &suffix) { return nullptr; } virtual MediaSession::Ptr LookMediaSession([[maybe_unused]] MediaSessionId sessionId) { return nullptr; } bool has_auth_info_ = false; std::string realm_; std::string username_; std::string password_; std::string version_; RtspUrlInfo rtsp_url_info_; }; } #endif ================================================ FILE: rtsp_main.cpp ================================================ #include #include #include #include #include #include #include #include "helper.h" #include "rtsp_output_helper.h" #include "rtsp_output.h" #include "ui/rtsp_properties.hpp" OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-rtspserver", "en-US") void obs_frontend_event(enum obs_frontend_event event, void *ptr); void rtsp_output_auto_start(RtspOutputHelper *rtspOutputHelper); void rtsp_output_stop(RtspOutputHelper *rtspOutputHelper); void rtsp_output_save_settings(RtspOutputHelper *rtspOutputHelper); void rtsp_output_save_hotkey_settings(RtspOutputHelper *rtspOutputHelper); void server_log_write_callback(xop::Priority priority, std::string info); const char *obs_module_name(void) { return obs_module_text("RtspServer"); } const char *obs_module_description(void) { return obs_module_text("RstpServer.Description"); } bool obs_module_load(void) { xop::Logger::Instance().SetWriteCallback(server_log_write_callback); rtsp_output_register(); RtspOutputHelper *rtspOutputHelper; { auto *settings = rtsp_output_read_data(); auto *config = rtsp_properties_open_config(); const char *str = nullptr; str = config_get_string(config, HOTKEY_CONFIG_SECTIION, "RtspOutput"); obs_data_t *hotkey = nullptr; if (str) hotkey = obs_data_create_from_json(str); rtspOutputHelper = RtspOutputHelper::CreateRtspOutput(settings, hotkey); obs_data_release(hotkey); config_close(config); obs_data_release(settings); } const auto mainWindow = static_cast(obs_frontend_get_main_window()); const auto action = static_cast(obs_frontend_add_tools_menu_qaction( obs_module_text("RtspServer"))); obs_frontend_push_ui_translation(obs_module_get_string); const auto rtspProperties = new RtspProperties(rtspOutputHelper->GetOutputName(), mainWindow); obs_frontend_pop_ui_translation(); QAction::connect(action, &QAction::triggered, rtspProperties, &QDialog::exec); obs_frontend_add_event_callback(obs_frontend_event, rtspOutputHelper); return true; } void obs_module_unload(void) { obs_frontend_remove_event_callback(obs_frontend_event, nullptr); } void obs_frontend_event(enum obs_frontend_event event, void *ptr) { const auto rtspOutputHelper = static_cast(ptr); switch (event) { case OBS_FRONTEND_EVENT_FINISHED_LOADING: rtsp_output_auto_start(rtspOutputHelper); break; case OBS_FRONTEND_EVENT_EXIT: rtsp_output_stop(rtspOutputHelper); rtsp_output_save_settings(rtspOutputHelper); rtsp_output_save_hotkey_settings(rtspOutputHelper); delete rtspOutputHelper; break; default: ; } } void rtsp_output_auto_start(RtspOutputHelper *rtspOutputHelper) { auto *config = rtsp_properties_open_config(); auto autoStart = false; if (config) { autoStart = config_get_bool(config, CONFIG_SECTIION, "AutoStart"); config_close(config); } if (autoStart) rtspOutputHelper->Start(); } void rtsp_output_stop(RtspOutputHelper *rtspOutputHelper) { rtspOutputHelper->Stop(); } void rtsp_output_save_hotkey_settings(RtspOutputHelper *rtspOutputHelper) { auto *data = rtspOutputHelper->HotkeysSave(); auto *str = obs_data_get_json(data); auto *config = rtsp_properties_open_config(); config_set_string(config, HOTKEY_CONFIG_SECTIION, "RtspOutput", str); config_save(config); config_close(config); } void rtsp_output_save_settings(RtspOutputHelper *rtspOutputHelper) { auto *data = rtspOutputHelper->GetSettings(); rtsp_output_save_data(data); } void server_log_write_callback(xop::Priority priority, std::string info) { switch (priority) { case xop::LOG_DEBUG: blog(LOG_DEBUG, "[rtsp-server] %s", info.c_str()); break; case xop::LOG_STATE: blog(LOG_INFO, "[rtsp-server] %s", info.c_str()); break; case xop::LOG_INFO: blog(LOG_INFO, "[rtsp-server] %s", info.c_str()); break; case xop::LOG_WARNING: blog(LOG_WARNING, "[rtsp-server] %s", info.c_str()); break; case xop::LOG_ERROR: blog(LOG_ERROR, "[rtsp-server] %s", info.c_str()); break; default: ; } } ================================================ FILE: rtsp_output.cpp ================================================ #include #include #include #include #include #include #include #include #include #include #include #include "threadsafe_queue.h" #include "rtsp_output.h" #include "helper.h" #define USEC_IN_SEC 1000000 #define ERROR_BEGIN_DATA_CAPTURE 1 #define ERROR_INIT_ENCODERS 2 #define ERROR_START_RTSP_SERVER 3 #define ERROR_START_MULTICAST 4 #define ERROR_ENCODE OBS_OUTPUT_ENCODE_ERROR #define OBS_RTSPSERVER_QUEUE_SIZE_LIMIT 2 struct queue_frame { queue_frame(size_t size = 0) : av_frame(size), channe_id(xop::MediaChannelId::channel_0) { } struct xop::AVFrame av_frame; xop::MediaChannelId channe_id; }; struct rtsp_out_data { obs_output_t *output = nullptr; volatile bool active; volatile bool starting; volatile bool stopping; volatile uint64_t stop_ts; //volatile uint32_t num_clients = 0; std::array audio_timestamp_clocks; std::array channel_ids; volatile uint64_t total_bytes_sent = 0; volatile uint32_t enabled_audio_channels_count = 0; volatile bool output_audio = false; std::unique_ptr event_loop; std::shared_ptr server; xop::MediaSessionId session_id = 0; std::unique_ptr> frame_queue; std::unique_ptr frame_push_thread; obs_hotkey_pair_id start_stop_hotkey; }; static const char *rtsp_output_getname(void *unused) { UNUSED_PARAMETER(unused); return obs_module_text("RtspOutput"); } static inline bool active(const rtsp_out_data *out_data) { return os_atomic_load_bool(&out_data->active); } static inline bool starting(const rtsp_out_data *out_data) { return os_atomic_load_bool(&out_data->starting); } static inline bool stopping(const rtsp_out_data *out_data) { return os_atomic_load_bool(&out_data->stopping); } static void add_prestart_signal(const rtsp_out_data *out_data) { const auto handler = obs_output_get_signal_handler(out_data->output); signal_handler_add(handler, "void pre_start()"); } static void send_prestart_signal(const rtsp_out_data *out_data) { const auto handler = obs_output_get_signal_handler(out_data->output); signal_handler_signal(handler, "pre_start", nullptr); } static bool rtsp_output_start_hotkey(void *data, const obs_hotkey_pair_id id, obs_hotkey_t *hotkey, const bool pressed) { UNUSED_PARAMETER(id); UNUSED_PARAMETER(hotkey); const auto out_data = static_cast(data); if (!pressed) return false; if (stopping(out_data) || starting(out_data) || active(out_data)) return false; return obs_output_start(out_data->output); } static bool rtsp_output_stop_hotkey(void *data, const obs_hotkey_pair_id id, obs_hotkey_t *hotkey, const bool pressed) { UNUSED_PARAMETER(id); UNUSED_PARAMETER(hotkey); const auto *out_data = static_cast(data); if (!pressed) return false; if (stopping(out_data) || starting(out_data) || !active(out_data)) return false; obs_output_stop(out_data->output); return true; } static void rtsp_output_destroy(void *data) { //rtsp_out_data *out_data = (rtsp_out_data *)data; bfree(data); } static void rtsp_output_update(void *data, obs_data_t *settings); static void *rtsp_output_create(obs_data_t *settings, obs_output_t *output) { const auto data = static_cast( bzalloc(sizeof(struct rtsp_out_data))); data->output = output; data->event_loop = std::make_unique(); data->server = xop::RtspServer::Create(data->event_loop.get()); add_prestart_signal(data); data->start_stop_hotkey = obs_hotkey_pair_register_output( output, "RtspOutput.Start", obs_module_text("RtspOutput.Hotkey.StartOutput"), "RtspOutput.Stop", obs_module_text("RtspOutput.Hotkey.StopOutput"), rtsp_output_start_hotkey, rtsp_output_stop_hotkey, data, data); UNUSED_PARAMETER(settings); return data; } static void rtsp_push_frame(void *param); static void set_output_error(const rtsp_out_data *out_data, int code, ...) { const char *message; const char *lookup_string; switch (code) { case ERROR_BEGIN_DATA_CAPTURE: message = "can't begin data capture"; lookup_string = "RtspOutput.Error.BeginDataCapture"; break; case ERROR_INIT_ENCODERS: message = "initialize encoders error"; lookup_string = "RtspOutput.Error.InitEncoders"; break; case ERROR_START_RTSP_SERVER: message = "starting RTSP server failed on port '%d'"; lookup_string = "RtspOutput.Error.StartRtspServer"; break; case ERROR_START_MULTICAST: message = "starting multicast failed"; lookup_string = "RtspOutput.Error.StartMulticast"; break; case ERROR_ENCODE: message = "encode error"; lookup_string = "RtspOutput.Error.Encode"; break; default: message = "unknown error"; lookup_string = "RtspOutput.Error.Unknown"; break; } { char buffer[500] = {0}; va_list args; va_start(args, code); #if defined(WIN32) || defined(_WIN32) vsprintf_s(buffer, obs_module_text(lookup_string), args); #else vsnprintf(buffer, sizeof(buffer), obs_module_text(lookup_string), args); #endif va_end(args); obs_output_set_last_error(out_data->output, buffer); } { va_list args; va_start(args, code); blogva(LOG_WARNING, message, args); va_end(args); } } static bool rtsp_output_add_video_channel(void *data, xop::MediaSession *session) { const auto *out_data = static_cast(data); const auto video_encoder = obs_output_get_video_encoder(out_data->output); if (video_encoder == nullptr) return false; const auto video = obs_encoder_video(video_encoder); const auto video_frame_rate = video_output_get_frame_rate(video); vector extra_data; { uint8_t *p_extra_data = nullptr; size_t extra_data_size = 0; if (!obs_encoder_get_extra_data(video_encoder, &p_extra_data, &extra_data_size)) extra_data_size = 0; extra_data = vector(p_extra_data, p_extra_data + extra_data_size); } switch (get_encoder_codec(video_encoder)) { case encoder_codec::H264: { session->AddSource( xop::MediaChannelId::channel_0, xop::H264Source::CreateNew( extra_data, static_cast(video_frame_rate))); } break; case encoder_codec::HEVC: { session->AddSource( xop::MediaChannelId::channel_0, xop::H265Source::CreateNew( extra_data, vector(), static_cast(video_frame_rate))); } break; default: break; } return true; } static bool rtsp_output_add_audio_channel(void *data, xop::MediaSession *session, const size_t idx, const xop::MediaChannelId channel_id) { auto *out_data = static_cast(data); const auto audio_encoder = obs_output_get_audio_encoder(out_data->output, idx); if (audio_encoder == nullptr) return true; const auto audio = obs_encoder_audio(audio_encoder); const auto audio_channels = audio_output_get_channels(audio); const auto audio_sample_rate = obs_encoder_get_sample_rate(audio_encoder); session->AddSource( channel_id, xop::AACSource::CreateNew(audio_sample_rate, static_cast(audio_channels), false)); out_data->audio_timestamp_clocks[idx] = audio_sample_rate; return true; } static bool rtsp_output_start(void *data) { const auto out_data = static_cast(data); if (starting(out_data) || stopping(out_data)) return false; send_prestart_signal(out_data); const auto settings = obs_output_get_settings(out_data->output); rtsp_output_update(data, settings); const auto port = static_cast(obs_data_get_int(settings, "port")); uint32_t enabled_audio_channels_count = 0; for (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) { if (obs_output_get_audio_encoder(out_data->output, index) == nullptr) continue; out_data->channel_ids[index] = static_cast( ++enabled_audio_channels_count); } out_data->enabled_audio_channels_count = enabled_audio_channels_count; if (!obs_output_can_begin_data_capture(out_data->output, 0)) { set_output_error(out_data, ERROR_BEGIN_DATA_CAPTURE); return false; } if (!obs_output_initialize_encoders(out_data->output, 0)) { set_output_error(out_data, ERROR_INIT_ENCODERS); return false; } if (!out_data->server->Start("0.0.0.0", port) || !out_data->server->Start("::0", port)) { set_output_error(out_data, ERROR_START_RTSP_SERVER, port); out_data->server->Stop(); return false; } obs_output_begin_data_capture(out_data->output, 0); os_atomic_set_bool(&out_data->starting, true); return true; } static void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code); static void rtsp_output_rtsp_start(void *data) { const auto out_data = static_cast(data); const auto settings = obs_output_get_settings(out_data->output); const auto port = static_cast(obs_data_get_int(settings, "port")); const auto url_suffix = obs_data_get_string(settings, "url_suffix"); out_data->output_audio = obs_data_get_bool(settings, "output_audio"); xop::MediaSession *session = xop::MediaSession::CreateNew( url_suffix, out_data->enabled_audio_channels_count + 1); if (!rtsp_output_add_video_channel(data, session)) { rtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS); return; } if (out_data->output_audio) for (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) { if (!rtsp_output_add_audio_channel( data, session, index, out_data->channel_ids[index])) { rtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS); return; } } out_data->frame_queue = std::make_unique>( OBS_RTSPSERVER_QUEUE_SIZE_LIMIT); session->AddNotifyConnectedCallback( [](const xop::MediaSessionId session_id, const std::string &peer_ip, const uint16_t peer_port) { blog(LOG_INFO, "Rtsp client %d(%s:%d) is connected.", session_id, peer_ip.c_str(), peer_port); }); session->AddNotifyDisconnectedCallback( [](const xop::MediaSessionId session_id, const std::string &peer_ip, const uint16_t peer_port) { blog(LOG_INFO, "Rtsp client %d(%s:%d) is disconnected.", session_id, peer_ip.c_str(), peer_port); }); out_data->session_id = out_data->server->AddSession(session); if (obs_data_get_bool(settings, "multicast")) { if (!session->StartMulticast()) { rtsp_output_actual_stop(out_data, ERROR_START_MULTICAST); return; } blog(LOG_INFO, "------------------------------------------------"); blog(LOG_INFO, "Rtsp multicast info:"); blog(LOG_INFO, "\tipv6 address: \t%s", session->GetMulticastIp(true).c_str()); blog(LOG_INFO, "\tipv4 address: \t%s", session->GetMulticastIp(false).c_str()); blog(LOG_INFO, "\tchannel 0 port (video): \t%d", session->GetMulticastPort(static_cast(0))); for (size_t index = 1; index < OBS_OUTPUT_MULTI_TRACK + 1 ; index++) { auto channel_port = session->GetMulticastPort(static_cast(index)); if (channel_port == 0) break; blog(LOG_INFO, "\tchannel %zu port (audio): \t%d", index, channel_port); } blog(LOG_INFO, "------------------------------------------------"); } out_data->total_bytes_sent = 0; out_data->frame_push_thread = std::make_unique(rtsp_push_frame, out_data); os_atomic_set_bool(&out_data->active, true); os_atomic_set_bool(&out_data->starting, false); blog(LOG_INFO, "Starting rtsp server on port '%d'.", port); } static void rtsp_output_stop(void *data, const uint64_t ts) { auto *out_data = static_cast(data); if (starting(out_data) || stopping(out_data)) return; out_data->stop_ts = ts / 1000ULL; //obs_output_pause(out_data->output, false); os_atomic_set_bool(&out_data->stopping, true); } static void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code) { os_atomic_set_bool(&out_data->active, false); if (out_data->frame_queue) out_data->frame_queue->termination(); if (out_data->frame_push_thread) { out_data->frame_push_thread->join(); out_data->frame_push_thread.reset(); } if (out_data->session_id) { out_data->server->RemoveSession(out_data->session_id); out_data->session_id = 0; } out_data->server->Stop(); //out_data->num_clients = 0; if (out_data->frame_queue) out_data->frame_queue.reset(); if (code < 0) { set_output_error(out_data, code); obs_output_signal_stop(out_data->output, code); } else if (code > 0) { obs_output_end_data_capture(out_data->output); set_output_error(out_data, code); obs_output_signal_stop(out_data->output, OBS_OUTPUT_ERROR); } else { obs_output_end_data_capture(out_data->output); } os_atomic_set_bool(&out_data->starting, false); os_atomic_set_bool(&out_data->stopping, false); blog(LOG_INFO, "Rtsp server stopped."); } static uint32_t get_timestamp(const uint64_t timestamp_clock, const struct encoder_packet *packet) { // Convert the incoming dts time to the correct clock time for the timestamp. // We use a int64 to ensure the roll over is handled correctly. // We do the [USEC_IN_SEC / 2] trick to make sure the result of the division rounds to the nearest int. const uint64_t timestamp = packet->dts_usec * timestamp_clock; return static_cast((timestamp + USEC_IN_SEC / 2) / USEC_IN_SEC); } static void rtsp_push_frame(void *param) { auto *out_data = static_cast(param); blog(LOG_INFO, "Starting rtsp frame push thread."); while (true) { std::shared_ptr queue_frame = out_data->frame_queue->wait_and_pop(); if (queue_frame == nullptr) break; out_data->total_bytes_sent += queue_frame->av_frame.size; out_data->server->PushFrame(out_data->session_id, queue_frame->channe_id, queue_frame->av_frame); } blog(LOG_INFO, "Rtsp frame push thread stopped."); } static void rtsp_output_video(void *param, struct encoder_packet *packet) { const auto *out_data = static_cast(param); struct queue_frame queue_frame(packet->size); xop::AVFrame *frame = &queue_frame.av_frame; queue_frame.channe_id = xop::MediaChannelId::channel_0; frame->timestamp = get_timestamp(90000, packet); memcpy(frame->buffer.get(), packet->data, packet->size); out_data->frame_queue->move_push(std::move(queue_frame)); } static void rtsp_output_audio(void *param, struct encoder_packet *packet) { const auto *out_data = static_cast(param); queue_frame queue_frame(packet->size); xop::AVFrame *frame = &queue_frame.av_frame; queue_frame.channe_id = out_data->channel_ids[packet->track_idx]; frame->timestamp = get_timestamp( out_data->audio_timestamp_clocks[packet->track_idx], packet); memcpy(frame->buffer.get(), packet->data, packet->size); out_data->frame_queue->move_push(std::move(queue_frame)); } static void rtsp_output_data(void *data, struct encoder_packet *packet) { auto *out_data = static_cast(data); if (!active(out_data) && !starting(out_data)) return; if (starting(out_data)) { rtsp_output_rtsp_start(out_data); return; } if (!packet) { rtsp_output_actual_stop(out_data, OBS_OUTPUT_ENCODE_ERROR); return; } if (stopping(out_data) && packet->sys_dts_usec >= static_cast(out_data->stop_ts)) { rtsp_output_actual_stop(out_data, OBS_OUTPUT_SUCCESS); return; } //if (out_data->num_clients > 0) { if (packet->type == OBS_ENCODER_VIDEO) rtsp_output_video(data, packet); else if (packet->type == OBS_ENCODER_AUDIO && out_data->output_audio) rtsp_output_audio(data, packet); //} else if (!stopping(out_data)) { //obs_output_pause(out_data->output, true); //} } static void rtsp_output_defaults(obs_data_t *defaults) { obs_data_set_default_bool(defaults, "multicast", false); #if defined(__APPLE__) || defined(__MACH__) // On osx the application will run using a non-priviliged user. // Opening ports below 1024 is not possible. obs_data_set_default_int(defaults, "port", 8554); #else obs_data_set_default_int(defaults, "port", 554); #endif obs_data_set_default_string(defaults, "url_suffix", "live"); obs_data_set_default_bool(defaults, "output_audio", true); obs_data_set_default_bool(defaults, "authentication", false); obs_data_set_default_string(defaults, "authentication_realm", ""); obs_data_set_default_string(defaults, "authentication_username", ""); obs_data_set_default_string(defaults, "authentication_password", ""); } static void rtsp_output_update(void *data, obs_data_t *settings) { const auto *out_data = static_cast(data); const auto auth_enabled = obs_data_get_bool(settings, "authentication"); const auto auth_realm = obs_data_get_string(settings, "authentication_realm"); const auto auth_username = obs_data_get_string(settings, "authentication_username"); const auto auth_password = obs_data_get_string(settings, "authentication_password"); if (auth_enabled && auth_realm && *auth_realm != '\0' && auth_username && *auth_username != '\0') out_data->server->SetAuthConfig(auth_realm, auth_username, auth_password); else { obs_data_set_bool(settings, "authentication", false); out_data->server->SetAuthConfig("", "", ""); } } static obs_properties_t *rtsp_output_properties(void *data) { UNUSED_PARAMETER(data); obs_properties_t *props = obs_properties_create(); obs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE); obs_properties_add_bool( props, "multicast", obs_module_text("RtspOutput.Properties.Multicast")); obs_properties_add_int(props, "port", obs_module_text("RtspOutput.Properties.Port"), 1, 65535, 1); obs_properties_add_text( props, "url_suffix", obs_module_text("RtspOutput.Properties.UrlSuffix"), OBS_TEXT_DEFAULT); obs_properties_add_bool( props, "output_audio", obs_module_text("RtspOutput.Properties.OutputAudio")); obs_properties_t *auth_group = obs_properties_create(); obs_properties_add_text( auth_group, "authentication_realm", obs_module_text("RtspOutput.Properties.Authentication.Realm"), OBS_TEXT_DEFAULT); obs_properties_add_text( auth_group, "authentication_username", obs_module_text( "RtspOutput.Properties.Authentication.Username"), OBS_TEXT_DEFAULT); obs_properties_add_text( auth_group, "authentication_password", obs_module_text( "RtspOutput.Properties.Authentication.Password"), OBS_TEXT_PASSWORD); obs_properties_add_group( props, "authentication", obs_module_text("RtspOutput.Properties.Authentication"), OBS_GROUP_CHECKABLE, auth_group); return props; } static uint64_t rtsp_output_total_bytes_sent(void *data) { const auto *out_data = static_cast(data); return out_data->total_bytes_sent; } static int rtsp_output_get_dropped_frames(void *data) { const auto *out_data = static_cast(data); if (!active(out_data) || out_data->frame_queue == nullptr) { return 0; } auto dropped_count = out_data->frame_queue->dropped_count(); while (dropped_count > INT32_MAX) dropped_count -= INT32_MAX; return static_cast(dropped_count); } void rtsp_output_register() { struct obs_output_info output_info = {}; output_info.id = "rtsp_output"; output_info.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK; output_info.encoded_video_codecs = "h264;hevc"; output_info.encoded_audio_codecs = "aac"; output_info.get_name = rtsp_output_getname; output_info.create = rtsp_output_create; output_info.destroy = rtsp_output_destroy; output_info.start = rtsp_output_start; output_info.stop = rtsp_output_stop; output_info.encoded_packet = rtsp_output_data; output_info.get_defaults = rtsp_output_defaults; output_info.update = rtsp_output_update; output_info.get_properties = rtsp_output_properties; output_info.get_total_bytes = rtsp_output_total_bytes_sent; output_info.get_dropped_frames = rtsp_output_get_dropped_frames; obs_register_output(&output_info); } ================================================ FILE: rtsp_output.h ================================================ #pragma once [[maybe_unused]] static const char *rtsp_output_getname(void *unused); void rtsp_output_register(); ================================================ FILE: rtsp_output_helper.cpp ================================================ #include #include #include #include #include #include #include "rtsp_output_helper.h" #include "helper.h" using namespace std; RtspOutputHelper::RtspOutputHelper(const string outputName) : RtspOutputHelper(obs_get_output_by_name(outputName.c_str())) { } RtspOutputHelper::RtspOutputHelper(obs_output_t *obsOutput) : obsOutput(obsOutput) { } RtspOutputHelper::~RtspOutputHelper() { obs_output_release(obsOutput); obs_encoder_release(videoEncoder); for (const auto audioEncoder : audioEncoders) obs_encoder_release(audioEncoder); } RtspOutputHelper *RtspOutputHelper::CreateRtspOutput(obs_data_t *settings, obs_data_t *hotkey) { const auto rtspOutput = new RtspOutputHelper( obs_output_create("rtsp_output", obs_module_text("RtspOutput"), settings, hotkey)); rtspOutput->SignalConnect( "pre_start", OnPreStartSignal, rtspOutput); return rtspOutput; } void RtspOutputHelper::UpdateSettings(obs_data_t *settings) const { obs_output_update(obsOutput, settings); } obs_data_t *RtspOutputHelper::GetSettings() const { return obs_output_get_settings(obsOutput); } void RtspOutputHelper::UpdateEncoder() { GetBaseConfig(); CreateVideoEncoder(); CreateAudioEncoder(); } bool RtspOutputHelper::Start() const { return obs_output_start(obsOutput); } void RtspOutputHelper::Stop() const { obs_output_stop(obsOutput); } string RtspOutputHelper::GetLastError() const { return obs_output_get_last_error(obsOutput); } obs_data_t *RtspOutputHelper::HotkeysSave() const { return obs_hotkeys_save_output(obsOutput); } void RtspOutputHelper::SignalConnect(const char *signal, signal_callback_t callback, void *data) const { const auto handler = obs_output_get_signal_handler(obsOutput); signal_handler_connect(handler, signal, callback, data); } void RtspOutputHelper::SignalDisconnect(const char *signal, signal_callback_t callback, void *data) const { const auto handler = obs_output_get_signal_handler(obsOutput); signal_handler_disconnect(handler, signal, callback, data); } string RtspOutputHelper::GetOutputName() const { return string(obs_output_get_name(obsOutput)); } uint64_t RtspOutputHelper::GetTotalBytes() const { return obs_output_get_total_bytes(obsOutput); } int RtspOutputHelper::GetTotalFrames() const { return obs_output_get_total_frames(obsOutput); } int RtspOutputHelper::GetFramesDropped() const { return obs_output_get_frames_dropped(obsOutput); } bool RtspOutputHelper::IsActive() const { return obs_output_active(obsOutput); } void RtspOutputHelper::CreateVideoEncoder() { obs_encoder_t *encoder; if (outputSettings.adv_out) //encoder = obs_get_encoder_by_name("streaming_h264"); //OBS 27.2.4 Or Older encoder = obs_get_encoder_by_name("advanced_video_stream"); else //encoder = obs_get_encoder_by_name("simple_h264_stream"); //OBS 27.2.4 Or Older encoder = obs_get_encoder_by_name("simple_video_stream"); obs_encoder_release(videoEncoder); videoEncoder = obs_video_encoder_create( obs_encoder_get_id(encoder), "rtsp_output_video", obs_encoder_get_settings(encoder), nullptr); obs_encoder_release(encoder); if (outputSettings.adv_out) obs_encoder_set_scaled_size(videoEncoder, outputSettings.rescale_cx, outputSettings.rescale_cy); obs_encoder_set_video(videoEncoder, obs_get_video()); { auto video = obs_output_video(obsOutput); if (video == nullptr) video = obs_get_video(); obs_encoder_set_video(videoEncoder, video); } obs_output_set_video_encoder(obsOutput, videoEncoder); } void RtspOutputHelper::CreateAudioEncoder() { obs_encoder_t *encoder; if (outputSettings.adv_out) { /*if ((encoder = obs_get_encoder_by_name("adv_stream_aac")) == nullptr) encoder = obs_get_encoder_by_name( "avc_aac_stream");*/ //OBS 26.0.2 Or Older /*if ((encoder = obs_get_encoder_by_name("adv_stream_audio")) == nullptr) //OBS 30.0.0 Or Older encoder = obs_get_encoder_by_name("adv_stream_aac");*/ encoder = obs_get_encoder_by_name("adv_stream_audio"); } else encoder = obs_get_encoder_by_name("simple_aac"); for (const auto audioEncoder : audioEncoders) obs_encoder_release(audioEncoder); audioEncoders.clear(); const auto config = rtsp_properties_open_config(); auto tracks = static_cast(config_get_uint(config, CONFIG_SECTIION, "AudioTracks")); { auto outputData = GetSettings(); if (tracks == 0) { obs_data_set_bool(outputData, "output_audio", false); tracks = 0x1; } else obs_data_set_bool(outputData, "output_audio", true); UpdateSettings(outputData); obs_data_release(outputData); } auto trackIndex = 0; for (auto idx = 0; idx < OBS_OUTPUT_MULTI_TRACK; idx++) { if ((tracks & (1 << idx)) == 0) continue; auto audioEncoder = obs_audio_encoder_create( obs_encoder_get_id(encoder), string("rtsp_output_audio_track") .append(to_string(idx + 1)) .c_str(), obs_encoder_get_settings(encoder), idx, nullptr); { auto audio = obs_output_audio(obsOutput); if (audio == nullptr) audio = obs_get_audio(); obs_encoder_set_audio(audioEncoder, audio); } audioEncoders.push_back(audioEncoder); obs_output_set_audio_encoder(obsOutput, audioEncoder, trackIndex++); } config_close(config); obs_encoder_release(encoder); } void RtspOutputHelper::GetBaseConfig() { config_t *basicConfig = obs_frontend_get_profile_config(); const char *mode = config_get_string(basicConfig, "Output", "Mode"); outputSettings.adv_out = astrcmpi(mode, "Advanced") == 0; outputSettings.rescale_cx = 0; outputSettings.rescale_cy = 0; if (outputSettings.adv_out) { const bool rescale = config_get_bool(basicConfig, "AdvOut", "Rescale"); if (const char *rescaleRes = config_get_string(basicConfig, "AdvOut", "RescaleRes"); rescale && rescaleRes && *rescaleRes) { if (sscanf(rescaleRes, "%ux%u", &outputSettings.rescale_cx, &outputSettings.rescale_cy) != 2) { outputSettings.rescale_cx = 0; outputSettings.rescale_cy = 0; } } } } void RtspOutputHelper::OnPreStartSignal(void *data, calldata_t *cd) { UNUSED_PARAMETER(cd); const auto helper = static_cast(data); helper->UpdateEncoder(); } ================================================ FILE: rtsp_output_helper.h ================================================ #ifndef RTSP_OUTPUT_HELPER_H #define RTSP_OUTPUT_HELPER_H #include #include #include struct rtsp_output_settings { bool adv_out = false; uint32_t rescale_cx = 0; uint32_t rescale_cy = 0; }; class RtspOutputHelper { public: RtspOutputHelper(std::string outputName); ~RtspOutputHelper(); static RtspOutputHelper *CreateRtspOutput(obs_data_t *settings, obs_data_t *hotkey); void UpdateSettings(obs_data_t *settings) const; obs_data_t *GetSettings() const; void UpdateEncoder(); bool Start() const; void Stop() const; std::string GetLastError() const; obs_data_t *HotkeysSave() const; void SignalConnect(const char *signal, signal_callback_t callback, void *data) const; void SignalDisconnect(const char *signal, signal_callback_t callback, void *data) const; std::string GetOutputName() const; uint64_t GetTotalBytes() const; int GetTotalFrames() const; int GetFramesDropped() const; bool IsActive() const; private: RtspOutputHelper(obs_output_t *obsOutput); void CreateVideoEncoder(); void CreateAudioEncoder(); void GetBaseConfig(); static void OnPreStartSignal(void *data, calldata_t *cd); obs_output_t *obsOutput; struct rtsp_output_settings outputSettings; obs_encoder_t *videoEncoder = nullptr; std::vector audioEncoders; }; #endif // RTSP_OUTPUT_HELPER_H ================================================ FILE: rtspoutput.rc.in ================================================ 1 VERSIONINFO FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK} BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "OBS" VALUE "FileDescription", "OBS RTSP Server Plugin" VALUE "FileVersion", "${PROJECT_VERSION}" VALUE "InternalName", "obs-rtspserver" VALUE "OriginalFilename", "obs-rtspserver.dll" VALUE "ProductName", "OBS Studio" VALUE "ProductVersion", "${OBS_PLUGUIN_VERSION}" VALUE "Comments", "RTSP server for OBS" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0409, 0x04B0 END END ================================================ FILE: threadsafe_queue.h ================================================ #ifndef __THREADSAFE_QUEUE_H_ #define __THREADSAFE_QUEUE_H_ #include #include #include #include #include using namespace std; template class threadsafe_queue { public: threadsafe_queue(size_t size_limit) : size_limit(size_limit), m_termination(false) { } ~threadsafe_queue() = default; /** * 1. When termination is not called, one element is dequeued every time the * queue is called until the queue is empty. This method blocks the thread. * 2. After termination is called, this method will never block. If it is * already in a blocked state, contact the blocked state. * 3. When true is returned, the value is valid. When false is returned, value * is invalid. Returns false when termination is called and the queue is empty. **/ //return nullptr if the queue is empty std::shared_ptr wait_and_pop() { unique_lock lk(mut); data_cond.wait(lk, [this] { return !data_queue.empty() || m_termination.load(memory_order_acquire); }); //dequeue if not empty if (!data_queue.empty()) { shared_ptr res = data_queue.front(); data_queue.pop(); return res; } //If the queue is empty, return nullptr return nullptr; } //return false if the queue is empty bool wait_and_pop(T &&value) { shared_ptr res = wait_and_pop(); if (res == nullptr) return false; value = std::move(res); return true; } //return nullptr if the queue is empty std::shared_ptr try_pop() { lock_guard lk(mut); //dequeue if not empty if (!data_queue.empty()) { shared_ptr res = data_queue.front(); data_queue.pop(); return res; } //If the queue is empty, return nullptr return nullptr; } //return false if the queue is empty bool try_pop(T &&value) { shared_ptr res = try_pop(); if (res == nullptr) return false; value = std::move(res); return true; } //insert queue, move void move_push(T &&new_value) { if (m_termination.load(memory_order_acquire)) return; shared_ptr data(make_shared(std::move(new_value))); unique_lock lk(mut); data_queue.push(data); if (data_queue.size() > size_limit) { data_queue.pop(); m_dropped_count.fetch_add(1, memory_order_relaxed); } data_cond.notify_one(); } //insert queue void push(T new_value) { move_push(new_value); } bool empty() { unique_lock lk(mut); return data_queue.empty(); } size_t size() { unique_lock lk(mut); return data_queue.size(); } size_t dropped_count() const { return m_dropped_count.load(memory_order_relaxed); } //Set this queue to terminated state. //In the exit state, the enqueue is ignored, and the dequeue can be performed. //When the queue is empty, wait_and_pop will not block. void termination() { unique_lock lk(mut); m_termination.store(true, memory_order_release); data_cond.notify_all(); } //Get whether this queue is terminated bool is_termination() const { return m_termination.load(memory_order_acquire); } private: mutex mut; queue> data_queue; const size_t size_limit; condition_variable data_cond; atomic_bool m_termination; atomic_size_t m_dropped_count; }; #endif ================================================ FILE: ui/rtsp_properties.cpp ================================================ #include #include #include #include #include #include #include #include #include "rtsp_properties.hpp" #include "ui_rtsp_properties.h" #include "../helper.h" RtspProperties::RtspProperties(std::string rtspOutputName, QWidget *parent) : QDialog(parent), ui(new Ui::RtspProperties), statusTimer(new QTimer(this)), rtspOutputHelper(new RtspOutputHelper(std::move(rtspOutputName))), settings(rtspOutputHelper->GetSettings()) { ui->setupUi(this); connect(ui->pushButtonStart, &QPushButton::clicked, this, &RtspProperties::onPushButtonStartClicked); connect(ui->pushButtonStop, &QPushButton::clicked, this, &RtspProperties::onPushButtonStopClicked); connect(ui->pushButtonAddressCopy, &QPushButton::clicked, this, &RtspProperties::onPushButtonAddressCopyClicked); connect(ui->checkBoxEnableMulticast, &QCheckBox::clicked, this, &RtspProperties::onCheckBoxEnableMulticastClicked); connect(ui->spinBoxPort, static_cast(&QSpinBox::valueChanged), this, &RtspProperties::onSpinBoxPortValueChanged); connect(ui->lineEditUrlSuffix, &QLineEdit::textChanged, this, &RtspProperties::onLineEditUrlSuffixValueChanged); connect(ui->checkBoxEnableAuthentication, &QCheckBox::clicked, this, &RtspProperties::onCheckBoxEnableAuthenticationClicked); connect(ui->lineEditRealm, &QLineEdit::textChanged, this, &RtspProperties::onLineEditRealmTextChanged); connect(ui->lineEditUsername, &QLineEdit::textChanged, this, &RtspProperties::onLineEditUsernameTextChanged); connect(ui->lineEditPassword, &QLineEdit::textChanged, this, &RtspProperties::onLineEditPasswordTextChanged); connect(statusTimer, &QTimer::timeout, this, &RtspProperties::onStatusTimerTimeout); connect(this, &RtspProperties::setButtonStatus, this, &RtspProperties::onButtonStatusChanging); connect(this, &RtspProperties::setStatusTimerStatus, this, &RtspProperties::onStatusTimerStatusChanging); connect(this, &RtspProperties::setLabelMessageStatus, this, &RtspProperties::onLabelMessageStatusChanging); //ui->lineEditUrlSuffix->setValidator(new QRegExpValidator(QRegExp("^([-A-Za-z0-9+&@#%=~_|]+)(\\/[-A-Za-z0-9+&@#%=~_|]+)*$"), this)); #ifdef VERSION_STRING ui->labelVersion->setText(VERSION_STRING); #endif onButtonStatusChanging(!rtspOutputHelper->IsActive(), rtspOutputHelper->IsActive()); rtspOutputHelper->SignalConnect("start", OnOutputStart, this); rtspOutputHelper->SignalConnect("stop", OnOutputStop, this); { const auto config = rtsp_properties_open_config(); LoadConfig(config); config_close(config); } } RtspProperties::~RtspProperties() { rtspOutputHelper->SignalDisconnect("start", OnOutputStart, this); rtspOutputHelper->SignalDisconnect("stop", OnOutputStop, this); obs_data_release(settings); delete ui; delete rtspOutputHelper; } int RtspProperties::exec() { if (const auto host = this->parentWidget(); host) { const auto hostRect = host->geometry(); this->move(hostRect.center() - this->rect().center()); } return QDialog::exec(); } void RtspProperties::onPushButtonStartClicked() { { const auto config = rtsp_properties_open_config(); SaveConfig(config); config_close(config); } setLabelMessageStatus(!rtspOutputHelper->Start()); } void RtspProperties::onPushButtonStopClicked() { rtspOutputHelper->Stop(); setButtonStatus(false, false); } void RtspProperties::onPushButtonAddressCopyClicked() const { QString url = "rtsp://localhost"; if (ui->spinBoxPort->value() != 554) { url.append(":"); url.append(ui->spinBoxPort->text()); } url.append("/"); url.append(ui->lineEditUrlSuffix->text()); QApplication::clipboard()->setText(url); } void RtspProperties::onCheckBoxEnableMulticastClicked(const int checked) const { obs_data_set_bool(settings, "multicast", checked); } void RtspProperties::onSpinBoxPortValueChanged(const int value) const { obs_data_set_int(settings, "port", value); } void RtspProperties::onLineEditUrlSuffixValueChanged(const QString &value) const { //auto rx = QRegExp("^[-A-Za-z0-9+&@#%=~_|]+(/[-A-Za-z0-9+&@#%=~_|]+)*$"); //if (!rx.exactMatch(value)) //return; obs_data_set_string(settings, "url_suffix", value.toStdString().c_str()); } void RtspProperties::onCheckBoxEnableAuthenticationClicked(const bool checked) const { obs_data_set_bool(settings, "authentication", checked); } void RtspProperties::onLineEditRealmTextChanged(const QString &value) const { obs_data_set_string(settings, "authentication_realm", value.toStdString().c_str()); } void RtspProperties::onLineEditUsernameTextChanged(const QString &value) const { obs_data_set_string(settings, "authentication_username", value.toStdString().c_str()); } void RtspProperties::onLineEditPasswordTextChanged(const QString &value) const { obs_data_set_string(settings, "authentication_password", value.toStdString().c_str()); } void RtspProperties::onStatusTimerTimeout() { const auto totalBytes = rtspOutputHelper->GetTotalBytes(); const auto totalFrames = rtspOutputHelper->GetTotalFrames(); const auto framesDropped = rtspOutputHelper->GetFramesDropped(); const auto bitps = (totalBytes - lastTotalBytes) * 8; lastTotalBytes = totalBytes; ui->labelTotalData->setText( rtsp_properties_get_data_volume_display(totalBytes).c_str()); ui->labelBitrate->setText(QString("%1 kb/s").arg( bitps / 1000.0, 0, 'f', 0)); ui->labelFramesDropped->setText( QString("%1 / %2 (%3%)") .arg(framesDropped) .arg(totalFrames) .arg(totalFrames == 0 ? 0 : framesDropped * 100.0 / totalFrames, 0, 'f', 1)); } void RtspProperties::onButtonStatusChanging(const bool outputStarted, const bool outputStopped) const { ui->checkBoxEnableMulticast->setEnabled(outputStarted); ui->spinBoxPort->setEnabled(outputStarted); ui->lineEditUrlSuffix->setEnabled(outputStarted); ui->checkBoxAudioTrack1->setEnabled(outputStarted); ui->checkBoxAudioTrack2->setEnabled(outputStarted); ui->checkBoxAudioTrack3->setEnabled(outputStarted); ui->checkBoxAudioTrack4->setEnabled(outputStarted); ui->checkBoxAudioTrack5->setEnabled(outputStarted); ui->checkBoxAudioTrack6->setEnabled(outputStarted); ui->pushButtonStart->setEnabled(outputStarted); ui->pushButtonStop->setEnabled(outputStopped); } void RtspProperties::onStatusTimerStatusChanging(const bool start) { if (start) { lastTotalBytes = 0; statusTimer->start(1000); } else { statusTimer->stop(); ui->labelTotalData->setText("0.0 MB"); ui->labelBitrate->setText("0 kb/s"); ui->labelFramesDropped->setText("0 / 0 (0.0%)"); } } void RtspProperties::onLabelMessageStatusChanging(const bool showError) const { if (showError) ui->labelMessage->setText( QString(rtspOutputHelper->GetLastError().c_str())); else ui->labelMessage->setText(""); } void RtspProperties::showEvent(QShowEvent *event) { UNUSED_PARAMETER(event); ui->checkBoxEnableMulticast->blockSignals(true); ui->checkBoxEnableMulticast->setChecked( obs_data_get_bool(settings, "multicast")); ui->checkBoxEnableMulticast->blockSignals(false); ui->spinBoxPort->blockSignals(true); ui->spinBoxPort->setValue(obs_data_get_int(settings, "port")); ui->spinBoxPort->blockSignals(false); ui->lineEditUrlSuffix->blockSignals(true); ui->lineEditUrlSuffix->setText( std::string(obs_data_get_string(settings, "url_suffix")).c_str()); ui->lineEditUrlSuffix->blockSignals(false); const auto realm = std::string( obs_data_get_string(settings, "authentication_realm")); const auto username = std::string( obs_data_get_string(settings, "authentication_username")); const auto password = std::string( obs_data_get_string(settings, "authentication_password")); auto enbledAuth = false; if (!realm.empty() && !username.empty()) enbledAuth = obs_data_get_bool(settings, "authentication"); ui->checkBoxEnableAuthentication->setChecked(enbledAuth); ui->lineEditRealm->blockSignals(true); ui->lineEditRealm->setText(realm.c_str()); ui->lineEditRealm->blockSignals(false); ui->lineEditUsername->blockSignals(true); ui->lineEditUsername->setText(username.c_str()); ui->lineEditUsername->blockSignals(false); ui->lineEditPassword->blockSignals(true); ui->lineEditPassword->setText(password.c_str()); ui->lineEditPassword->blockSignals(false); } void RtspProperties::closeEvent(QCloseEvent *event) { UNUSED_PARAMETER(event); if (this->isHidden()) return; { const auto config = rtsp_properties_open_config(); SaveConfig(config); config_close(config); } rtspOutputHelper->UpdateSettings(settings); } void RtspProperties::OnOutputStart(void *data, calldata_t *cd) { UNUSED_PARAMETER(cd); auto page = static_cast(data); page->setButtonStatus(false, true); page->setStatusTimerStatus(true); } void RtspProperties::OnOutputStop(void *data, calldata_t *cd) { const auto page = static_cast(data); if (const auto code = calldata_int(cd, "code"); code != OBS_OUTPUT_SUCCESS) page->setLabelMessageStatus(true); page->setButtonStatus(true, false); page->setStatusTimerStatus(false); } void RtspProperties::LoadConfig(config_t *config) const { ui->checkBoxAuto->setChecked( config_get_bool(config, CONFIG_SECTIION, "AutoStart")); { auto tracks = config_get_uint(config, CONFIG_SECTIION, "AudioTracks"); ui->checkBoxAudioTrack1->setChecked(tracks & (1 << 0)); ui->checkBoxAudioTrack2->setChecked(tracks & (1 << 1)); ui->checkBoxAudioTrack3->setChecked(tracks & (1 << 2)); ui->checkBoxAudioTrack4->setChecked(tracks & (1 << 3)); ui->checkBoxAudioTrack5->setChecked(tracks & (1 << 4)); ui->checkBoxAudioTrack6->setChecked(tracks & (1 << 5)); } } void RtspProperties::SaveConfig(config_t *config) const { if (!config) return; config_set_bool(config, CONFIG_SECTIION, "AutoStart", ui->checkBoxAuto->isChecked()); { uint64_t tracks = 0; tracks |= ui->checkBoxAudioTrack1->isChecked() ? (1 << 0) : 0; tracks |= ui->checkBoxAudioTrack2->isChecked() ? (1 << 1) : 0; tracks |= ui->checkBoxAudioTrack3->isChecked() ? (1 << 2) : 0; tracks |= ui->checkBoxAudioTrack4->isChecked() ? (1 << 3) : 0; tracks |= ui->checkBoxAudioTrack5->isChecked() ? (1 << 4) : 0; tracks |= ui->checkBoxAudioTrack6->isChecked() ? (1 << 5) : 0; config_set_uint(config, CONFIG_SECTIION, "AudioTracks", tracks); } config_save(config); } ================================================ FILE: ui/rtsp_properties.hpp ================================================ #ifndef RTSP_PROPERTIES_H #define RTSP_PROPERTIES_H #include #include #include #include "../rtsp_output_helper.h" namespace Ui { class RtspProperties; } class RtspProperties : public QDialog { Q_OBJECT public: explicit RtspProperties(std::string rtspOutputName, QWidget *parent = 0); ~RtspProperties(); public Q_SLOTS: virtual int exec(); private Q_SLOTS: void onPushButtonStartClicked(); void onPushButtonStopClicked(); void onPushButtonAddressCopyClicked() const; void onCheckBoxEnableMulticastClicked(int checked) const; void onSpinBoxPortValueChanged(int value) const; void onLineEditUrlSuffixValueChanged(const QString &value) const; void onCheckBoxEnableAuthenticationClicked(bool checked) const; void onLineEditRealmTextChanged(const QString &value) const; void onLineEditUsernameTextChanged(const QString &value) const; void onLineEditPasswordTextChanged(const QString &value) const; void onStatusTimerTimeout(); void onButtonStatusChanging(bool outputStarted, bool outputStopped) const; void onStatusTimerStatusChanging(bool start); void onLabelMessageStatusChanging(bool showError) const; Q_SIGNALS: void setButtonStatus(bool outputStarted, bool outputStopped); void setStatusTimerStatus(bool start); void setLabelMessageStatus(bool showError); private: Ui::RtspProperties *ui; QTimer *statusTimer; signal_handler_t *signalHandler; RtspOutputHelper *rtspOutputHelper; uint64_t lastTotalBytes; obs_data_t *settings; void showEvent(QShowEvent *event); void closeEvent(QCloseEvent *event); static void OnOutputStart(void *data, calldata_t *cd); static void OnOutputStop(void *data, calldata_t *cd); void LoadConfig(config_t *config) const; void SaveConfig(config_t *config) const; }; #endif // RTSP_PROPERTIES_H ================================================ FILE: ui/rtsp_properties.ui ================================================ RtspProperties RtspServer.Properties 0 0 RtspServer.Properties.Options QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter RtspServer.Properties.Options.AutoStart RtspServer.Properties.Options.Output labelOutputOptionsTip RtspServer.Properties.Options.Output.Tip RtspServer.Properties.Options.EnabledAudioTracks widgetEnabledAudioTracks 0 0 0 0 0 0 1 0 0 2 0 0 3 0 0 4 0 0 5 0 0 6 Qt::Horizontal 0 0 0 0 RtspServer.Properties.Target QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 80 16777215 Qt::RightToLeft RtspServer.Properties.Target.Address Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter widgetAddress 0 0 0 0 0 0 0 0 rtsp://localhost: 0 0 1 65535 0 0 / 0 0 RtspServer.Properties.Target.Address.Copy pushButtonAddressCopy labelAddressSplit spinBoxPort labelAddressPrefix lineEditUrlSuffix RtspServer.Properties.Target.Multicast widgetAddress labelAddress checkBoxEnableMulticast 0 0 RtspServer.Properties.Authentication QFormLayout::AllNonFixedFieldsGrow Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter RtspServer.Properties.Authentication.Enabled false true false RtspServer.Properties.Authentication.Realm lineEditRealm false Qt::LeftToRight RtspServer.Properties.Authentication.Username lineEditUsername false true false Qt::RightToLeft RtspServer.Properties.Authentication.Password lineEditPassword false QLineEdit::PasswordEchoOnEdit RtspServer.Properties.Authentication.PasswordPlaceholder true 0 0 RtspServer.Properties.Status 0 0 0 0 0 0 60 0 0kb/s RtspServer.Properties.Status.TotalDataSent RtspServer.Properties.Status.Bitrate 0 0 60 0 0.0 MB 0 0 RtspServer.Properties.Status.DroppedFrames 0 0 60 0 0 / 0 (0.0%) labelBitrate labelBitrateTitle labelTotalData labelTotalDataTitle labelFramesDroppedTitle labelFramesDropped 0 0 0 12 QLabel { color : red; } 0 Qt::Vertical 0 0 0 0 0 0 0 0 RtspServer.Properties.Version Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft 0 0 0 0 0 0 75 23 RtspServer.Properties.StartOutput true false 75 23 RtspServer.Properties.StopOutput 16 16 true true false groupBoxTarget groupBoxOption groupBoxAuthentication groupBoxStatus RtspPropertiesverSpacer checkBoxAuto checkBoxAudioTrack1 checkBoxAudioTrack2 checkBoxAudioTrack3 checkBoxAudioTrack4 checkBoxAudioTrack5 checkBoxAudioTrack6 checkBoxEnableMulticast spinBoxPort lineEditUrlSuffix pushButtonAddressCopy checkBoxEnableAuthentication lineEditRealm lineEditUsername lineEditPassword pushButtonStart pushButtonStop checkBoxEnableAuthentication toggled(bool) labelUsername setEnabled(bool) 552 207 93 263 checkBoxEnableAuthentication toggled(bool) labelPassword setEnabled(bool) 552 207 105 294 checkBoxEnableAuthentication toggled(bool) lineEditUsername setEnabled(bool) 552 207 552 263 checkBoxEnableAuthentication toggled(bool) lineEditPassword setEnabled(bool) 552 207 552 294 checkBoxEnableAuthentication toggled(bool) lineEditRealm setEnabled(bool) 520 207 660 232 checkBoxEnableAuthentication toggled(bool) labelRealm setEnabled(bool) 520 207 112 232