[
  {
    "path": ".github/workflows/changelogConfig.json",
    "content": "{\n    \"typeLabels\": [\n      { \"types\": [\"breaking\"], \"title\": \"⚡️ Breaking Changes\" },\n      { \"types\": [\"feat\", \"feature\"], \"title\": \"🚀 New Features\" },\n      { \"types\": [\"fix\", \"bugfix\"], \"title\": \"💊 Bugfixes\" },\n      { \"types\": [\"improvements\", \"enhancement\"], \"title\": \"🔨 Improvements\" },\n      { \"types\": [\"perf\"], \"title\": \"🏎️ Performance Improvements\" },\n      { \"types\": [\"build\", \"ci\"], \"title\": \"🏗️ Build System\" },\n      { \"types\": [\"refactor\"], \"title\": \"🪚 Refactors\" },\n      { \"types\": [\"doc\", \"docs\"], \"title\": \"📚 Documentation Changes\" },\n      { \"types\": [\"test\", \"tests\"], \"title\": \"🔍 Tests\" },\n      { \"types\": [\"style\"], \"title\": \"💅 Code Style Changes\" },\n      { \"types\": [\"chore\"], \"title\": \"🧹 Chores\" },\n      { \"types\": [\"other\"], \"title\": \"Other Changes\" }\n    ],\n    \"bumpLabels\": [\n      {\n        \"title\": \"major\",\n        \"types\": [\"breaking\"]\n      },\n      {\n        \"title\": \"minor\",\n        \"types\": [\"feat\", \"feature\"]\n      },\n      {\n        \"title\": \"patch\",\n        \"types\": []\n      }\n    ],\n    \"issuesUrl\": \"\",\n    \"sortOrder\": \"desc\",\n    \"emptyMessage\": \"No changes\"\n  }"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "\nname: 'CI Release'\n\non:\n    #release:\n    #  types: [published]\n  push:\n    tags:\n      - v[1-9].[0-9]+.[0-9]+\n      - v[1-9][0-9]+.[0-9]+.[0-9]+\n\n      - v[1-9].[0-9]+.[0-9]+-[a-z0-9]+\n      - v[1-9][0-9]+.[0-9]+.[0-9]+-[a-z0-9]+\n\nenv:\n  PRODUCT_NAME: 'obs-rtspserver'\n  DEPS_VERSION_MAC: '2023-11-03'\n  DEPS_HASH_MAC: '90c2fc069847ec2768dcc867c1c63b112c615ed845a907dc44acab7a97181974'\n  #QT_VERSION_MAC: '2022-02-13'\n  QT_HASH_MAC: 'ba4a7152848da0053f63427a2a2cb0a199af3992997c0db08564df6f48c9db98'\n  DEPS_VERSION_WIN: '2023-11-03'\n  DEPS_X64_HASH_WIN: 'd0825a6fb65822c993a3059edfba70d72d2e632ef74893588cf12b1f0d329ce6'\n  DEPS_X86_HASH_WIN: 'b69c864275189464483c240ef3f25ea16fba3050b136fe6c3db6e9ee63036683'\n  QT_X64_HASH_WIN: 'bc57dedf76b47119a6dce0435a2f21b35b08c8f2948b1cb34a157320f77732d1'\n  QT_X86_HASH_WIN: '50129f9836ef987c23db2e0535085fa2d52474ef0de44bc11c9df6cfa602b785'\n  #QT_VERSION_WIN: '5.15.2'\n  NSIS_VERSION_WIN: '3.09'\njobs:\n  get_obs_info:\n    name: '01 - Get obs-studio last release info'\n    runs-on: [ubuntu-latest]\n    outputs:\n      latest_id: ${{ steps.latest_release.outputs.id }}\n      latest_url: ${{ steps.latest_release.outputs.url }}\n      latest_html_url: ${{ steps.latest_release.outputs.html_url }}\n      latest_upload_url: ${{ steps.latest_release.outputs.upload_url }}\n      latest_name: ${{ steps.latest_release.outputs.name }}\n      latest_tag_name: ${{ steps.latest_release.outputs.tag_name }}\n      latest_target_commitish: ${{ steps.latest_release.outputs.target_commitish }}\n      latest_created_at: ${{ steps.latest_release.outputs.created_at }}\n      latest_published_at: ${{ steps.latest_release.outputs.published_at }}\n    steps:\n      - name: Get latest release info\n        id: latest_release\n        uses: cardinalby/git-get-release-action@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          repo: 'obsproject/obs-studio'\n          latest: true\n          \n  get_plugin_info:\n    name: '01 - Get plugin git info'\n    runs-on: [ubuntu-latest]\n    outputs:\n      git_tag_name: ${{ steps.tag_name.outputs.tag }}\n    steps:\n      - name: 'Get plugin git tag'\n        uses: devops-actions/action-get-tag@v1.0.2\n        id: tag_name\n          \n  clang_check:\n    name: '02 - Code format check'\n    runs-on: [ubuntu-latest]\n    needs: [get_plugin_info]\n    steps:\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          path: 'plugin'\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          submodules: 'recursive'\n\n      - name: 'Install clang-format'\n        run: sudo apt-get install -y clang-format-12\n\n      - name: 'Run clang-format'\n        working-directory: 'plugin'\n        run: |\n          source CI/utility/formatcode.sh\n          source CI/utility/check-format.sh\n          \n  windows_build:\n    name: '03 - Windows (Latest)'\n    runs-on: [windows-2022]\n    needs: [get_obs_info, get_plugin_info, clang_check]\n    strategy:\n      matrix:\n        arch: [64, 32]\n    env:\n      #CMAKE_GENERATOR: \"Visual Studio 17 2022\"\n      CMAKE_SYSTEM_VERSION: \"10.0.22000.0\"\n      OBS_VERSION: \"${{ needs.get_obs_info.outputs.latest_tag_name }}\"\n    steps:\n      - name: 'Add msbuild to PATH'\n        uses: microsoft/setup-msbuild@v1\n\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          path: 'plugin'\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          submodules: 'recursive'\n          \n      - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          repository: obsproject/obs-studio\n          path: 'obs-studio'\n          ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}'\n          fetch-depth: 0\n          submodules: 'recursive'\n      \n      - name: \"Install Dependencies\"\n        working-directory: 'plugin'\n        run: CI/windows/01_install_dependencies.ps1 -BuildArch ${{ matrix.arch }}-bit -NoChoco -InstallList \"obs-deps\",\"qt-deps\",\"obs-studio\"\n        \n      - name: 'Build libobs and obs-frontend-api'\n        working-directory: 'plugin'\n        run: CI/windows/02_build_obs_libs.ps1 -BuildArch \"${{ matrix.arch }}-bit\"\n        \n      - name: 'Build plugin'\n        working-directory: 'plugin'\n        run: CI/windows/03_build_plugin.ps1 -BuildArch \"${{ matrix.arch }}-bit\"\n        \n      - name: 'Create build artifact'\n        working-directory: 'plugin'\n        run: CI/windows/04_package_plugin.ps1 -BuildArch \"${{ matrix.arch }}-bit\"\n        \n      - name: 'Upload build Artifact'\n        uses: actions/upload-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-windows-${{ matrix.arch }}'\n          path: '${{ github.workspace }}/plugin/*-windows-*.zip'\n          \n  windows_package:\n    name: '04 - Windows Installer'\n    runs-on: [windows-2022]\n    needs: [get_plugin_info, windows_build]\n    env:\n      #CMAKE_GENERATOR: \"Visual Studio 17 2022\"\n      CMAKE_SYSTEM_VERSION: \"10.0.22000.0\"\n      OBS_VERSION: \"${{ needs.get_obs_info.outputs.latest_tag_name }}\"\n    steps:\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          path: 'plugin'\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          submodules: 'recursive'\n          \n      - name: 'Download 64-bit artifact'\n        uses: actions/download-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-windows-64'\n          path: 'plugin'\n\n      - name: 'Download 32-bit artifact'\n        uses: actions/download-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-windows-32'\n          path: 'plugin'\n          \n      - name: \"Install Dependencies\"\n        working-directory: 'plugin'\n        run: CI/windows/01_install_dependencies.ps1 -BuildArch 64-bit -NoChoco -InstallList \"nsis\"\n          \n      - name: 'Build NSIS installer'\n        working-directory: 'plugin'\n        run: |\n          Get-ChildItem -Filter \"*-windows-x86.zip\" -File | Expand-Archive -Force -DestinationPath ./release/\n          Get-ChildItem -Filter \"*-windows-x64.zip\" -File | Expand-Archive -Force -DestinationPath ./release/\n          CI/windows/04_package_plugin.ps1 -BuildArch 64-bit -BuildInstaller -CombinedArchs\n        \n      - name: 'Upload build Artifact'\n        uses: actions/upload-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-windows-release'\n          path: '${{ github.workspace }}/plugin/${{ env.PRODUCT_NAME }}-*-windows-all*.*'\n\n  linux_build:\n    name: '03 - Linux (Ubuntu)'\n    runs-on: ${{ matrix.ubuntu }}\n    needs: [get_obs_info, get_plugin_info, clang_check]\n    strategy:\n      matrix:\n        #ubuntu: ['ubuntu-20.04', 'ubuntu-22.04']\n        ubuntu: ['ubuntu-22.04']\n    env:\n      OBS_VERSION: \"${{ needs.get_obs_info.outputs.latest_tag_name }}\"\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          path: 'plugin'\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          submodules: 'recursive'\n          \n      - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          repository: obsproject/obs-studio\n          path: 'obs-studio'\n          ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}'\n          fetch-depth: 0\n          submodules: 'recursive'\n          \n      - name: \"Install Dependencies\"\n        working-directory: 'plugin'\n        run: source CI/linux/01_install_dependencies.sh --disable-pipewire\n        \n      - name: 'Build libobs and obs-frontend-api'\n        working-directory: 'plugin'\n        run: source CI/linux/02_build_obs_libs.sh --disable-pipewire\n        \n      - name: 'Build plugin'\n        working-directory: 'plugin'\n        run: source CI/linux/03_build_plugin.sh\n        \n      - name: 'Create build artifact'\n        working-directory: 'plugin'\n        run: source CI/linux/04_package_plugin.sh\n\n      - name: 'Upload build Artifact'\n        uses: actions/upload-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-linux-${{ matrix.ubuntu }}'\n          path: '${{ github.workspace }}/plugin/build/${{ env.PRODUCT_NAME }}-*-linux.*'\n          \n  macos_build:\n    name: '03 - macOS (Latest)'\n    runs-on: [macos-latest]\n    strategy:\n      matrix:\n        arch: ['universal', 'x86_64', 'arm64']\n    needs: [get_obs_info, get_plugin_info, clang_check]\n    env:\n      MACOSX_DEPLOYMENT_TARGET: '10.13'\n      BLOCKED_FORMULAS: 'speexdsp'\n      OBS_VERSION: \"${{ needs.get_obs_info.outputs.latest_tag_name }}\"\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          path: 'plugin'\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          submodules: 'recursive'\n          \n      - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          repository: obsproject/obs-studio\n          path: 'obs-studio'\n          ref: '${{ needs.get_obs_info.outputs.latest_tag_name }}'\n          fetch-depth: 0\n          submodules: 'recursive'\n\n      - name: 'Setup build environment'\n        run: |\n          REMOVE_FORMULAS=\"\"\n          for FORMULA in ${{ env.BLOCKED_FORMULAS }}; do\n            if [ -d \"/usr/local/opt/${FORMULA}\" ]; then\n              REMOVE_FORMULAS=\"${REMOVE_FORMULAS}${FORMULA} \"\n            fi\n          done\n          if [ -n \"${REMOVE_FORMULAS}\" ]; then\n            brew uninstall ${REMOVE_FORMULAS}\n          fi\n\n      - name: 'Install dependencies'\n        working-directory: 'plugin'\n        run: source CI/macos/01_install_dependencies.sh --architecture \"${{ matrix.arch }}\"\n\n      - name: 'Build libobs and obs-frontend-api'\n        working-directory: 'plugin'\n        run: source CI/macos/02_build_obs_libs.sh --architecture \"${{ matrix.arch }}\"\n\n      - name: 'Build plugin'\n        working-directory: 'plugin'\n        run: source CI/macos/03_build_plugin.sh --architecture \"${{ matrix.arch }}\"\n\n      - name: 'Create build artifact'\n        working-directory: 'plugin'\n        run: source CI/macos/04_package_plugin.sh --architecture \"${{ matrix.arch }}\"\n\n      - name: 'Upload build Artifact'\n        uses: actions/upload-artifact@v4\n        with:\n          name: '${{ env.PRODUCT_NAME }}-macos-${{ matrix.arch }}'\n          path: '${{ github.workspace }}/plugin/build/*-macos-${{ matrix.arch }}.*'\n\n  create_changelog:\n    name: '02 - Create Changelog'\n    runs-on: [ubuntu-latest]\n    needs: [get_plugin_info]\n    outputs:\n      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 }}\"\n\n    steps:\n      - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: actions/checkout@v4\n        with:\n          ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}'\n          fetch-depth: 0\n\n      - name: 'Get last release info'\n        id: get_last_release\n        uses: cardinalby/git-get-release-action@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          latest: true\n      \n      - name: 'Create changelog text'\n        id: create_changelog_text\n        uses: dlavrenuek/conventional-changelog-action@v1.2.3\n        with:\n          from: \"${{ steps.get_last_release.outputs.tag_name }}\"\n          to: \"${{ needs.get_plugin_info.outputs.git_tag_name }}\"\n          config-file: \"${{ github.workspace }}/.github/workflows/changelogConfig.json\"\n\n\n  create_release:\n    name: '05 - Create release'\n    runs-on: [ubuntu-latest]\n    needs: [get_plugin_info, windows_package, linux_build, macos_build, create_changelog]\n\n    steps:\n      - name: 'Check whether the version is prerelease'\n        uses: MeilCli/regex-match@v1\n        id: prerelease_test\n        with:\n          search_string: ${{ needs.get_plugin_info.outputs.git_tag_name }}\n          regex_pattern: \"^v[1-9][0-9]*.[0-9]+.[0-9]+-[a-z0-9]+$\"\n\n      - name: 'Create release ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: ncipollo/release-action@v1\n        id: create_release\n        with:\n          #bodyFile: \"body.md\"\n          body: \"${{ needs.create_changelog.outputs.changelog }}\"\n          draft: true\n          prerelease: ${{ steps.prerelease_test.outputs.matched }}\n          name: \"${{ env.PRODUCT_NAME }} ${{ needs.get_plugin_info.outputs.git_tag_name }}\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Download release artifacts'\n        uses: actions/download-artifact@v4\n\n      - name: 'Upload Windows .zip artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-windows-release/${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-all.zip\n          asset_name: ${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows.zip\n          asset_content_type: application/zip\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload Windows .exe artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-windows-release/${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-all-installer.exe\n          asset_name: ${{ env.PRODUCT_NAME }}-${{ needs.get_plugin_info.outputs.git_tag_name }}-windows-installer.exe\n          asset_content_type: application/x-msdownload\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      #- name: 'Upload linux qt5 .tar.gz artifact to release'\n      #  uses: shogo82148/actions-upload-release-asset@v1\n      #  with:\n      #    upload_url: ${{ steps.create_release.outputs.upload_url }}\n      #    asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz\n      #    asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.tar.gz\n      #    asset_content_type: application/x-gzip\n      #    github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload linux qt6 .tar.gz artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.tar.gz\n          asset_content_type: application/x-gzip\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n    #  - name: 'Upload linux qt5 .deb artifact to release'\n    #    uses: shogo82148/actions-upload-release-asset@v1\n    #    with:\n    #      upload_url: ${{ steps.create_release.outputs.upload_url }}\n    #      asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb\n    #      asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.deb\n    #      asset_content_type: application/vnd.debian.binary-package\n    #      github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload linux qt6 .deb artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.deb\n          asset_content_type: application/vnd.debian.binary-package\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n    #  - name: 'Upload linux qt5 .rpm artifact to release'\n    #    uses: shogo82148/actions-upload-release-asset@v1\n    #    with:\n    #      upload_url: ${{ steps.create_release.outputs.upload_url }}\n    #      asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm\n    #      asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.rpm\n    #      asset_content_type: application/vnd.debian.binary-package\n    #      github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload linux qt6 .rpm artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.rpm\n          asset_content_type: application/vnd.debian.binary-package\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload macos universal .zip artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-universal/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.zip\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.zip\n          asset_content_type: application/zip\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload macos universal .pkg artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-universal/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.pkg\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-universal.pkg\n          asset_content_type: application/vnd.apple.installer+xml\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n      \n      - name: 'Upload macos x86_64 .zip artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-x86_64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.zip\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.zip\n          asset_content_type: application/zip\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload macos x86_64 .pkg artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-x86_64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.pkg\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-x86_64.pkg\n          asset_content_type: application/vnd.apple.installer+xml\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload macos arm64 .zip artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-arm64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.zip\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.zip\n          asset_content_type: application/zip\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: 'Upload macos arm64 .pkg artifact to release'\n        uses: shogo82148/actions-upload-release-asset@v1\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-macos-arm64/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.pkg\n          asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-macos-arm64.pkg\n          asset_content_type: application/vnd.apple.installer+xml\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/main.yml.bak",
    "content": "name: 'CI Windows Release'\n\non:\n    release:\n      types: [published]\n#    push:\n#    paths-ignore:\n#      - 'docs/**'\n#    tags:\n#      - '[0-9]+.[0-9]+.[0-9]+'\n\njobs:\n  windows:\n    name: 'Windows 32+64bit'\n    runs-on: [windows-latest]\n    env:\n      QT_VERSION: '5.10.1'\n      WINDOWS_DEPS_VERSION: '2017'\n      CMAKE_GENERATOR: \"Visual Studio 16 2019\"\n      CMAKE_SYSTEM_VERSION: \"10.0\"\n    steps:\n      - name: 'Add msbuild to PATH'\n        uses: microsoft/setup-msbuild@v1.0.0\n      - name: 'Install prerequisite: QT'\n        run: |\n          curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C -\n          7z x Qt_${{ env.QT_VERSION }}.7z -o\"${{ github.workspace }}\\cmbuild\\QT\"\n      - name: 'Install prerequisite: Pre-built OBS dependencies'\n        run: |\n          curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -\n          7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o\"${{ github.workspace }}\\cmbuild\\deps\"\n      - name: 'Install prerequisite: NSIS'\n        working-directory: ${{ github.workspace }}\n        run: |\n          Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')\n          scoop bucket add extras\n          scoop install nsis\n      - name: 'Checkout OBS'\n        uses: actions/checkout@v2\n        with:\n          repository: obsproject/obs-studio\n          path: ${{ github.workspace }}/obs-studio\n          submodules: 'recursive'\n      - name: 'Checkout obs-rtspserver'\n        uses: actions/checkout@v2\n        with:\n          path: ${{ github.workspace }}/obs-studio/plugins/obs-rtspserver\n          submodules: 'recursive'\n      - name: 'Get OBS-Studio git info'\n        shell: bash\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          git fetch --prune --unshallow\n          echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD)\n          echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD)\n          echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0)\n      - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'\n        shell: bash\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          git checkout ${{ env.OBS_GIT_TAG }}\n          git submodule update\n      - name: 'Get obs-rtspserver git info'\n        shell: bash\n        working-directory: ${{ github.workspace }}/obs-studio/plugins/obs-rtspserver\n        run: |\n          git fetch --prune --unshallow\n          echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }}\n          echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD)\n          echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0)\n#     - name: 'Restore OBS 32-bit build v${{ env.OBS_GIT_TAG }} from cache'\n#       id: build-cache-obs-32\n#       uses: actions/cache@v1\n#       env:\n#         CACHE_NAME: 'build-cache-obs-32'\n#       with:\n#         path: ${{ github.workspace }}/obs-studio/build32\n#         key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}\n#         restore-keys: |\n#           ${{ runner.os }}-${{ env.CACHE_NAME }}-\n      - name: 'Add obs-rtspserver Subdirectory'\n        working-directory: ${{ github.workspace }}/obs-studio/plugins\n        run: echo \"add_subdirectory(obs-rtspserver)\" >> .\\CMakeLists.txt\n      - name: 'Configure OBS 32-bit'\n#       if: steps.build-cache-obs-32.outputs.cache-hit != 'true'\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          mkdir .\\build32\n          cd .\\build32\n          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 ..\n      - name: 'Build obs-rtspserver 32-bit'\n#       if: steps.build-cache-obs-32.outputs.cache-hit != 'true'\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          msbuild /m /p:Configuration=RelWithDebInfo .\\build32\\plugins\\obs-rtspserver\\obs-rtspserver.vcxproj\n#     - name: 'Restore OBS 64-bit build v${{ env.OBS_GIT_TAG }} from cache'\n#       id: build-cache-obs-64\n#       uses: actions/cache@v1\n#       env:\n#         CACHE_NAME: 'build-cache-obs-64'\n#       with:\n#         path: ${{ github.workspace }}/obs-studio/build64\n#         key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}\n#         restore-keys: |\n#           ${{ runner.os }}-${{ env.CACHE_NAME }}-\n      - name: 'Configure OBS 64-bit'\n#       if: steps.build-cache-obs-64.outputs.cache-hit != 'true'\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          mkdir .\\build64\n          cd .\\build64\n          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 ..\n      - name: 'Build obs-rtspserver 64-bit'\n#       if: steps.build-cache-obs-64.outputs.cache-hit != 'true'\n        working-directory: ${{ github.workspace }}/obs-studio\n        run: |\n          msbuild /m /p:Configuration=RelWithDebInfo .\\build64\\plugins\\obs-rtspserver\\obs-rtspserver.vcxproj\n      - name: 'Set release filename'\n        shell: bash\n        run: |\n          FILENAME=\"obs-rtspserver-${{ env.GIT_TAG }}-windows\"\n          echo \"::set-env name=WIN_FILENAME::$FILENAME\"\n      - name: 'Package obs-rtspserver'\n        working-directory: ${{ github.workspace }}\n        run: |\n          mkdir build-package\\obs-plugins\\64bit\n          mkdir build-package\\obs-plugins\\32bit\n          mkdir build-package\\data\\obs-plugins\\obs-rtspserver\\locale\\\n          robocopy .\\obs-studio\\build64\\plugins\\obs-rtspserver\\RelWithDebInfo\\ .\\build-package\\obs-plugins\\64bit\\ obs-rtspserver.dll obs-rtspserver.pdb\n          robocopy .\\obs-studio\\build32\\plugins\\obs-rtspserver\\RelWithDebInfo\\ .\\build-package\\obs-plugins\\32bit\\ obs-rtspserver.dll obs-rtspserver.pdb\n          robocopy /E .\\obs-studio\\plugins\\obs-rtspserver\\data\\ .\\build-package\\data\\obs-plugins\\obs-rtspserver\\ *\n          robocopy .\\obs-studio\\plugins\\obs-rtspserver\\ .\\installer\\ LICENSE\n          robocopy .\\obs-studio\\plugins\\obs-rtspserver\\installer\\ .\\installer\\ installer.nsi obs.ico\n          mkdir release\n          7z a \".\\release\\${{ env.WIN_FILENAME }}.zip\" \".\\build-package\\*\"\n      - name: 'Publish ${{ env.WIN_FILENAME }}.zip'\n        id: create_release\n        if: success()\n        uses: actions/upload-artifact@v2-preview\n        with:\n          name: '${{ env.GIT_TAG }}-windows'\n          path: ${{ github.workspace }}\\release\\*.zip\n      - name: Build obs-rtspserver installer\n        working-directory: ${{ github.workspace }}\\installer\n        run: makensis /DVERSION=${{ env.GIT_TAG }} .\\installer.nsi\n      - name: 'Publish ${{ env.WIN_FILENAME }}-windows-installer.exe'\n        if: success()\n        uses: actions/upload-artifact@v2-preview\n        with:\n          name: '${{ env.GIT_TAG }}-windows-installer'\n          path: ${{ github.workspace }}\\build-package\\*.exe\n\n  make-release:\n    name: 'Upload release'\n    runs-on: [ubuntu-latest]\n    needs: [windows]\n    steps:\n      - name: 'Get the version'\n        shell: bash\n        id: get_version\n        run: |\n          echo ::set-env name=TAG_VERSION::${GITHUB_REF/refs\\/tags\\//}\n#      - name: 'Create Release'\n#        id: create_release\n#         uses: actions/create-release@v1\n#        env:\n#          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n#        with:\n#          tag_name: ${{ env.TAG_VERSION }}\n#          release_name: obs-rtspserver ${{ env.TAG_VERSION }}\n#          draft: false\n#          prerelease: false\n      - name: 'Download release artifacts'\n        uses: actions/download-artifact@v2-preview\n      - name: 'Upload Windows .zip artifact to release'\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ github.event.release.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-windows/obs-rtspserver-${{ env.TAG_VERSION }}-windows.zip\n          asset_name: obs-rtspserver-${{ env.TAG_VERSION }}-windows.zip\n          asset_content_type: application/zip\n      - name: 'Upload Windows .exe artifact to release'\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ github.event.release.upload_url }}\n          asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-windows-installer/obs-rtspserver-${{ env.TAG_VERSION }}-windows-installer.exe\n          asset_name: obs-rtspserver-${{ env.TAG_VERSION }}-windows-installer.exe\n          asset_content_type: application/zip\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: 'test'\n\non:\n    #release:\n    #  types: [published]\n  push:\n    tags:\n      - test[0-9]+\njobs:\n  create-release:\n    name: '05 - Create release'\n    runs-on: [ubuntu-latest]\n    \n    steps:\n      - name: 'Checkout plugin'\n        uses: actions/checkout@v2.3.3\n\n      - name: 'Create release ${{ needs.get_plugin_info.outputs.git_tag_name }}'\n        uses: ncipollo/release-action@v1\n        id: create_release\n        with:\n          #bodyFile: \"body.md\"\n          #omitBody: true\n          #omitBodyDuringUpdate: true\n          draft: true\n          prerelease: true\n          token: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/text.yml.bak",
    "content": "on: \n  release:\n    types: [published]\n\njobs:\n  one:\n    runs-on: ubuntu-16.04\n    steps:\n      - name: Dump GitHub context\n        env:\n          GITHUB_CONTEXT: ${{ toJson(github) }}\n        run: echo \"$GITHUB_CONTEXT\"\n      - name: Dump job context\n        env:\n          JOB_CONTEXT: ${{ toJson(job) }}\n        run: echo \"$JOB_CONTEXT\"\n      - name: Dump steps context\n        env:\n          STEPS_CONTEXT: ${{ toJson(steps) }}\n        run: echo \"$STEPS_CONTEXT\"\n      - name: Dump runner context\n        env:\n          RUNNER_CONTEXT: ${{ toJson(runner) }}\n        run: echo \"$RUNNER_CONTEXT\"\n      - name: Dump strategy context\n        env:\n          STRATEGY_CONTEXT: ${{ toJson(strategy) }}\n        run: echo \"$STRATEGY_CONTEXT\"\n      - name: Dump matrix context\n        env:\n          MATRIX_CONTEXT: ${{ toJson(matrix) }}\n        run: echo \"$MATRIX_CONTEXT\"\n"
  },
  {
    "path": ".gitignore",
    "content": "*.aps\nrtspoutput.rc\nresource.h\nbundle/installer-macos.generated.pkgproj\nbundle/installer-macos.pkgproj\nbundle/LICENSE.txt\nbundle/macOS/Plugin-Info.plist\ninstaller/LICENSE\nbuild*/\nrelease/\n.vs/\n.vscode/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"rtsp-server/3rdpart/libb64/libb64\"]\n\tpath = rtsp-server/3rdpart/libb64/libb64\n\turl = https://github.com/libb64/libb64\n"
  },
  {
    "path": "CI/build-linux.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux plugin build script\n##############################################################################\n#\n# This script contains all steps necessary to:\n#\n#   * Build libobs and obs-frontend-api with all required dependencies\n#   * Build your plugin\n#   * Create debian package\n#\n# Parameters:\n#   -h, --help                      : Print usage help\n#   -q, --quiet                     : Suppress most build process output\n#   -v, --verbose                   : Enable more verbose build process output\n#   -p, --package                   : Create installer for plugin\n#   -b, --build-dir                 : Specify alternative build directory\n#                                     (default: build)\n#\n# Environment Variables (optional):\n#   OBS_VERSION         : OBS Version\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\n## SET UP ENVIRONMENT ##\n_RUN_OBS_BUILD_SCRIPT=TRUE\n\nCHECKOUT_DIR=\"$(git rev-parse --show-toplevel)\"\nif [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n    source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\nfi\nPRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\nDEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\nOBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\nsource \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\nsource \"${CHECKOUT_DIR}/CI/include/build_support_linux.sh\"\n\n## DEPENDENCY INSTALLATION ##\nsource \"${CHECKOUT_DIR}/CI/linux/01_install_dependencies.sh\"\n\n## OBS LIBRARY BUILD ##\nsource \"${CHECKOUT_DIR}/CI/linux/02_build_obs_libs.sh\"\n\n## PLUGIN BUILD ##\nsource \"${CHECKOUT_DIR}/CI/linux/03_build_plugin.sh\"\n\n## PLUGIN PACKAGE AND NOTARIZE ##\nsource \"${CHECKOUT_DIR}/CI/linux/04_package_plugin.sh\"\n\n## MAIN SCRIPT FUNCTIONS ##\nprint_usage() {\n    echo -e \"build_linux.sh - Build script for ${PRODUCT_NAME}\\n\"\n    echo -e \"Usage: ${0}\\n\" \\\n        \"-h, --help                     : Print this help\\n\" \\\n        \"-q, --quiet                    : Suppress most build process output\\n\" \\\n        \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n        \"-d, --skip-dependency-checks   : Skip dependency checks\\n\" \\\n        \"-p, --package                  : Create installer for plugin\\n\" \\\n        \"-b, --build-dir                : Specify alternative build directory (default: build)\\n\"\n\n}\n\nobs-build-main() {\n    while true; do\n        case \"${1}\" in\n            -h | --help ) print_usage; exit 0 ;;\n            -d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;;\n            -q | --quiet ) export QUIET=TRUE; shift ;;\n            -v | --verbose ) export VERBOSE=TRUE; shift ;;\n            -p | --package ) PACKAGE=TRUE; shift ;;\n            -b | --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n            -- ) shift; break ;;\n            * ) break ;;\n        esac\n    done\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n    step \"Fetching version tags...\"\n    git fetch origin --tags\n    GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n    GIT_HASH=$(git rev-parse --short HEAD)\n    GIT_TAG=$(git describe --tags --abbrev=0 2&>/dev/null || true)\n    FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-linux\"\n\n    if [ -z \"${SKIP_DEP_CHECKS}\" ]; then\n        install_dependencies\n    fi\n\n    build_obs_libs\n    build_obs_plugin\n\n    if [ -n \"${PACKAGE}\" ]; then\n        package_obs_plugin\n    fi\n\n    cleanup\n}\n\nobs-build-main $*\n"
  },
  {
    "path": "CI/build-macos.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS plugin build script\n##############################################################################\n#\n# This script contains all steps necessary to:\n#\n#   * Build libobs and obs-frontend-api with all required dependencies\n#   * Build your plugin\n#   * Create macOS module bundle\n#   * Create macOS plugin installer package\n#   * Notarize macOS plugin installer package\n#\n# Parameters:\n#   -h, --help                      : Print usage 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#   -c, --codesign                  : Codesign plugin and installer\n#   -n, --notarize                  : Notarize plugin installer\n#                                     (implies --codesign)\n#   -b, --build-dir                 : Specify alternative build directory\n#                                     (default: build)\n#\n# Environment Variables (optional):\n#   MACOS_DEPS_VERSION  : Pre-compiled macOS dependencies version\n#   QT_VERSION          : Pre-compiled Qt version\n#   OBS_VERSION         : OBS version\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\n## SET UP ENVIRONMENT ##\n_RUN_OBS_BUILD_SCRIPT=TRUE\n\nCHECKOUT_DIR=\"$(/usr/bin/git rev-parse --show-toplevel)\"\nif [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n    source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\nfi\nPRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\nDEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\nOBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\nsource \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\nsource \"${CHECKOUT_DIR}/CI/include/build_support_macos.sh\"\n\n## DEPENDENCY INSTALLATION ##\nsource \"${CHECKOUT_DIR}/CI/macos/01_install_dependencies.sh\"\n\n## OBS LIBRARY BUILD ##\nsource \"${CHECKOUT_DIR}/CI/macos/02_build_obs_libs.sh\"\n\n## PLUGIN BUILD ##\nsource \"${CHECKOUT_DIR}/CI/macos/03_build_plugin.sh\"\n\n## PLUGIN PACKAGE AND NOTARIZE ##\nsource \"${CHECKOUT_DIR}/CI/macos/04_package_plugin.sh\"\n\n## MAIN SCRIPT FUNCTIONS ##\nprint_usage() {\n    echo -e \"build_macos.sh - Build script for ${PRODUCT_NAME}\\n\"\n    echo -e \"Usage: ${0}\\n\" \\\n        \"-h, --help                     : Print this help\\n\" \\\n        \"-q, --quiet                    : Suppress most build process output\\n\" \\\n        \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n        \"-a, --architecture             : Specify build architecture (default: universal, alternative: x86_64, arm64)\\n\" \\\n        \"-d, --skip-dependency-checks   : Skip dependency checks\\n\" \\\n        \"-p, --package                  : Create installer for plugin\\n\" \\\n        \"-c, --codesign                 : Codesign plugin and installer\\n\" \\\n        \"-n, --notarize                 : Notarize plugin installer (implies --codesign)\\n\" \\\n        \"-b, --build-dir                : Specify alternative build directory (default: build)\\n\"\n}\n\nobs-build-main() {\n    while true; do\n        case \"${1}\" in\n            -h | --help ) print_usage; exit 0 ;;\n            -q | --quiet ) export QUIET=TRUE; shift ;;\n            -v | --verbose ) export VERBOSE=TRUE; shift ;;\n            -a | --architecture ) ARCH=\"${2}\"; shift 2 ;;\n            -d | --skip-dependency-checks ) SKIP_DEP_CHECKS=TRUE; shift ;;\n            -p | --package ) PACKAGE=TRUE; shift ;;\n            -c | --codesign ) CODESIGN=TRUE; shift ;;\n            -n | --notarize ) NOTARIZE=TRUE; PACKAGE=TRUE CODESIGN=TRUE; shift ;;\n            -b | --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n            -- ) shift; break ;;\n            * ) break ;;\n        esac\n    done\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n    check_macos_version\n    check_archs\n    step \"Fetching version tags...\"\n    /usr/bin/git fetch origin --tags\n    GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD)\n    GIT_HASH=$(/usr/bin/git rev-parse --short HEAD)\n    GIT_TAG=$(/usr/bin/git describe --tags --abbrev=0 2&>/dev/null || true)\n\n    if [ \"${ARCH}\" = \"arm64\" ]; then\n        FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Apple.pkg\"\n    elif [ \"${ARCH}\" = \"x86_64\" ]; then\n        FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Intel.pkg\"\n    else\n        FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-${GIT_HASH}-macOS-Universal.pkg\"\n    fi\n\n    if [ -z \"${SKIP_DEP_CHECKS}\" ]; then\n        install_dependencies\n    fi\n\n    build_obs_libs\n    build_obs_plugin\n\n    if [ -n \"${PACKAGE}\" ]; then\n        package_obs_plugin\n    fi\n\n    if [ -n \"${NOTARIZE}\" ]; then\n        notarize_obs_plugin\n    fi\n\n    cleanup\n}\n\nobs-build-main $*\n"
  },
  {
    "path": "CI/build-windows.ps1",
    "content": "Param(\n    [Switch]$Help,\n    [Switch]$Quiet,\n    [Switch]$Verbose,\n    [Switch]$NoChoco,\n    [Switch]$SkipDependencyChecks,\n    [Switch]$BuildInstaller,\n    [Switch]$CombinedArchs,\n    [String]$BuildDirectory = \"build\",\n    [String]$BuildArch = (Get-CimInstance CIM_OperatingSystem).OSArchitecture,\n    [String]$BuildConfiguration = \"RelWithDebInfo\"\n)\n\n##############################################################################\n# Windows plugin build script\n##############################################################################\n#\n# This script contains all steps necessary to:\n#\n#   * Build libobs and obs-frontend-api with all required dependencies\n#   * Build your plugin\n#   * Create 64-bit or 32-bit packages\n#   * Create 64-bit or 32-bit installation packages\n#   * Create combined installation packages\n#\n# Parameters:\n#   -Help                   : Print usage help\n#   -NoChco                 : Skip automatic dependency installation\n#                             via Chocolatey\n#   -SkipDependencyChecks   : Skips dependency checks\n#   -BuildDirectory         : Directory to use for builds\n#                             Default: Win64 on 64-bit systems\n#                                      Win32 on 32-bit systems\n#  -BuildArch               : Build architecture to use (32bit or 64bit)\n#  -BuildConfiguration      : Build configuration to use\n#                             Default: RelWithDebInfo\n#  -BuildInstaller          : Build InnoSetup installer - Default: off\"\n#  -CombinedArchs           : Create combined packages and installer\n#                             (64-bit and 32-bit) - Default: off\"\n#\n# Environment Variables (optional):\n#  WindowsDepsVersion       : Pre-compiled Windows dependencies version\n#  WindowsQtVersion         : Pre-compiled Qt version\n#  ObsVersion               : OBS Version\n#\n##############################################################################\n\n$ErrorActionPreference = \"Stop\"\n\n$_RunObsBuildScript = $true\n\n$CheckoutDir = git rev-parse --show-toplevel\n\n$DepsBuildDir = \"${CheckoutDir}/../obs-build-dependencies\"\n$ObsBuildDir = \"${CheckoutDir}/../obs-studio\"\n\nif (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) {\n    . ${CheckoutDir}/CI/include/build_environment.ps1\n}\n\n. ${CheckoutDir}/CI/include/build_support_windows.ps1\n\n## DEPENDENCY INSTALLATION ##\n. ${CheckoutDir}/CI/windows/01_install_dependencies.ps1\n\n## OBS LIBRARY BUILD ##\n. ${CheckoutDir}/CI/windows/02_build_obs_libs.ps1\n\n## PLUGIN BUILD ##\n. ${CheckoutDir}/CI/windows/03_build_plugin.ps1\n\n## PLUGIN PACKAGE AND NOTARIZE ##\n. ${CheckoutDir}/CI/windows/04_package_plugin.ps1\n\n## MAIN SCRIPT FUNCTIONS ##\nfunction Build-Obs-Plugin-Main {\n    Ensure-Directory ${CheckoutDir}\n    Write-Step \"Fetching version tags...\"\n    & git fetch origin --tags\n    $GitBranch = git rev-parse --abbrev-ref HEAD\n    $GitHash = git rev-parse --short HEAD\n    $ErrorActionPreference = \"SilentlyContiue\"\n    $GitTag = git describe --tags --abbrev=0\n    $ErrorActionPreference = \"Stop\"\n\n    if ($GitTag -eq $null) {\n        $GitTag=$ProductVersion\n    }\n\n    $FileName = \"${ProductName}-${GitTag}-${GitHash}\"\n\n    if(!($SkipDependencyChecks.isPresent)) {\n        Install-Dependencies -NoChoco:$NoChoco\n    }\n\n    if($CombinedArchs.isPresent) {\n        Build-OBS-Libs -BuildArch 64-bit\n        Build-OBS-Libs -BuildArch 32-bit\n        Build-OBS-Plugin -BuildArch 64-bit\n        Build-OBS-Plugin -BuildArch 32-bit\n    } else {\n        Build-OBS-Libs\n        Build-OBS-Plugin\n    }\n\n    Package-OBS-Plugin\n}\n\nfunction Print-Usage {\n    Write-Host \"build-windows.ps1 - Build script for ${ProductName}\"\n    $Lines = @(\n        \"Usage: ${MyInvocation.MyCommand.Name}\",\n        \"-Help                    : Print this help\",\n        \"-Quiet                   : Suppress most build process output\"\n        \"-Verbose                 : Enable more verbose build process output\"\n        \"-NoChoco                 : Skip automatic dependency installation via Chocolatey - Default: on\",\n        \"-SkipDependencyChecks    : Skips dependency checks - Default: off\",\n        \"-BuildDirectory          : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems\",\n        \"-BuildArch               : Build architecture to use (32bit or 64bit) - Default: local architecture\",\n        \"-BuildConfiguration      : Build configuration to use - Default: RelWithDebInfo\",\n        \"-BuildInstaller          : Build InnoSetup installer - Default: off\",\n        \"-CombinedArchs           : Create combined packages and installer (64-bit and 32-bit) - Default: off\"\n    )\n    $Lines | Write-Host\n}\n\nif($Help.isPresent) {\n    Print-Usage\n    exit 0\n}\n\nBuild-Obs-Plugin-Main\n"
  },
  {
    "path": "CI/include/Brewfile",
    "content": "brew \"cmake\"\nbrew \"ninja\"\nbrew \"coreutils\"\n"
  },
  {
    "path": "CI/include/Xcnotary",
    "content": "brew \"akeru-inc/tap/xcnotary\"\n"
  },
  {
    "path": "CI/include/build_environment.ps1",
    "content": "$ProductName = \"obs-rtspserver\"\n#$ProductVersion = \"@CMAKE_PROJECT_VERSION@\"\n"
  },
  {
    "path": "CI/include/build_environment.ps1.in",
    "content": "$ProductName = \"@CMAKE_PROJECT_NAME@\"\n$ProductVersion = \"@CMAKE_PROJECT_VERSION@\"\n"
  },
  {
    "path": "CI/include/build_environment.sh",
    "content": "PRODUCT_NAME=\"obs-rtspserver\"\n#PRODUCT_VERSION=\"@CMAKE_PROJECT_VERSION@\"\n#LINUX_MAINTAINER_EMAIL=\"@LINUX_MAINTAINER_EMAIL@\"\n"
  },
  {
    "path": "CI/include/build_environment.sh.in",
    "content": "PRODUCT_NAME=\"@CMAKE_PROJECT_NAME@\"\nPRODUCT_VERSION=\"@CMAKE_PROJECT_VERSION@\"\nLINUX_MAINTAINER_EMAIL=\"@LINUX_MAINTAINER_EMAIL@\"\n"
  },
  {
    "path": "CI/include/build_support.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Unix support functions\n##############################################################################\n#\n# This script file can be included in build scripts for UNIX-compatible\n# shells to compose build scripts.\n#\n##############################################################################\n\n## DEFINE UTILITIES ##\n\nif [ -z \"${QUIET}\" ]; then\n    status() {\n        echo -e \"${COLOR_BLUE}[${PRODUCT_NAME}] ${1}${COLOR_RESET}\"\n    }\n\n    step() {\n        echo -e \"${COLOR_GREEN}  + ${1}${COLOR_RESET}\"\n    }\n\n    info() {\n        echo -e \"${COLOR_ORANGE}  + ${1}${COLOR_RESET}\"\n    }\n\n    error() {\n        echo -e \"${COLOR_RED}  + ${1}${COLOR_RESET}\"\n    }\nelse\n    status() {\n        :\n    }\n\n    step() {\n        :\n    }\n\n    info() {\n        :\n    }\n\n    error() {\n        echo -e \"${COLOR_RED}  + ${1}${COLOR_RESET}\"\n    }\nfi\n\nexists() {\n  /usr/bin/command -v \"$1\" >/dev/null 2>&1\n}\n\nensure_dir() {\n    [[ -n \"${1}\" ]] && /bin/mkdir -p \"${1}\" && builtin cd \"${1}\"\n}\n\ncleanup() {\n    :\n}\n\ncaught_error() {\n    error \"ERROR during build step: ${1}\"\n    cleanup\n    exit 1\n}\n\n# Setup build environment\n\nBUILD_DIR=\"${BUILD_DIR:-build}\"\nBUILD_CONFIG=\"${BUILD_CONFIG:-RelWithDebInfo}\"\n#CI_WORKFLOW=\"${CHECKOUT_DIR}/.github/workflows/main.yml\"\nCURRENT_ARCH=$(uname -m)\nCURRENT_DATE=\"$(date +\"%Y-%m-%d\")\"\n\nif [ \"${GITHUB_ACTIONS}\" = \"true\" ]; then\n    CI=\"true\"\nfi\n\n## Utility functions ##\n\ncheck_ccache() {\n    if ccache -V >/dev/null 2>&1; then\n        info \"CCache available\"\n        CMAKE_CCACHE_OPTIONS=\"-DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache\"\n\n        if [ \"${CI}\" ]; then\n            ccache --set-config=cache_dir=${GITHUB_WORKSPACE:-${HOME}}/.ccache\n            ccache --set-config=max_size=${CCACHE_SIZE:-500M}\n            ccache --set-config=compression=true\n            ccache -z\n        fi\n    else\n        info \"CCache not available\"\n    fi\n}\n\n_add_ccache_to_path() {\n    if [ \"${CMAKE_CCACHE_OPTIONS}\" ]; then\n        PATH=\"/usr/local/opt/ccache/libexec:${PATH}\"\n        status \"Compiler Info:\"\n        local IFS=$'\\n'\n        for COMPILER_INFO in $(type cc c++ gcc g++ clang clang++ || true); do\n            info \"${COMPILER_INFO}\"\n        done\n    fi\n}\n\nsafe_fetch() {\n    if [ $# -lt 2 ]; then\n        error \"Usage: safe_fetch URL HASH\"\n        return 1\n    fi\n\n    while true; do\n        case \"${1}\" in\n            -n | --nocontinue ) NOCONTINUE=TRUE; shift ;;\n            -- ) shift; break ;;\n            * ) break ;;\n        esac\n    done\n\n    DOWNLOAD_URL=\"${1}\"\n    DOWNLOAD_HASH=\"${2}\"\n    DOWNLOAD_FILE=\"$(basename ${DOWNLOAD_URL})\"\n    CURLCMD=${CURLCMD:-curl}\n\n\n    if [ \"${NOCONTINUE}\" ]; then\n        ${CURLCMD/--continue-at -/} \"${DOWNLOAD_URL}\"\n    else\n        ${CURLCMD} \"${DOWNLOAD_URL}\"\n    fi\n\n    if [ \"${DOWNLOAD_HASH}\" = \"$(sha256sum \"${DOWNLOAD_FILE}\" | cut -d \" \" -f 1)\" ]; then\n        info \"${DOWNLOAD_FILE} downloaded successfully and passed hash check\"\n        return 0\n    else\n        error \"${DOWNLOAD_FILE} downloaded successfully and failed hash check\"\n        return 1\n    fi\n}\n\ncheck_and_fetch() {\n    if [ $# -lt 2 ]; then\n        caught_error \"Usage: check_and_fetch URL HASH\"\n    fi\n\n    while true; do\n        case \"${1}\" in\n            -n | --nocontinue ) NOCONTINUE=TRUE; shift ;;\n            -- ) shift; break ;;\n            * ) break ;;\n        esac\n    done\n\n    DOWNLOAD_URL=\"${1}\"\n    DOWNLOAD_HASH=\"${2}\"\n    DOWNLOAD_FILE=\"$(basename \"${DOWNLOAD_URL}\")\"\n\n    if [ -f \"${DOWNLOAD_FILE}\" ] && [ \"${DOWNLOAD_HASH}\" = \"$(sha256sum \"${DOWNLOAD_FILE}\" | cut -d \" \" -f 1)\" ]; then\n        info \"${DOWNLOAD_FILE} exists and passed hash check\"\n        return 0\n    else\n        safe_fetch \"${DOWNLOAD_URL}\" \"${DOWNLOAD_HASH}\"\n    fi\n}\n\ngithub_fetch() {\n    if [ $# -ne 3 ]; then\n        error \"Usage: github_fetch GITHUB_USER GITHUB_REPOSITORY GITHUB_COMMIT_HASH\"\n        return 1\n    fi\n\n    GH_USER=\"${1}\"\n    GH_REPO=\"${2}\"\n    GH_REF=\"${3}\"\n\n    if [ -d \"./.git\" ]; then\n        info \"Repository ${GH_USER}/${GH_REPO} already exists, updating...\"\n        git config advice.detachedHead false\n        git config remote.origin.url \"https://github.com/${GH_USER}/${GH_REPO}.git\"\n        git config remote.origin.fetch \"+refs/heads/master:refs/remotes/origin/master\"\n        git config remote.origin.tapOpt --no-tags\n\n        if ! git rev-parse -q --verify \"${GH_COMMIT}^{commit}\"; then\n            git fetch origin\n        fi\n\n        git checkout -f \"${GH_REF}\" --\n        git reset --hard \"${GH_REF}\" --\n        if [ -d \"./.gitmodules\" ]; then\n            git submodule foreach --recursive git submodule sync\n            git submodule update --init --recursive\n        fi\n\n    else\n        git clone \"https://github.com/${GH_USER}/${GH_REPO}.git\" \"$(pwd)\"\n        git config advice.detachedHead false\n        info \"Checking out commit ${GH_REF}..\"\n        git checkout -f \"${GH_REF}\" --\n\n        if [ -d \"./.gitmodules\" ]; then\n            git submodule foreach --recursive git submodule sync\n            git submodule update --init --recursive\n        fi\n    fi\n}\n\napply_patch() {\n    if [ $# -ne 2 ]; then\n        error \"Usage: apply_patch PATCH_URL PATCH_HASH\"\n        return 1\n    fi\n\n    COMMIT_URL=\"${1}\"\n    COMMIT_HASH=\"${2}\"\n    PATCH_FILE=\"$(basename ${COMMIT_URL})\"\n\n    if [ \"${COMMIT_URL:0:5}\" = \"https\" ]; then\n        ${CURLCMD:-curl} \"${COMMIT_URL}\"\n        if [ \"${COMMIT_HASH}\" = \"$(sha256sum ${PATCH_FILE} | cut -d \" \" -f 1)\" ]; then\n            info \"${PATCH_FILE} downloaded successfully and passed hash check\"\n        else\n            error \"${PATCH_FILE} downloaded successfully and failed hash check\"\n            return 1\n        fi\n\n        info \"Applying patch ${COMMIT_URL}\"\n    else\n        PATCH_FILE=\"${COMMIT_URL}\"\n    fi\n\n    patch -g 0 -f -p1 -i \"${PATCH_FILE}\"\n}\n"
  },
  {
    "path": "CI/include/build_support_linux.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux support functions\n##############################################################################\n#\n# This script file can be included in build scripts for Linux.\n#\n##############################################################################\n\n# Setup build environment\n\n# CI_OBS_VERSION=$(cat \"${CI_WORKFLOW}\" | sed -En \"s/[ ]+OBS_VERSION: '([0-9\\.]+)'/\\1/p\")\n\nif [ \"${TERM-}\" -a -z \"${CI}\" ]; then\n    COLOR_RED=$(tput setaf 1)\n    COLOR_GREEN=$(tput setaf 2)\n    COLOR_BLUE=$(tput setaf 4)\n    COLOR_ORANGE=$(tput setaf 3)\n    COLOR_RESET=$(tput sgr0)\nelse\n    COLOR_RED=$(echo -e '\\033[31m')\n    COLOR_GREEN=$(echo -e '\\033[32m')\n    COLOR_BLUE=$(echo -e '\\033[34m')\n    COLOR_ORANGE=$(echo -e '\\033[33m')\n    COLOR_RESET=$(echo -e '\\033[0m')\nfi\n\nif [ \"${CI}\" -o \"${QUIET}\" ]; then\n    export CURLCMD=\"curl --silent --show-error --location -O\"\nelse\n    export CURLCMD=\"curl --progress-bar --location --continue-at - -O\"\nfi\n"
  },
  {
    "path": "CI/include/build_support_macos.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS support functions\n##############################################################################\n#\n# This script file can be included in build scripts for macOS.\n#\n##############################################################################\n\n# Setup build environment\n\n#CI_DEPS_VERSION=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+DEPS_VERSION_MAC: '([0-9\\-]+)'/\\1/p\")\n#CI_DEPS_HASH=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+DEPS_HASH_MAC: '([0-9a-f]+)'/\\1/p\")\n#CI_QT_VERSION=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+QT_VERSION_MAC: '([0-9\\.]+)'/\\1/p\" | /usr/bin/head -1)\n#CI_QT_HASH=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+QT_HASH_MAC: '([0-9a-f]+)'/\\1/p\")\n#CI_MACOSX_DEPLOYMENT_TARGET=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+MACOSX_DEPLOYMENT_TARGET: '([0-9\\.]+)'/\\1/p\")\n#CI_OBS_VERSION=$(/bin/cat \"${CI_WORKFLOW}\" | /usr/bin/sed -En \"s/[ ]+OBS_VERSION: '([0-9\\.]+)'/\\1/p\")\nMACOS_DEPS_VERSION=${MACOS_DEPS_VERSION:-${DEPS_VERSION_MAC}}\nMACOS_DEPS_HASH=${MACOS_DEPS_HASH:-${DEPS_HASH_MAC}}\nQT_HASH=${QT_HASH:-${QT_HASH_MAC}}\n\nMACOS_VERSION=\"$(/usr/bin/sw_vers -productVersion)\"\nMACOS_MAJOR=\"$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 1)\"\nMACOS_MINOR=\"$(echo ${MACOS_VERSION} | /usr/bin/cut -d '.' -f 2)\"\n\nif [ \"${TERM-}\" -a -z \"${CI}\" ]; then\n    COLOR_RED=$(/usr/bin/tput setaf 1)\n    COLOR_GREEN=$(/usr/bin/tput setaf 2)\n    COLOR_BLUE=$(/usr/bin/tput setaf 4)\n    COLOR_ORANGE=$(/usr/bin/tput setaf 3)\n    COLOR_RESET=$(/usr/bin/tput sgr0)\nelse\n    COLOR_RED=$(echo -e '\\033[31m')\n    COLOR_GREEN=$(echo -e '\\033[32m')\n    COLOR_BLUE=$(echo -e '\\033[34m')\n    COLOR_ORANGE=$(echo -e '\\033[33m')\n    COLOR_RESET=$(echo -e '\\033[0m')\nfi\n\n## DEFINE UTILITIES ##\ncheck_macos_version() {\n    step \"Check macOS version...\"\n    #MIN_VERSION=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}}\n    MIN_VERSION=${MACOSX_DEPLOYMENT_TARGET}\n    MIN_MAJOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 1)\n    MIN_MINOR=$(echo ${MIN_VERSION} | /usr/bin/cut -d '.' -f 2)\n\n    if [ \"${MACOS_MAJOR}\" -lt \"11\" ] && [ \"${MACOS_MINOR}\" -lt \"${MIN_MINOR}\" ]; then\n        error \"WARNING: Minimum required macOS version is ${MIN_VERSION}, but running on ${MACOS_VERSION}\"\n    fi\n\n    if [ \"${MACOS_MAJOR}\" -ge \"11\" ]; then\n        export CODESIGN_LINKER=\"ON\"\n    fi\n}\n\ninstall_homebrew_deps() {\n    if ! exists brew; then\n        error \"Homebrew not found - please install homebrew (https://brew.sh)\"\n        exit 1\n    fi\n\n    brew bundle --file \"${CHECKOUT_DIR}/CI/include/Brewfile\" ${QUIET:+--quiet}\n\n    check_curl\n}\n\ncheck_curl() {\n    if [ \"${MACOS_MAJOR}\" -lt \"11\" ] && [ \"${MACOS_MINOR}\" -lt \"15\" ]; then\n        if [ ! -d /usr/local/opt/curl ]; then\n            step \"Installing Homebrew curl..\"\n            brew install curl\n        fi\n\n        CURLCMD=\"/usr/local/opt/curl/bin/curl\"\n    else\n        CURLCMD=\"curl\"\n    fi\n\n    if [ \"${CI}\" -o \"${QUIET}\" ]; then\n        export CURLCMD=\"${CURLCMD} --silent --show-error --location -O\"\n    else\n        export CURLCMD=\"${CURLCMD} --progress-bar --location --continue-at - -O\"\n    fi\n}\n\ncheck_archs() {\n    step \"Check Architecture...\"\n    ARCH=\"${ARCH:-universal}\"\n    if [ \"${ARCH}\" = \"universal\" ]; then\n        CMAKE_ARCHS=\"x86_64;arm64\"\n    elif [ \"${ARCH}\" != \"x86_64\" -a \"${ARCH}\" != \"arm64\" ]; then\n        caught_error \"Unsupported architecture '${ARCH}' provided\"\n    else\n        CMAKE_ARCHS=\"${ARCH}\"\n    fi\n}\n\n## SET UP CODE SIGNING AND NOTARIZATION CREDENTIALS ##\n##############################################################################\n# Apple Developer Identity needed:\n#\n#    + Signing the code requires a developer identity in the system's keychain\n#    + codesign will look up and find the identity automatically\n#\n##############################################################################\nread_codesign_ident() {\n    if [ ! -n \"${CODESIGN_IDENT}\" ]; then\n        step \"Code-signing Setup\"\n        read -p \"${COLOR_ORANGE}  + Apple developer application identity: ${COLOR_RESET}\" CODESIGN_IDENT\n    fi\n}\n\nread_codesign_ident_installer() {\n    if [ ! -n \"${CODESIGN_IDENT_INSTALLER}\" ]; then\n        step \"Code-signing Setup for Installer\"\n        read -p \"${COLOR_ORANGE}  + Apple developer installer identity: ${COLOR_RESET}\" CODESIGN_IDENT_INSTALLER\n    fi\n}\n\n##############################################################################\n# Apple Developer credentials necessary:\n#\n#   + Signing for distribution and notarization require an active Apple\n#     Developer membership\n#   + An Apple Development identity is needed for code signing\n#     (i.e. 'Apple Development: YOUR APPLE ID (PROVIDER)')\n#   + Your Apple developer ID is needed for notarization\n#   + An app-specific password is necessary for notarization from CLI\n#   + This password will be stored in your macOS keychain under the identifier\n#     'OBS-Codesign-Password'with access Apple's 'altool' only.\n##############################################################################\n\nread_codesign_pass() {\n    step \"Notarization Setup\"\n\n    if [ -z \"${CODESIGN_IDENT_USER}\" ]; then\n        read -p \"${COLOR_ORANGE}  + Apple account id: ${COLOR_RESET}\" CODESIGN_IDENT_USER\n    fi\n\n    if [ -z \"${CODESIGN_IDENT_PASS}\" ]; then\n        CODESIGN_IDENT_PASS=$(stty -echo; read -p \"${COLOR_ORANGE}  + Apple developer password: ${COLOR_RESET}\" secret; stty echo; echo $secret)\n        echo \"\"\n    fi\n\n    step \"Updating notarization keychain\"\n\n    echo -n \"${COLOR_ORANGE}\"\n    /usr/bin/xcrun altool --store-password-in-keychain-item \"OBS-Codesign-Password\" -u \"${CODESIGN_IDENT_USER}\" -p \"${CODESIGN_IDENT_PASS}\"\n    echo -n \"${COLOR_RESET}\"\n    CODESIGN_IDENT_SHORT=$(echo \"${CODESIGN_IDENT}\" | /usr/bin/sed -En \"s/.+\\((.+)\\)/\\1/p\")\n}\n"
  },
  {
    "path": "CI/include/build_support_windows.ps1",
    "content": "function Write-Status {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $output\n    )\n\n    if (!($Quiet.isPresent)) {\n        if (Test-Path env:CI) {\n            Write-Output \"`e[33;34m[${ProductName}] ${output}`e[33;0m\"\n        } else {\n            Write-Host -ForegroundColor blue \"[${ProductName}] ${output}\"\n        }\n    }\n}\n\nfunction Write-Info {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $output\n    )\n\n    if (!($Quiet.isPresent)) {\n        if (Test-Path env:CI) {\n            Write-Output \"`e[33;33m + ${output}`e[33;0m\"\n        } else {\n            Write-Host -ForegroundColor DarkYellow \" + ${output}\"\n        }\n    }\n}\n\nfunction Write-Step {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $output\n    )\n\n    if (!($Quiet.isPresent)) {\n        if (Test-Path env:CI) {\n            Write-Output \"`e[33;32m + ${output}`e[33;0m\"\n        } else {\n            Write-Host -ForegroundColor green \" + ${output}\"\n        }\n    }\n}\n\nfunction Write-Error {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $output\n    )\n\n    if (Test-Path env:CI) {\n        Write-Output \"e[33;31m + ${output}`e[33;0m\"\n    } else {\n        Write-Host -ForegroundColor red \" + ${output}\"\n    }\n}\n\nfunction Test-CommandExists {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $Command\n    )\n\n    $CommandExists = $false\n    $OldActionPref = $ErrorActionPreference\n    $ErrorActionPreference = \"stop\"\n\n    try {\n        if (Get-Command $Command) {\n            $CommandExists = $true\n        }\n    } Catch {\n        $CommandExists = $false\n    } Finally {\n        $ErrorActionPreference = $OldActionPref\n    }\n\n    return $CommandExists\n}\n\nfunction Ensure-Directory {\n    param(\n        [parameter(Mandatory=$true)]\n        [string] $Directory\n    )\n\n    if (!(Test-Path $Directory)) {\n        $null = New-Item -ItemType Directory -Force -Path $Directory\n    }\n\n    Set-Location -Path $Directory\n}\n\nfunction TestFileHash {\n    param (\n        [string]$Path,\n        [string]$Hash\n    )\n    $FileHash = Get-FileHash -Path $Path -Algorithm SHA256\n    if (-not ($FileHash.Hash -ieq $Hash)) {\n        Write-Error \"${Path} failed hash check.\"\n        return $false\n    }\n    return $true\n}\n\n$BuildDirectory = \"$(if (Test-Path Env:BuildDirectory) { $env:BuildDirectory } else { $BuildDirectory })\"\n$BuildConfiguration = \"$(if (Test-Path Env:BuildConfiguration) { $env:BuildConfiguration } else { $BuildConfiguration })\"\n$BuildArch = \"$(if (Test-Path Env:BuildArch) { $env:BuildArch } else { $BuildArch })\"\n$OBSBranch = \"$(if (Test-Path Env:OBSBranch) { $env:OBSBranch } else { $OBSBranch })\"\n#$WindowsDepsVersion = \"$(if (Test-Path Env:WindowsDepsVersion ) { $env:WindowsDepsVersion } else { \"2022-02-13\" })\"\n$WindowsDepsVersion = \"$(if (Test-Path Env:DEPS_VERSION_WIN ) { $env:DEPS_VERSION_WIN } else { \"2022-08-02\" })\"\n$CmakeSystemVersion = \"$(if (Test-Path Env:CMAKE_SYSTEM_VERSION) { $env:CMAKE_SYSTEM_VERSION } else { \"10.0.22000.0\" })\"\n#$OBSVersion = \"$(if ( Test-Path Env:OBSVersion ) { $env:ObsVersion } else { \"27.2.3\" })\"\n$OBSVersion = \"$(if ( Test-Path Env:OBS_VERSION ) { $env:OBS_VERSION } else { \"28.0.1\" })\"\n#$NSISVersion = \"$(if ( Test-Path Env:NSISVersion ) { $env:NSISVersion } else { \"3.08\" })\"\n$NSISVersion = \"$(if ( Test-Path Env:NSIS_VERSION_WIN ) { $env:NSIS_VERSION_WIN } else { \"3.08\" })\"\n$WindowsDepsX64Hash = \"$(if (Test-Path Env:DEPS_X64_HASH_WIN ) { $env:DEPS_X64_HASH_WIN } else { \"-\" })\"\n$WindowsDepsX86Hash = \"$(if (Test-Path Env:DEPS_X86_HASH_WIN ) { $env:DEPS_X86_HASH_WIN } else { \"-\" })\"\n$WindowsQtX64Hash = \"$(if (Test-Path Env:QT_X64_HASH_WIN ) { $env:QT_X64_HASH_WIN } else { \"-\" })\"\n$WindowsQtX86Hash = \"$(if (Test-Path Env:QT_X86_HASH_WIN ) { $env:QT_X86_HASH_WIN } else { \"-\" })\"\n\nif ($env:GITHUB_ACTIONS -eq \"true\")\n{\n    $env:CI = \"true\"\n}\n\nfunction Install-Windows-Dependencies {\n    Write-Status \"Checking Windows build dependencies\"\n\n    $ObsBuildDependencies = @(\n        @(\"7z\", \"7zip\"),\n        @(\"cmake\", \"cmake --install-arguments 'ADD_CMAKE_TO_PATH=System'\")\n    )\n\n    if(!(Test-CommandExists \"choco\")) {\n        Set-ExecutionPolicy AllSigned\n        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'))\n    }\n\n    Foreach($Dependency in $ObsBuildDependencies) {\n        if($Dependency -is [system.array]) {\n            $Command = $Dependency[0]\n            $ChocoName = $Dependency[1]\n        } else {\n            $Command = $Dependency\n            $ChocoName = $Dependency\n        }\n\n        if(!(Test-CommandExists \"${Command}\")) {\n            Invoke-Expression \"choco install -y ${ChocoName}\"\n        }\n    }\n\n    $env:Path = [System.Environment]::GetEnvironmentVariable(\"Path\", \"Machine\") + \";\" + [System.Environment]::GetEnvironmentVariable(\"Path\", \"User\")\n}\n\nfunction Find-Msvc-Vcvarsall-Path {\n    param(\n        [string]$Version = \"\"\n    )\n    $vswherePath = \"${env:ProgramFiles}\\Microsoft Visual Studio\\Installer\\vswhere.exe\"\n    if (-not (Test-Path $vswherePath)) { $vswherePath = \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\Installer\\vswhere.exe\" }\n    $argumentList = \"-products\", \"*\", \"-requires\", \"Microsoft.VisualStudio.Component.VC.Tools.x86.x64\", \"-property\", \"installationPath\", \"-latest\"\n    if ($Version -ne \"\") {\n        $argumentList += \"-version\", $Version\n    }\n    $output = (&$vswherePath $argumentList)\n    $VcvarsallPath = \"$output\\VC\\Auxiliary\\Build\\vcvarsall.bat\"\n    return $VcvarsallPath\n}\n\nfunction Set-Msvc-Environment-And-Run-Cmake {\n    param(\n        [string]$VsVersion = \"\",\n        [ValidateSet(\"x86\", \"amd64\", \"x86_amd64\", \"x86_arm\", \"x86_arm64\", \"amd64_x86\", \"amd64_arm\", \"amd64_arm64\")]\n        [String]$Arch,\n        [ValidateSet(\"\", \"store\", \"uwp\")]\n        [String]$PlatformType = \"\",\n        [string]$WinsdkVersion = \"\",\n        [string]$VcvarsVer = \"\",\n        [string]$VcvarsSpectreLibs = \"\",\n        [string[]]$CmakeArgumentList\n    )\n    $VcvarsallPath = Find-Msvc-Vcvarsall-Path($VsVersion)\n    $argumentList = \"/C\", \"call\", \"\"\"$VcvarSallPath\"\"\", $Arch, $PlatformType, $WinsdkVersion\n    if (-not $VcvarsVer -eq '') { $argumentList += \"-vcvars_ver=$VcvarsVer\" }\n    if (-not $VcvarsSpectreLibs -eq '') {  $argumentList += \"-vcvars_spectre_libs=$VcvarsSpectreLibs\" }\n    $argumentList += \"&&call\", \"cmake.exe\"\n    $argumentList += $CmakeArgumentList\n    &\"$env:windir\\System32\\cmd.exe\" $argumentList\n} "
  },
  {
    "path": "CI/linux/01_install_dependencies.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux dependency management function\n##############################################################################\n#\n# This script file can be included in build scripts for Linux or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\ninstall_obs-studio() {\n    if [ -n \"${OBS_BRANCH}\" ]; then\n        CHECKOUT_REF=\"${OBS_BRANCH}\"\n    else\n        #CHECKOUT_REF=\"tags/${OBS_VERSION:-${CI_OBS_VERSION}}\"\n        CHECKOUT_REF=\"tags/${OBS_VERSION}\"\n    fi\n\n    ensure_dir \"${OBS_BUILD_DIR}\"\n\n    if [ ! -d \"${OBS_BUILD_DIR}/.git\" ]; then\n        git clone --recursive https://github.com/obsproject/obs-studio \"$(pwd)\"\n        git fetch origin --tags\n        git checkout ${CHECKOUT_REF} -b obs-plugin-build\n    else\n        if ! git show-ref --verify --quiet refs/heads/obs-plugin-build; then\n            git checkout ${CHECKOUT_REF} -b obs-plugin-build\n        else\n            git checkout obs-plugin-build\n        fi\n    fi\n}\n\ninstall_build-deps() {\n    shift\n    status \"Install OBS build dependencies\"\n    trap \"caught_error 'install_build-deps'\" ERR\n\n    sudo apt-get install -y $@\n}\n\ninstall_obs-deps() {\n    shift\n    status \"Install OBS dependencies\"\n    trap \"caught_error 'install_obs-deps'\" ERR\n\n    #if [ -z \"${DISABLE_PIPEWIRE}\" ]; then\n    #    sudo apt-get install -y $@ libpipewire-0.3-dev\n    #else\n    sudo apt-get install -y $@\n    #fi\n}\n\ninstall_qt5-deps() {\n    shift\n    status \"Install Qt5 dependencies\"\n    trap \"caught_error 'install_qt5-deps'\" ERR\n\n    sudo apt-get install -y $@\n}\n\ninstall_qt6-deps() {\n    shift\n    status \"Install Qt6 dependencies\"\n    trap \"caught_error 'install_qt6-deps'\" ERR\n\n    _QT6_AVAILABLE=\"$(sudo apt-cache madison ${1})\"\n    if [ \"${_QT6_AVAILABLE}\" ]; then\n        sudo apt-get install -y $@\n    fi\n}\n\ninstall_dependencies() {\n    status \"Set up apt\"\n    trap \"caught_error 'install_dependencies'\" ERR\n\n    BUILD_DEPS=(\n        \"build-deps cmake ninja-build pkg-config clang clang-format build-essential curl ccache\"\n        \"obs-deps libavcodec-dev libavdevice-dev libavfilter-dev libavformat-dev libavutil-dev libswresample-dev \\\n         libswscale-dev libx264-dev libcurl4-openssl-dev libmbedtls-dev libgl1-mesa-dev libjansson-dev \\\n         libluajit-5.1-dev python3-dev libx11-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \\\n         libxcb-composite0-dev libxinerama-dev libxcb1-dev libx11-xcb-dev libxcb-xfixes0-dev swig libcmocka-dev \\\n         libpci-dev libxss-dev libglvnd-dev libgles2-mesa libgles2-mesa-dev libwayland-dev libxkbcommon-dev \\\n         libpulse-dev\"\n        \"qt5-deps qtbase5-dev qtbase5-private-dev libqt5svg5-dev qtwayland5\"\n        \"qt6-deps qt6-base-dev qt6-base-private-dev libqt6svg6-dev qt6-wayland\"\n    )\n\n    sudo dpkg --add-architecture amd64\n    sudo apt-get -qq update\n\n    for DEPENDENCY in \"${BUILD_DEPS[@]}\"; do\n        set -- ${DEPENDENCY}\n        trap \"caught_error ${DEPENDENCY}\" ERR\n        FUNC_NAME=\"install_${1}\"\n        ${FUNC_NAME} ${@}\n    done\n}\n\ninstall-dependencies-standalone() {\n    CHECKOUT_DIR=\"$(git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    DEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_linux.sh\"\n\n    status \"Setting up plugin build dependencies\"\n    install_dependencies\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\"\n}\n\ninstall-dependencies-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        install-dependencies-standalone\n    fi\n}\n\ninstall-dependencies-main $*\n"
  },
  {
    "path": "CI/linux/02_build_obs_libs.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux libobs library build function\n##############################################################################\n#\n# This script file can be included in build scripts for Linux or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\nbuild_obs_libs() {\n    status \"Build libobs and obs-frontend-api\"\n    trap \"caught_error 'build_obs_libs'\" ERR\n    check_ccache\n\n    ensure_dir \"${OBS_BUILD_DIR}\"\n\n    step \"Configuring OBS build system\"\n    check_ccache\n    cmake -S . -B plugin_${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \\\n        -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \\\n        -DENABLE_PLUGINS=OFF \\\n        -DENABLE_UI=ON \\\n        -DENABLE_SCRIPTING=OFF \\\n        -DENABLE_PIPEWIRE=OFF \\\n        -DBUILD_BROWSER=OFF \\\n        ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}\n\n    step \"Building libobs and obs-frontend-api\"\n    cmake --build plugin_${BUILD_DIR} -t obs-frontend-api\n}\n\nbuild-obs-libs-standalone() {\n    CHECKOUT_DIR=\"$(git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_linux.sh\"\n\n    build_obs_libs\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\nbuild-obs-libs-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        build-obs-libs-standalone\n    fi\n}\n\nbuild-obs-libs-main $*\n"
  },
  {
    "path": "CI/linux/03_build_plugin.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux libobs plugin build function\n##############################################################################\n#\n# This script file can be included in build scripts for Linux or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\nbuild_obs_plugin() {\n    status \"Build plugin ${PRODUCT_NAME}\"\n    trap \"caught_error 'builds_obs_plugin'\" ERR\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n\n    step \"Configuring OBS plugin build system\"\n    check_ccache\n\n    cmake -S . -B ${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \\\n        -DOBS_SOURCE_DIR=\"${OBS_BUILD_DIR}\" \\\n        ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}\n\n    step \"Building OBS plugin\"\n    cmake --build ${BUILD_DIR}\n}\n\nbuild-plugin-standalone() {\n    CHECKOUT_DIR=\"$(git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_linux.sh\"\n\n    build_obs_plugin\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\nbuild-plugin-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        build-plugin-standalone\n    fi\n}\n\nbuild-plugin-main $*\n"
  },
  {
    "path": "CI/linux/04_package_plugin.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# Linux libobs plugin build function\n##############################################################################\n#\n# This script file can be included in build scripts for Linux or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\npackage_obs_plugin() {\n    status \"Package OBS plugin ${PRODUCT_NAME}\"\n    trap \"caught_error 'package_obs_plugin'\" ERR\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n\n    step \"Package ${PRODUCT_NAME}...\"\n    \n    cmake --build \"${BUILD_DIR}\" -t package\n}\n\npackage-plugin-standalone() {\n    CHECKOUT_DIR=\"$(git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_linux.sh\"\n\n    #GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n    #GIT_HASH=$(git rev-parse --short HEAD)\n    #GIT_TAG=$(git describe --tags --always --dirty='-dev')\n    #GIT_VERSION=$(echo ${GIT_TAG} | grep -Eos '[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$')\n    #FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG}-linux\"\n\n    package_obs_plugin\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\npackage-plugin-main() {\n    if [ ! -n \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        package-plugin-standalone\n    fi\n}\n\npackage-plugin-main $*\n"
  },
  {
    "path": "CI/macos/01_install_dependencies.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS dependency management function\n##############################################################################\n#\n# This script file can be included in build scripts for macOS or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\ninstall_obs-deps() {\n    status \"Set up precompiled macOS OBS dependencies v${1}\"\n    ensure_dir \"${DEPS_BUILD_DIR}\"\n    step \"Download...\"\n    check_and_fetch \"https://github.com/obsproject/obs-deps/releases/download/${1}/macos-deps-${1}-universal.tar.xz\" \"${2}\"\n    mkdir -p obs-deps\n    step \"Unpack...\"\n    /usr/bin/tar -xf \"./macos-deps-${1}-universal.tar.xz\" -C ./obs-deps\n    /usr/bin/xattr -r -d com.apple.quarantine ./obs-deps\n}\n\ninstall_qt-deps() {\n    status \"Set up precompiled dependency Qt v${1}\"\n    ensure_dir \"${DEPS_BUILD_DIR}\"\n    step \"Download...\"\n    check_and_fetch \"https://github.com/obsproject/obs-deps/releases/download/${1}/macos-deps-qt6-${1}-universal.tar.xz\" \"${2}\"\n    mkdir -p obs-deps\n    step \"Unpack...\"\n    /usr/bin/tar -xf \"./macos-deps-qt6-${1}-universal.tar.xz\" -C ./obs-deps\n    /usr/bin/xattr -r -d com.apple.quarantine ./obs-deps\n}\n\ninstall_obs-studio() {\n    if [ \"${OBS_BRANCH}\" ]; then\n        CHECKOUT_REF=\"${OBS_BRANCH}\"\n    else\n        #CHECKOUT_REF=\"tags/${OBS_VERSION:-${CI_OBS_VERSION}}\"\n        CHECKOUT_REF=\"tags/${OBS_VERSION}\"\n    fi\n\n    ensure_dir \"${OBS_BUILD_DIR}\"\n\n    if [ ! -d \"${OBS_BUILD_DIR}/.git\" ]; then\n        /usr/bin/git clone --recursive https://github.com/obsproject/obs-studio \"$(pwd)\"\n        /usr/bin/git fetch origin --tags\n        /usr/bin/git checkout ${CHECKOUT_REF} -b obs-plugin-build\n    else\n        if ! /usr/bin/git show-ref --verify --quiet refs/heads/obs-plugin-build; then\n            /usr/bin/git checkout ${CHECKOUT_REF} -b obs-plugin-build\n        else\n            /usr/bin/git checkout obs-plugin-build\n        fi\n    fi\n}\n\ninstall_dependencies() {\n    status \"Installing Homebrew dependencies\"\n    trap \"caught_error 'install_dependencies'\" ERR\n\n    BUILD_DEPS=(\n        #\"obs-deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}} ${MACOS_DEPS_HASH:-${CI_DEPS_HASH}}\"\n        \"obs-deps ${MACOS_DEPS_VERSION} ${MACOS_DEPS_HASH}\"\n        #\"qt-deps ${MACOS_DEPS_VERSION:-${CI_DEPS_VERSION}} ${QT_HASH:-${CI_QT_HASH}}\"\n        \"qt-deps ${MACOS_DEPS_VERSION} ${QT_HASH}\"\n        #\"obs-studio ${OBS_VERSION:-${CI_OBS_VERSION}}\"\n        \"obs-studio ${OBS_VERSION}\"\n    )\n\n    install_homebrew_deps\n\n    for DEPENDENCY in \"${BUILD_DEPS[@]}\"; do\n        set -- ${DEPENDENCY}\n        trap \"caught_error ${DEPENDENCY}\" ERR\n        FUNC_NAME=\"install_${1}\"\n        ${FUNC_NAME} ${2} ${3}\n    done\n}\n\ninstall-dependencies-standalone() {\n    CHECKOUT_DIR=\"$(/usr/bin/git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    DEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_macos.sh\"\n\n    status \"Setting up plugin build dependencies\"\n    check_macos_version\n    check_archs\n    install_dependencies\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"-a, --architecture             : Specify build architecture (default: universal, alternative: x86_64, arm64)\\n\"\n}\n\ninstall-dependencies-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                -a | --architecture ) ARCH=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        install-dependencies-standalone\n    fi\n}\n\ninstall-dependencies-main $*\n"
  },
  {
    "path": "CI/macos/02_build_obs_libs.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS libobs library build function\n##############################################################################\n#\n# This script file can be included in build scripts for macOS or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\nbuild_obs_libs() {\n    status \"Build libobs and obs-frontend-api\"\n    trap \"caught_error 'build_obs_libs'\" ERR\n\n    ensure_dir \"${OBS_BUILD_DIR}\"\n\n    step \"Configuring OBS build system\"\n    check_ccache\n    #-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}} \\\n    cmake -S . -B plugin_${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \\\n        -DCMAKE_OSX_ARCHITECTURES=\"${CMAKE_ARCHS}\" \\\n        -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \\\n        -DOBS_CODESIGN_LINKER=${CODESIGN_LINKER:-OFF} \\\n        -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \\\n        -DENABLE_PLUGINS=OFF \\\n        -DENABLE_UI=ON \\\n        -DENABLE_SCRIPTING=OFF \\\n        -DENABLE_SPARKLE_UPDATER=OFF \\\n        -DSPARKLE=OFF \\\n        -DBUILD_BROWSER=OFF \\\n        -DCMAKE_PREFIX_PATH=\"${DEPS_BUILD_DIR}/obs-deps\" \\\n        ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}\n\n    step \"Building libobs and obs-frontend-api\"\n    cmake --build plugin_${BUILD_DIR} -t obs-frontend-api\n}\n\nbuild-obs-libs-standalone() {\n    CHECKOUT_DIR=\"$(/usr/bin/git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    DEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_macos.sh\"\n\n    check_macos_version\n    check_archs\n    build_obs_libs\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"-a, --architecture             : Specify build architecture (default: universal, alternative: x86_64, arm64)\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\nbuild-obs-libs-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                -a | --architecture ) ARCH=\"${2}\"; shift 2 ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        build-obs-libs-standalone\n    fi\n}\n\nbuild-obs-libs-main $*\n"
  },
  {
    "path": "CI/macos/03_build_plugin.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS libobs plugin build function\n##############################################################################\n#\n# This script file can be included in build scripts for macOS or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\nbuild_obs_plugin() {\n    status \"Build plugin ${PRODUCT_NAME}\"\n    trap \"caught_error 'builds_obs_plugin'\" ERR\n\n    if [ \"${CODESIGN}\" ]; then\n        read_codesign_ident\n    fi\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n    step \"Configuring OBS plugin build system\"\n    check_ccache\n\n    #-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET:-${CI_MACOSX_DEPLOYMENT_TARGET}} \\\n    cmake -S . -B ${BUILD_DIR} -G Ninja ${CMAKE_CCACHE_OPTIONS} \\\n        -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \\\n        -DCMAKE_OSX_ARCHITECTURES=\"${CMAKE_ARCHS}\" \\\n        -DOBS_CODESIGN_LINKER=${CODESIGN_LINKER:-OFF} \\\n        -DCMAKE_BUILD_TYPE=${BUILD_CONFIG} \\\n        -DOBS_BUNDLE_CODESIGN_IDENTITY=\"${CODESIGN_IDENT:--}\" \\\n        -DCMAKE_PREFIX_PATH=\"${DEPS_BUILD_DIR}/obs-deps\" \\\n        -DOBS_SOURCE_DIR=\"${OBS_BUILD_DIR}\" \\\n        ${QUIET:+-Wno-deprecated -Wno-dev --log-level=ERROR}\n\n    step \"Building OBS plugin\"\n    cmake --build ${BUILD_DIR}\n\n}\n\nbuild-plugin-standalone() {\n    CHECKOUT_DIR=\"$(/usr/bin/git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    DEPS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-build-dependencies\"\n    OBS_BUILD_DIR=\"${CHECKOUT_DIR}/../obs-studio\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_macos.sh\"\n\n    check_macos_version\n    check_archs\n\n    build_obs_plugin\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"-a, --architecture             : Specify build architecture (default: x86_64, alternative: arm64)\\n\" \\\n            \"-c, --codesign                 : Codesign OBS and all libraries (default: ad-hoc only)\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\nbuild-plugin-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                -c | --codesign ) CODESIGN=TRUE; shift ;;\n                -a | --architecture ) ARCH=\"${2}\"; shift 2 ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        build-plugin-standalone\n    fi\n}\n\nbuild-plugin-main $*\n"
  },
  {
    "path": "CI/macos/04_package_plugin.sh",
    "content": "#!/bin/bash\n\n##############################################################################\n# macOS libobs plugin package function\n##############################################################################\n#\n# This script file can be included in build scripts for macOS or run directly\n#\n##############################################################################\n\n# Halt on errors\nset -eE\n\npackage_obs_plugin() {\n    if [ \"${CODESIGN}\" ]; then\n        read_codesign_ident\n    fi\n\n    status \"Package OBS plugin ${PRODUCT_NAME}\"\n    trap \"caught_error 'package_obs_plugin'\" ERR\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n\n    # if [ -d \"${BUILD_DIR}/rundir/${PRODUCT_NAME}.plugin\" ]; then\n    #     rm -rf \"${BUILD_DIR}/rundir/${PRODUCT_NAME}.plugin\"\n    # fi\n    if [ -d \"${CHECKOUT_DIR}/release\" ]; then\n        rm -rf \"${CHECKOUT_DIR}/release\"\n    fi\n\n    cmake --install \"${BUILD_DIR}\" --prefix \"${CHECKOUT_DIR}/release\"\n\n    if ! type packagesbuild &>/dev/null; then\n        status \"Setting up dependency Packages.app\"\n        step \"Download...\"\n        check_and_fetch \"http://s.sudre.free.fr/Software/files/Packages.dmg\" \"9d9a73a64317ea6697a380014d2e5c8c8188b59d5fb8ce8872e56cec06cd78e8\"\n        step \"Mount disk image...\"\n        hdiutil attach -noverify Packages.dmg\n\n        step \"Install Packages.app\"\n        PACKAGES_VOLUME=$(hdiutil info -plist | grep \"/Volumes/Packages\" | sed 's/<string>\\/Volumes\\/\\([^<]*\\)<\\/string>/\\1/' | sed -e 's/^[[:space:]]*//')\n        sudo installer -pkg \"/Volumes/${PACKAGES_VOLUME}/Install Packages.pkg\" -target /\n        hdiutil detach \"/Volumes/${PACKAGES_VOLUME}\"\n    fi\n\n    step \"Package ${PRODUCT_NAME}...\"\n    cp \"${CHECKOUT_DIR}/LICENSE\" \"${CHECKOUT_DIR}/bundle/LICENSE.txt\"\n    packagesbuild ./bundle/installer-macos.generated.pkgproj\n\n    ensure_dir \"${CHECKOUT_DIR}/release\"\n\n    step \"Creating zip archive...\"\n    zip -r9 \"${CHECKOUT_DIR}/${BUILD_DIR}/${FILE_NAME}.zip\" .\n    ensure_dir \"${CHECKOUT_DIR}\"\n\n    if [ \"${CODESIGN}\" ]; then\n        step \"Codesigning installer package...\"\n        read_codesign_ident_installer\n\n        /usr/bin/productsign --sign \"${CODESIGN_IDENT_INSTALLER}\" \"${BUILD_DIR}/${PRODUCT_NAME}.pkg\" \"${BUILD_DIR}/${FILE_NAME}.pkg\"\n    else\n        mv \"${BUILD_DIR}/${PRODUCT_NAME}.pkg\" \"${BUILD_DIR}/${FILE_NAME}.pkg\"\n    fi\n}\n\nnotarize_obs_plugin() {\n    status \"Notarize ${PRODUCT_NAME}\"\n    trap \"caught_error 'notarize_obs_plugin'\" ERR\n\n    if ! exists brew; then\n        error \"Homebrew not found - please install homebrew (https://brew.sh)\"\n        exit 1\n    fi\n\n    if ! exists xcnotary; then\n        step \"Install notarization dependency 'xcnotary'\"\n        brew install akeru-inc/tap/xcnotary\n    fi\n\n    ensure_dir \"${CHECKOUT_DIR}\"\n\n    if [ -f \"${FILE_NAME}\" ]; then\n        xcnotary precheck \"${FILE_NAME}\"\n    else\n        error \"No notarization package installer ('${FILE_NAME}') found\"\n        return\n    fi\n\n    if [ \"$?\" -eq 0 ]; then\n        read_codesign_ident_installer\n        read_codesign_pass\n\n        step \"Run xcnotary with ${FILE_NAME}...\"\n        xcnotary notarize \"${FILE_NAME}\" --developer-account \"${CODESIGN_IDENT_USER}\" --developer-password-keychain-item \"OBS-Codesign-Password\" --provider \"${CODESIGN_IDENT_SHORT}\"\n    fi\n}\n\npackage-plugin-standalone() {\n    CHECKOUT_DIR=\"$(/usr/bin/git rev-parse --show-toplevel)\"\n    if [ -f \"${CHECKOUT_DIR}/CI/include/build_environment.sh\" ]; then\n        source \"${CHECKOUT_DIR}/CI/include/build_environment.sh\"\n    fi\n    PRODUCT_NAME=\"${PRODUCT_NAME:-obs-plugin}\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support.sh\"\n    source \"${CHECKOUT_DIR}/CI/include/build_support_macos.sh\"\n\n    GIT_BRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD)\n    GIT_HASH=$(/usr/bin/git rev-parse --short HEAD)\n    GIT_TAG=$(/usr/bin/git describe --tags --always --dirty='-dev')\n    GIT_VERSION=$(echo ${GIT_TAG} | grep -Eos '[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$')\n\n    check_macos_version\n    check_archs\n\n    FILE_NAME=\"${PRODUCT_NAME}-${GIT_TAG:-${PRODUCT_VERSION}}-macos-${ARCH}\"\n\n    check_curl\n    package_obs_plugin\n\n    if [ \"${NOTARIZE}\" ]; then\n        notarize_obs_plugin\n    fi\n}\n\nprint_usage() {\n    echo -e \"Usage: ${0}\\n\" \\\n            \"-h, --help                     : Print this help\\n\" \\\n            \"-q, --quiet                    : Suppress most build process output\\n\" \\\n            \"-v, --verbose                  : Enable more verbose build process output\\n\" \\\n            \"-a, --architecture             : Specify build architecture (default: x86_64, alternative: arm64)\\n\" \\\n            \"-c, --codesign                 : Codesign OBS and all libraries (default: ad-hoc only)\\n\" \\\n            \"-n, --notarize                 : Notarize OBS (default: off)\\n\" \\\n            \"--build-dir                    : Specify alternative build directory (default: build)\\n\"\n}\n\npackage-plugin-main() {\n    if [ -z \"${_RUN_OBS_BUILD_SCRIPT}\" ]; then\n        while true; do\n            case \"${1}\" in\n                -h | --help ) print_usage; exit 0 ;;\n                -q | --quiet ) export QUIET=TRUE; shift ;;\n                -v | --verbose ) export VERBOSE=TRUE; shift ;;\n                -a | --architecture ) ARCH=\"${2}\"; shift 2 ;;\n                -c | --codesign ) CODESIGN=TRUE; shift ;;\n                -n | --notarize ) NOTARIZE=TRUE; CODESIGN=TRUE; shift ;;\n                -s | --standalone ) STANDALONE=TRUE; shift ;;\n                --build-dir ) BUILD_DIR=\"${2}\"; shift 2 ;;\n                -- ) shift; break ;;\n                * ) break ;;\n            esac\n        done\n\n        package-plugin-standalone\n    fi\n}\n\npackage-plugin-main $*\n"
  },
  {
    "path": "CI/utility/check-format.sh",
    "content": "#!/bin/bash\ndirty=$(git ls-files --modified)\n\nset +x\nif [[ $dirty ]]; then\n\techo \"=================================\"\n    echo \"Files were not formatted properly\"\n    echo \"$dirty\"\n    echo \"=================================\"\n    exit 1\nfi\n"
  },
  {
    "path": "CI/utility/formatcode.sh",
    "content": "#!/usr/bin/env bash\n# Original source https://github.com/Project-OSRM/osrm-backend/blob/master/scripts/format.sh\n\nset -o errexit\nset -o pipefail\nset -o nounset\n\nif [ ${#} -eq 1 ]; then\n    VERBOSITY=\"--verbose\"\nelse\n    VERBOSITY=\"\"\nfi\n\n# Runs the Clang Formatter in parallel on the code base.\n# Return codes:\n#  - 1 there are files to be formatted\n#  - 0 everything looks fine\n\n# Get CPU count\nOS=$(uname)\nNPROC=1\nif [[ ${OS} = \"Linux\" ]] ; then\n    NPROC=$(nproc)\nelif [[ ${OS} = \"Darwin\" ]] ; then\n    NPROC=$(sysctl -n hw.physicalcpu)\nfi\n\n# Discover clang-format\nif type clang-format-12 2> /dev/null ; then\n    CLANG_FORMAT=clang-format-12\nelif type clang-format 2> /dev/null ; then\n    # Clang format found, but need to check version\n    CLANG_FORMAT=clang-format\n    V=$(clang-format --version)\n    if [[ $V != *\"version 12.0\"* ]]; then\n        echo \"clang-format is not 12.0 (returned ${V})\"\n        exit 1\n    fi\nelse\n    echo \"No appropriate clang-format found (expected clang-format-12.0.0, or clang-format)\"\n    exit 1\nfi\n\nfind . -type d \\( \\\n    -path ./\\*build -o \\\n    -path ./cmake -o \\\n    -path ./deps -o \\\n    -path ./plugins/decklink/\\*/decklink-sdk -o \\\n    -path ./plugins/enc-amf -o \\\n    -path ./plugins/mac-syphon/syphon-framework -o \\\n    -path ./plugins/obs-outputs/ftl-sdk -o \\\n    -path ./plugins/obs-vst \\\n\\) -prune -false -type f -o \\\n    -name '*.h' -or \\\n    -name '*.hpp' -or \\\n    -name '*.m' -or \\\n    -name '*.m,' -or \\\n    -name '*.c' -or \\\n    -name '*.cpp' \\\n | xargs -L100 -P ${NPROC} ${CLANG_FORMAT} ${VERBOSITY} -i -style=file -fallback-style=none\n"
  },
  {
    "path": "CI/windows/01_install_dependencies.ps1",
    "content": "Param(\n    [Switch]$Help = $(if (Test-Path variable:Help) { $Help }),\n    [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }),\n    [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }),\n    [Switch]$NoChoco = $(if (Test-Path variable:NoChoco) { $true } else { $false }),\n    [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" } else { (Get-CimInstance CIM_OperatingSystem).OSArchitecture }),\n    [String]$ProductName = $(if (Test-Path variable:ProductName) { \"${ProductName}\" } else { \"obs-plugin\" }),\n    [string[]]$InstallList = $(if (Test-Path variable:InstallList) { \"${InstallList}\" } else { $null })\n)\n\n##############################################################################\n# Windows dependency management function\n##############################################################################\n#\n# This script file can be included in build scripts for Windows or run\n# directly\n#\n##############################################################################\n\n$ErrorActionPreference = \"Stop\"\n\nFunction Install-obs-deps {\n    Param(\n        [Parameter(Mandatory=$true)]\n        [String]$Version\n    )\n\n    $Arch = \"x86\"\n\n    if ($BuildArch -eq \"64-bit\") {\n        $Arch = \"x64\"\n    }\n\n    Write-Status \"Setup for pre-built Windows OBS dependencies v${Version} ${Arch}\"\n    Ensure-Directory $DepsBuildDir\n\n    if (!(Test-Path $DepsBuildDir/windows-deps-${Version}-${Arch})) {\n        Write-Status \"Setting up pre-built Windows OBS dependencies v${Version} ${Arch}\"\n\n        Write-Step \"Download...\"\n        $ProgressPreference = $(if ($Quiet.isPresent) { \"SilentlyContinue\" } else { \"Continue\" })\n        Invoke-WebRequest -Uri \"https://github.com/obsproject/obs-deps/releases/download/${Version}/windows-deps-${Version}-${Arch}.zip\" -UseBasicParsing -OutFile \"windows-deps-${Version}-${Arch}.zip\"\n        $ProgressPreference = 'Continue'\n\n        Write-Step \"Unpack...\"\n\n        Expand-Archive -Path \"windows-deps-${Version}-${Arch}.zip\"\n    } else {\n        Write-Step \"Found existing pre-built dependencies...\"\n\n    }\n}\n\nfunction Install-qt-deps {\n    Param(\n        [Parameter(Mandatory=$true)]\n        [String]$Version\n    )\n\n    $Arch = \"x86\"\n\n    if ($BuildArch -eq \"64-bit\") {\n        $Arch = \"x64\"\n    }\n\n    Write-Status \"Setup for pre-built dependency Qt v${Version} ${Arch}\"\n    Ensure-Directory $DepsBuildDir\n\n    if (!(Test-Path $DepsBuildDir/windows-deps-qt6-${Version}-${Arch})) {\n        Write-Status \"Setting up OBS dependency Qt v${Version} ${Arch}\"\n\n        Write-Step \"Download...\"\n        $ProgressPreference = $(if ($Quiet.isPresent) { 'SilentlyContinue' } else { 'Continue' })\n        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\"\n        $ProgressPreference = 'Continue'\n        \n        Write-Step \"Unpack...\"\n\n        Expand-Archive -Path \"windows-deps-qt6-${Version}-${Arch}.zip\"\n    } else {\n        Write-Step \"Found existing pre-built Qt...\"\n    }\n}\n\nfunction Install-obs-studio {\n    Param(\n        [parameter(Mandatory=$true)]\n        [string]$Version\n    )\n\n    $CheckoutRef = \"$(if (!(Test-Path variable:OBSBranch)) { ${OBSBranch} } else { \"tags/${OBSVersion}\" })\"\n\n    Write-Status \"Setup for OBS Studio v${OBSVersion}\"\n    Ensure-Directory ${ObsBuildDir}\n\n    if (!(Test-Path \"${ObsBuildDir}/.git\")) {\n        & git clone --recursive https://github.com/obsproject/obs-studio \"${pwd}\"\n        & git fetch origin --tags\n        & git checkout ${CheckoutRef} -b obs-plugin-build\n    } else {\n        & git show-ref --verify --quiet refs/heads/obs-plugin-build\n        $BranchExists = $?\n\n        if ($BranchExists -Eq $false) {\n            & git checkout ${CheckoutRef} -b obs-plugin-build\n        } else {\n            & git checkout obs-plugin-build\n        }\n    }\n}\n\nfunction Install-nsis {\n    Param(\n        [parameter(Mandatory=$true)]\n        [string]$Version\n    )\n\n    Write-Status \"Setup for NSIS v${Version}\"\n    Ensure-Directory $DepsBuildDir\n\n    if (!(Test-Path $DepsBuildDir/nsis-${Version})) {\n        Write-Status \"Setting up NSIS v${Version}\"\n\n        Write-Step \"Download...\"\n        $ProgressPreference = $(if ($Quiet.isPresent) { 'SilentlyContinue' } else { 'Continue' })\n        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\"\n        $ProgressPreference = 'Continue'\n        \n        Write-Step \"Unpack...\"\n\n        Expand-Archive -Path \"nsis-${Version}.zip\" -DestinationPath \"./\"\n    } else {\n        Write-Step \"Found existing NSIS...\"\n    }\n}\n\nfunction Install-Dependencies {\n    if(!($NoChoco.isPresent)) {\n        Install-Windows-Dependencies\n    }\n\n    $BuildDependencies = @(\n        @('obs-deps', $WindowsDepsVersion),\n        @('qt-deps', $WindowsDepsVersion),\n        @('obs-studio', $OBSVersion),\n        @('nsis', $NSISVersion)\n    )\n\n    Foreach($Dependency in ${BuildDependencies}) {\n        $DependencyName = $Dependency[0]\n        $DependencyVersion = $Dependency[1]\n\n        if ($null -ne $InstallList -and $InstallList -notcontains $DependencyName) {\n            continue\n        }\n\n        $FunctionName = \"Install-${DependencyName}\"\n        & $FunctionName -Version $DependencyVersion\n    }\n\n    Ensure-Directory ${CheckoutDir}\n}\n\nfunction Install-Dependencies-Standalone {\n    $CheckoutDir = git rev-parse --show-toplevel\n\n    if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) {\n        . ${CheckoutDir}/CI/include/build_environment.ps1\n    }\n\n    $DepsBuildDir = \"${CheckoutDir}/../obs-build-dependencies\"\n    $ObsBuildDir = \"${CheckoutDir}/../obs-studio\"\n\n    . ${CheckoutDir}/CI/include/build_support_windows.ps1\n\n    Write-Status \"Setting up plugin build dependencies\"\n    Install-Dependencies\n}\n\nfunction Print-Usage {\n    $Lines = @(\n        \"Usage: ${MyInvocation.MyCommand.Name}\",\n        \"-Help                    : Print this help\",\n        \"-Quiet                   : Suppress most build process output\",\n        \"-Verbose                 : Enable more verbose build process output\",\n        \"-NoChoco                 : Skip automatic dependency installation via Chocolatey - Default: on\"\n        \"-BuildArch               : Build architecture to use (32bit or 64bit) - Default: local architecture\"\n    )\n\n    $Lines | Write-Host\n}\n\nif(!(Test-Path variable:_RunObsBuildScript)) {\n    if ($Help.isPresent) {\n        Print-Usage\n        exit 0\n    }\n\n    Install-Dependencies-Standalone\n}\n"
  },
  {
    "path": "CI/windows/02_build_obs_libs.ps1",
    "content": "Param(\n    [Switch]$Help = $(if (Test-Path variable:Help) { $Help }),\n    [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }),\n    [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }),\n    [String]$ProductName = $(if (Test-Path variable:ProductName) { \"${ProductName}\" } else { \"obs-plugin\" }),\n    [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" } else { \"build\" }),\n    [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}),\n    [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" } else { \"RelWithDebInfo\" })\n)\n\n##############################################################################\n# Windows libobs library build function\n##############################################################################\n#\n# This script file can be included in build scripts for Windows or run\n# directly\n#\n##############################################################################\n\n$ErrorActionPreference = \"Stop\"\n\nfunction Build-OBS-Libs {\n    Param(\n        [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" }),\n        [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" }),\n        [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" })\n    )\n\n    Write-Status \"Build libobs and obs-frontend-api\"\n\n    Ensure-Directory ${ObsBuildDir}\n\n    if ($BuildArch -eq \"64-bit\") {\n        $QtDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x64\"\n        $DepsDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x64\"\n        \n        Set-Msvc-Environment-And-Run-Cmake -Arch \"amd64\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"-S\", \".\", \"-B\", \"\"\"plugin_${BuildDirectory}64\"\"\", \"-G\", \"Ninja\",\n            \"-DCMAKE_SYSTEM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DQTDIR=\"\"${QtDirectory}\"\"\",\n            \"-DDepsPath=\"\"${DepsDirectory}\"\"\",\n            \"-DCMAKE_C_COMPILER=cl.exe\",\n            \"-DCMAKE_CXX_COMPILER=cl.exe\",\n            \"-DENABLE_PLUGINS=OFF\",\n            \"-DENABLE_UI=ON\",\n            \"-DENABLE_SCRIPTING=OFF\",\n            \"-DBUILD_BROWSER=OFF\",\n            \"-DCMAKE_BUILD_TYPE=Release\",\n            \"$(if (Test-Path Variable:$Quiet) { \"-Wno-deprecated -Wno-dev --log-level=ERROR\" })\")\n\n        Set-Msvc-Environment-And-Run-Cmake -Arch \"amd64\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"--build\", \"\"\"plugin_${BuildDirectory}64\"\"\", \"-t\", \"obs-frontend-api\", \"--config ${BuildConfiguration}\")\n    } else {\n        $QtDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x86\"\n        $DepsDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x86\"\n\n        Set-Msvc-Environment-And-Run-Cmake -Arch \"x86\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"-S\", \".\", \"-B\", \"\"\"plugin_${BuildDirectory}32\"\"\", \"-G\", \"Ninja\",\n            \"-DCMAKE_SYSTEM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DCMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DQTDIR=\"\"${QtDirectory}\"\"\",\n            \"-DDepsPath=\"\"${DepsDirectory}\"\"\",\n            \"-DCMAKE_C_COMPILER=cl.exe\",\n            \"-DCMAKE_CXX_COMPILER=cl.exe\",\n            \"-DENABLE_PLUGINS=OFF\",\n            \"-DENABLE_UI=ON\",\n            \"-DENABLE_SCRIPTING=OFF\",\n            \"-DBUILD_BROWSER=OFF\",\n            \"-DCMAKE_BUILD_TYPE=Release\",\n            \"$(if (Test-Path Variable:$Quiet) { \"-Wno-deprecated -Wno-dev --log-level=ERROR\" })\")\n\n            Set-Msvc-Environment-And-Run-Cmake -Arch \"x86\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n                \"--build\", \"\"\"plugin_${BuildDirectory}32\"\"\", \"-t\", \"obs-frontend-api\", \"--config ${BuildConfiguration}\")\n    }\n\n    Ensure-Directory ${CheckoutDir}\n}\n\n\nfunction Build-OBS-Libs-Standalone {\n    $CheckoutDir = git rev-parse --show-toplevel\n\n    if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) {\n        . ${CheckoutDir}/CI/include/build_environment.ps1\n    }\n\n    $ObsBuildDir = \"${CheckoutDir}/../obs-studio\"\n\n    . ${CheckoutDir}/CI/include/build_support_windows.ps1\n\n    Build-OBS-Libs\n}\n\nfunction Print-Usage {\n    $Lines = @(\n        \"Usage: ${MyInvocation.MyCommand.Name}\",\n        \"-Help                    : Print this help\",\n        \"-Quiet                   : Suppress most build process output\",\n        \"-Verbose                 : Enable more verbose build process output\",\n        \"-BuildDirectory          : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems\",\n        \"-BuildArch               : Build architecture to use (32bit or 64bit) - Default: local architecture\",\n        \"-BuildConfiguration      : Build configuration to use - Default: RelWithDebInfo\"\n    )\n\n    $Lines | Write-Host\n}\n\nif(!(Test-Path variable:_RunObsBuildScript)) {\n    if($Help.isPresent) {\n        Print-Usage\n        exit 0\n    }\n\n    Build-OBS-Libs-Standalone\n}\n"
  },
  {
    "path": "CI/windows/03_build_plugin.ps1",
    "content": "Param(\n    [Switch]$Help = $(if (Test-Path variable:Help) { $Help }),\n    [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }),\n    [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }),\n    [String]$ProductName = $(if (Test-Path variable:ProductName) { \"${ProductName}\" } else { \"obs-plugin\" }),\n    [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" } else { \"build\" }),\n    [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}),\n    [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" } else { \"RelWithDebInfo\" })\n)\n\n##############################################################################\n# Windows libobs plugin build function\n##############################################################################\n#\n# This script file can be included in build scripts for Windows or run\n# directly\n#\n##############################################################################\n\n$ErrorActionPreference = \"Stop\"\n\nfunction Build-OBS-Plugin {\n    Param(\n        [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" }),\n        [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" }),\n        [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" })\n    )\n\n    Write-Status \"Build plugin ${ProductName}\"\n    Ensure-Directory ${CheckoutDir}\n\n    if ($BuildArch -eq \"64-bit\") {\n        $QtDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x64\"\n        $DepsDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x64\"\n        \n        Set-Msvc-Environment-And-Run-Cmake -Arch \"amd64\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"-S\", \".\", \"-B\", \"\"\"${BuildDirectory}64\"\"\", \"-G\", \"Ninja\",\n            \"-DCMAKE_SYSTEM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DQTDIR=\"\"${QtDirectory}\"\"\",\n            \"-DDepsPath=\"\"${DepsDirectory}\"\"\",\n            \"-DCMAKE_C_COMPILER=cl.exe\",\n            \"-DCMAKE_CXX_COMPILER=cl.exe\",\n            \"-DOBS_SOURCE_DIR=\"\"${ObsBuildDir}\"\"\",\n            \"-DCMAKE_BUILD_TYPE=\"\"${BuildConfiguration}\"\"\",\n            \"$(if (Test-Path Variable:$Quiet) { \"-Wno-deprecated -Wno-dev --log-level=ERROR\" })\")\n\n        Set-Msvc-Environment-And-Run-Cmake -Arch \"amd64\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"--build\", \"\"\"${BuildDirectory}64\"\"\", \"--config\", \"${BuildConfiguration}\")\n    } else {\n        $QtDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-qt6-${WindowsDepsVersion}-x86\"\n        $DepsDirectory = \"${CheckoutDir}/../obs-build-dependencies/windows-deps-${WindowsDepsVersion}-x86\"\n\n        Set-Msvc-Environment-And-Run-Cmake -Arch \"x86\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"-S\", \".\", \"-B\", \"\"\"${BuildDirectory}32\"\"\", \"-G\", \"Ninja\",\n            \"-DCMAKE_SYSTEM_VERSION=\"\"${CmakeSystemVersion}\"\"\",\n            \"-DQTDIR=\"\"${QtDirectory}\"\"\",\n            \"-DDepsPath=\"\"${DepsDirectory}\"\"\",\n            \"-DCMAKE_C_COMPILER=cl.exe\",\n            \"-DCMAKE_CXX_COMPILER=cl.exe\",\n            \"-DOBS_SOURCE_DIR=\"\"${ObsBuildDir}\"\"\",\n            \"-DCMAKE_BUILD_TYPE=\"\"${BuildConfiguration}\"\"\",\n            \"$(if (Test-Path Variable:$Quiet) { \"-Wno-deprecated -Wno-dev --log-level=ERROR\" })\")\n\n        Set-Msvc-Environment-And-Run-Cmake -Arch \"x86\" -WinsdkVersion \"${CmakeSystemVersion}\" -CmakeArgumentList @(\n            \"--build\", \"\"\"${BuildDirectory}32\"\"\", \"--config\", \"${BuildConfiguration}\")\n    }\n\n    Ensure-Directory ${CheckoutDir}\n}\n\nfunction Build-Plugin-Standalone {\n    $CheckoutDir = git rev-parse --show-toplevel\n\n    if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) {\n        . ${CheckoutDir}/CI/include/build_environment.ps1\n    }\n\n    $ObsBuildDir = \"${CheckoutDir}/../obs-studio\"\n\n    . ${CheckoutDir}/CI/include/build_support_windows.ps1\n\n    Build-OBS-Plugin\n}\n\nfunction Print-Usage {\n    $Lines = @(\n        \"Usage: ${MyInvocation.MyCommand.Name}\",\n        \"-Help                    : Print this help\",\n        \"-Quiet                   : Suppress most build process output\",\n        \"-Verbose                 : Enable more verbose build process output\",\n        \"-BuildDirectory          : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems\",\n        \"-BuildArch               : Build architecture to use (32bit or 64bit) - Default: local architecture\",\n        \"-BuildConfiguration      : Build configuration to use - Default: RelWithDebInfo\"\n    )\n\n    $Lines | Write-Host\n}\n\nif(!(Test-Path variable:_RunObsBuildScript)) {\n    if($Help.isPresent) {\n        Print-Usage\n        exit 0\n    }\n\n    Build-Plugin-Standalone\n }\n"
  },
  {
    "path": "CI/windows/04_package_plugin.ps1",
    "content": "Param(\n    [Switch]$Help = $(if (Test-Path variable:Help) { $Help }),\n    [Switch]$Quiet = $(if (Test-Path variable:Quiet) { $Quiet }),\n    [Switch]$Verbose = $(if (Test-Path variable:Verbose) { $Verbose }),\n    [Switch]$BuildInstaller = $(if ($BuildInstaller.isPresent) { $true }),\n    [String]$ProductName = $(if (Test-Path variable:ProductName) { \"${ProductName}\" } else { \"obs-plugin\" }),\n    [Switch]$CombinedArchs = $(if ($CombinedArchs.isPresent) { $true }),\n    [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" } else { \"build\" }),\n    [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" } else { (Get-WmiObject Win32_OperatingSystem).OSArchitecture}),\n    [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" } else { \"RelWithDebInfo\" })\n)\n\n##############################################################################\n# Windows libobs plugin package function\n##############################################################################\n#\n# This script file can be included in build scripts for Windows or run\n# directly with the -Standalone switch\n#\n##############################################################################\n\n$ErrorActionPreference = \"Stop\"\n\nfunction Package-OBS-Plugin {\n    Param(\n        [String]$BuildDirectory = $(if (Test-Path variable:BuildDirectory) { \"${BuildDirectory}\" }),\n        [String]$BuildArch = $(if (Test-Path variable:BuildArch) { \"${BuildArch}\" }),\n        [String]$BuildConfiguration = $(if (Test-Path variable:BuildConfiguration) { \"${BuildConfiguration}\" })\n    )\n\n    Write-Status \"Package plugin ${ProductName}\"\n    Ensure-Directory ${CheckoutDir}\n\n    $makensis_path = \"${CheckoutDir}/../obs-build-dependencies/nsis-$NSISVersion/makensis.exe\"\n\n    if ($CombinedArchs.isPresent) {\n        if (!(Test-Path ${CheckoutDir}/release/obs-plugins/64bit)) {\n            cmake --build ${BuildDirectory}64 --config ${BuildConfiguration} -t install\n        }\n\n        if (!(Test-Path ${CheckoutDir}/release/obs-plugins/32bit)) {\n            cmake --build ${BuildDirectory}32 --config ${BuildConfiguration} -t install\n        }\n\n        $CompressVars = @{\n            Path = \"${CheckoutDir}/release/*\"\n            CompressionLevel = \"Optimal\"\n            DestinationPath = \"${FileName}-windows-all.zip\"\n        }\n\n        Write-Step \"Creating zip archive...\"\n\n        Compress-Archive -Force @CompressVars\n        if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) {\n            Write-Step \"Creating installer...\"\n            Set-Location \"${CheckoutDir}/installer\"\n            Copy-Item \"${CheckoutDir}/LICENSE\" \"${CheckoutDir}/installer/LICENSE\"\n            & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=all .\\installer.nsi\n            Ensure-Directory ${CheckoutDir}\n        }\n    } elseif ($BuildArch -eq \"64-bit\") {\n        cmake --build ${BuildDirectory}64 --config ${BuildConfiguration} -t install\n\n        $CompressVars = @{\n            Path = \"${CheckoutDir}/release/*\"\n            CompressionLevel = \"Optimal\"\n            DestinationPath = \"${FileName}-windows-x64.zip\"\n        }\n\n        Write-Step \"Creating zip archive...\"\n\n        Compress-Archive -Force @CompressVars\n        if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) {\n            Write-Step \"Creating installer...\"\n            Set-Location \"${CheckoutDir}/installer\"\n            Copy-Item \"${CheckoutDir}/LICENSE\" \"${CheckoutDir}/installer/LICENSE\"\n            & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=x64 .\\installer.nsi\n            Ensure-Directory ${CheckoutDir}\n        }\n    } elseif ($BuildArch -eq \"32-bit\") {\n        cmake --build ${BuildDirectory}32 --config ${BuildConfiguration} -t install\n\n        $CompressVars = @{\n            Path = \"${CheckoutDir}/release/*\"\n            CompressionLevel = \"Optimal\"\n            DestinationPath = \"${FileName}-windows-x86.zip\"\n        }\n\n        Write-Step \"Creating zip archive...\"\n\n        Compress-Archive -Force @CompressVars\n        if(($BuildInstaller.isPresent) -And (Test-Path $makensis_path)) {\n            Write-Step \"Creating installer...\"\n            Set-Location \"${CheckoutDir}/installer\"\n            Copy-Item \"${CheckoutDir}/LICENSE\" \"${CheckoutDir}/installer/LICENSE\"\n            & $makensis_path /V4 /DVERSION=${ProductVersion} /DFVERSION=${ProductFileVersion} /DPLANTFORM=x86 .\\installer.nsi\n            Ensure-Directory ${CheckoutDir}\n        }\n    }\n}\n\nfunction Package-Plugin-Standalone {\n    $CheckoutDir = git rev-parse --show-toplevel\n\n    if (Test-Path ${CheckoutDir}/CI/include/build_environment.ps1) {\n        . ${CheckoutDir}/CI/include/build_environment.ps1\n    }\n\n    . ${CheckoutDir}/CI/include/build_support_windows.ps1\n\n    Ensure-Directory ${CheckoutDir}\n    $GitBranch = git rev-parse --abbrev-ref HEAD\n    $GitHash = git rev-parse --short HEAD\n    $ErrorActionPreference = \"SilentlyContiue\"\n    $GitTag = git describe --tags --always --dirty='-dev'\n    $ErrorActionPreference = \"Stop\"\n\n    if ($null -eq $GitTag) {\n        $GitTag=\"v$ProductVersion\"\n    } elseif ($GitTag -match \"[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$\") {\n        $ProductVersion = $Matches[0]\n    }\n\n    if ($ProductVersion -match \"^[0-9]+.[0-9]+.[0-9]+\")\n    {\n        $ProductFileVersion = $Matches[0] + \".0\"\n    }\n    $FileName = \"${ProductName}-v${ProductVersion}\"\n\n    Package-OBS-Plugin\n}\n\nfunction Print-Usage {\n    $Lines = @(\n        \"Usage: ${MyInvocation.MyCommand.Name}\",\n        \"-Help                    : Print this help\",\n        \"-Quiet                   : Suppress most build process output\",\n        \"-Verbose                 : Enable more verbose build process output\",\n        \"-CombinedArchs           : Create combined architecture package\",\n        \"-BuildDirectory          : Directory to use for builds - Default: build64 on 64-bit systems, build32 on 32-bit systems\",\n        \"-BuildArch               : Build architecture to use (32bit or 64bit) - Default: local architecture\",\n        \"-BuildConfiguration      : Build configuration to use - Default: RelWithDebInfo\"\n    )\n\n    $Lines | Write-Host\n}\n\n\nif(!(Test-Path variable:_RunObsBuildScript)) {\n    if($Help.isPresent) {\n        Print-Usage\n        exit 0\n    }\n\n    Package-Plugin-Standalone\n}\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5)\n\ninclude(external/GitInfoHelper.cmake)\nget_git_version(OBS_PLUGUN_GIT_TAG OBS_PLUGUIN_VERSION OBS_PLUGUIN_SHORT_VERSION OBS_PLUGUIN_LONG_VERSION)\nproject(obs-rtspserver VERSION ${OBS_PLUGUIN_LONG_VERSION} \n\tHOMEPAGE_URL \"https://obsproject.com/forum/resources/obs-rtspserver.1037\"\n\tDESCRIPTION \"RTSP server plugin for obs-studio\")\nset(LINUX_MAINTAINER_EMAIL \"scottxu@scottxublog.com\")\nset(LINUX_MAINTAINER \"Scott Xu\")\nset(MACOS_BUNDLEID \"com.scottxu.obs-rtspserver\")\n\nset(OBS_PLUGIN_OBS_SOURCE_DIR ${CMAKE_SOURCE_DIR})\nset(OBS_FRONTEND_API_INCLUDE_DIRS \"${CMAKE_SOURCE_DIR}/UI/obs-frontend-api\")\n\nadd_library(${CMAKE_PROJECT_NAME} MODULE)\n\nif (NOT COMMAND setup_plugin_target)\n    include(external/BuildHelper.cmake)\nendif()\n\nadd_subdirectory(rtsp-server)\n\nif(CMAKE_VERSION VERSION_LESS \"3.7.0\")\n    set(CMAKE_INCLUDE_CURRENT_DIR ON)\nendif()\n#set(CMAKE_AUTOMOC ON)\n#set(CMAKE_AUTOUIC ON)\n\nset(OBS_RTSPSERVER_SOURCES\n\trtsp_main.cpp\n\trtsp_output.cpp\n\trtsp_properties.cpp\n\trtsp_output_helper.cpp\n\t)\n\nset(OBS_RTSPSERVER_HEADERS\n\trtsp_output.h\n\trtsp_properties.h\n\trtsp_output_helper.h\n\t)\n\n file(GLOB OBS_RTSPSERVER_UI_SOURCES\n\tui/*.cpp)\n\nfile(GLOB OBS_RTSPSERVER_MAIN_SOURCES\n\t*.cpp)\n\nset(OBS_RTSPSERVER_SOURCES\n\t${OBS_RTSPSERVER_UI_SOURCES}\n\t${OBS_RTSPSERVER_MAIN_SOURCES}\n\t)\n\nfile(GLOB OBS_RTSPSERVER_UI_HEADERS\n\tui/*.hpp)\n\nfile(GLOB OBS_RTSPSERVER_MAIN_HEADERS\n\t*.h)\n\nset(OBS_RTSPSERVER_HEADERS\n\t${OBS_RTSPSERVER_UI_HEADERS}\n\t${OBS_RTSPSERVER_MAIN_HEADERS}\n\t)\n\nset_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY CXX_STANDARD 17)\nset_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY C_STANDARD 17)\n\nfind_qt(COMPONENTS Widgets Core)\n\nset_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON)\n\ntarget_sources(${CMAKE_PROJECT_NAME} PRIVATE\n\t${OBS_RTSPSERVER_SOURCES}\n\t${OBS_RTSPSERVER_HEADERS})\n\nif(WIN32)\n\tconfigure_file(${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc.in ${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc)\n\ttarget_sources(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rtspoutput.rc)\nendif()\n\t\ntarget_include_directories(${CMAKE_PROJECT_NAME} PRIVATE\n\t${OBS_FRONTEND_API_INCLUDE_DIRS}\n\t${LIBOBS_INCLUDE_DIRS}\n\t\"rtsp-server\")\n\ntarget_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE\n\trtsp-server\n\tobs-frontend-api\n\tlibobs\n\tQt::Core\n\tQt::Widgets)\n\nadd_definitions(-DVERSION_STRING=\"${OBS_PLUGUIN_VERSION}\")\n\nif(APPLE)\n\tset_target_properties(${CMAKE_PROJECT_NAME}\n\t\t\tPROPERTIES\n\t\t\tFOLDER \"plugins\"\n\t\t\tPRODUCTNAME \"OBS RTSP Server Plugin\")\nelse()\n\tset_target_properties(${CMAKE_PROJECT_NAME}\n\t\t\tPROPERTIES\n\t\t\tFOLDER \"plugins\"\n\t\t\tVERSION \"${OBS_PLUGUIN_VERSION}\"\n\t\t\tPRODUCTNAME \"OBS RTSP Server Plugin\")\nendif()\n\nsetup_plugin_target(${CMAKE_PROJECT_NAME})\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "README.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nThis is a plugin for obs-studio, encode and publish to a RTSP stream.\r\n\r\n**Supported Platforms** : Windows 10, Windows 11, Linux and macOS\r\n\r\n**Supported OBS Studio version** : 30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Install\r\n## Windows\r\nThe installer can be found in [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases).\r\n\r\nIf 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.\r\n\r\n### winget Package\r\nIf 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:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nYou 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.\r\n\r\n## Linux (Only x64)\r\n### Ubuntu/Debian DEB Package\r\nDownload the deb package from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and install it.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Replace {version} with last release version, e.g.: v2.2.0\r\n\r\n### Red-Hat RPM Package\r\nDownload the rpm package from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and install it.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Replace {version} with last release version, e.g.: v2.2.0\r\n\r\n### ArchLinux AUR Package\r\nobs-rtspserver is also available as an [AUR Package](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)\r\nIf you use [yay](https://github.com/Jguer/yay) just run this to install it:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Other\r\nDownload the tar.gz archive from the [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) and unpack to \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Replace {version} with last release version, e.g.: v2.2.0\r\n\r\n\r\n# Build\r\n* Install cmake, visual studio(only windows) and qt.\r\n* Download and configure the source code of obs-studio.\r\n* Copy source code to (obs-studio source code)/plugins/obs-rtspserver/\r\n* Add `add_subdirectory(obs-rtspserver)` to (obs-studio source code)/plugins/CMakeLists.txt\r\n* Build obs-rtspserver.\r\n\r\n# FAQ\r\n* [Can't find the plugin in the menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# License\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_de-DE.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Helfen Sie bei der Übersetzung von obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nDies ist ein Plugin für obs-studio, das die Ausgabe codiert und ein RTSP-Stream veröffentlicht.\r\n\r\n**Unterstützte Betriebssysteme** : Windows 10, Windows 11, Linux und macOS\r\n\r\n**Unterstützte OBS Studio Version**: 30.0.0+\r\n\r\n[![Paketstatus](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Installation\r\n## Windows\r\nDer Installer kann auf der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) gefunden werden.\r\n\r\nWenn 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.\r\n\r\n### winget Paket\r\nWenn 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:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nSie 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.\r\n\r\n## Linux (Nur x64)\r\n### Ubuntu/Debian DEB-Paket\r\nLaden Sie das deb-Paket von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und installieren Sie es.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0\r\n\r\n### Red-Hat RPM-Paket\r\nLaden Sie das RPM-Paket von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und installieren Sie es.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0\r\n\r\n### ArchLinux AUR-Paket\r\nobs-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:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Andere\r\nLaden Sie das tar.gz-Archiv von der [Release-Seite](https://github.com/iamscottxu/obs-rtspserver/releases) herunter und entpacken Sie es in \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Ersetzen Sie {version} durch die letzte Veröffentlichungsversion, z.B.: v2.2.0\r\n\r\n\r\n# Bauen\r\n* Install cmake, visual studio(only windows) and qt.\r\n* Laden Sie den Quellcode von obs-studio herunter und konfigurieren Sie ihn.\r\n* Kopieren Sie den Quellcode in (obs-studio Quellcode)/plugins/obs-rtspserver/\r\n* Fügen Sie `add_subdirectory(obs-rtspserver)` zu (obs-studio Quellcode)/plugins/CMakeLists.txt hinzu.\r\n* Build obs-rtspserver.\r\n\r\n# FAQ\r\n* [Kann das Plugin im Menü nicht finden](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Lizenz\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_es-ES.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Ayuda a traducir obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nEste es un complemento para obs-studio que codificará la salida y publicará un flujo RTSP.\r\n\r\n**Sistemas operativos compatibles** : Windows 10, Windows 11, Linux y macOS\r\n\r\n**Versión de OBS Studio compatible**: 30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Instalar\r\n## Windows\r\nEl instalador se puede encontrar en [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases).\r\n\r\nSi 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.\r\n\r\n### winget Paquete\r\nSi 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:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nPuede 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.\r\n\r\n## Linux (Solo x64)\r\n### Paquete DEB de Ubuntu/Debian\r\nDescarga el paquete deb desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) e instálalo.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0\r\n\r\n### Paquete Red-Hat RPM\r\nDescargue el paquete rpm desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) e instálelo.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0\r\n\r\n### ArchLinux AUR Paquete\r\nobs-rtspserver también está disponible como un [Paquete AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)\r\nSi usas [yay](https://github.com/Jguer/yay) simplemente ejecuta esto para instalarlo:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Otros\r\nDescargue el archivo tar.gz desde la [Página de lanzamiento](https://github.com/iamscottxu/obs-rtspserver/releases) y descomprímalo en \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Reemplace {version} con la última versión de lanzamiento, por ejemplo: v2.2.0\r\n\r\n\r\n# Construir\r\n* Instala cmake, visual studio (solo en Windows) y qt.\r\n* Descarga y configura el código fuente de obs-studio.\r\n* Copiar el código fuente a (código fuente de obs-studio)/plugins/obs-rtspserver/\r\n* Añade `add_subdirectory(obs-rtspserver)` a (código fuente de obs-studio)/plugins/CMakeLists.txt\r\n* Construye obs-rtspserver.\r\n\r\n# Preguntas frecuentes\r\n* [No puedo encontrar el complemento en el menú](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Licencia\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_fr-FR.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nCeci est un plugin pour obs-studio, encoder et publier dans un flux RTSP.\r\n\r\n**Systèmes d'exploitation pris en charge** : Windows 10, Windows 11, Linux and macOS\r\n\r\n**Version OBS Studio prise en charge** : 30.0.0+\r\n\r\n[![Statut de l'emballage](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Installer\r\n## Windows\r\nVous 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.\r\n\r\nSi 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.\r\n\r\n### winget Package\r\nSi 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 :\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nVous 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.\r\n\r\n## Linux (Seulement x64)\r\n### ArchLinux AUR Paquet\r\nTéléchargez le paquet deb depuis la [Page de sortie](https://github.com/iamscottxu/obs-rtspserver/releases) et installez-le.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0\r\n\r\n### Package Red-Hat RPM\r\nTéléchargez le package rpm depuis la [Page de sortie](https://github.com/iamscottxu/obs-rtspserver/releases) et installez-le.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0\r\n\r\n### ArchLinux AUR Package\r\nobs-rtspserver est également disponible en tant que [paquet AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)\r\nSi vous utilisez [yay](https://github.com/Jguer/yay), exécutez simplement ceci pour l'installer :\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Autre\r\nTéléchargez l'archive tar.gz depuis la [Page de publication](https://github.com/iamscottxu/obs-rtspserver/releases) et décompressez-la dans \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Remplacez {version} par la dernière version de sortie, par exemple : v2.2.0\r\n\r\n\r\n# Construire\r\n* Installer cmake, visual studio (uniquement sur Windows) et qt.\r\n* Téléchargez et configurez le code source d'obs-studio.\r\n* Copiez le code source vers (code source obs-studio)/plugins/obs-rtspserver/\r\n* Ajoutez `add_subdirectory(obs-rtspserver)` à (obs-studio source code)/plugins/CMakeLists.txt\r\n* Construisez obs-rtspserver.\r\n\r\n# FAQ\r\n* [Impossible de trouver le plugin dans le menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Licence\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_it-IT.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nQuesto è un plugin per obs-studio che codificherà l'output e pubblicherà un flusso RTSP.\r\n\r\n**Sistemi operativi supportati** : Windows 10, Windows 11, Linux e macOS\r\n\r\n**Versione di OBS Studio supportata**: 30.0.0+\r\n\r\n[![Stato del pacchetto](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Installazione\r\n## Windows\r\nÈ 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).\r\n\r\nSe 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.\r\n\r\n### winget Pacchetto\r\nSe 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:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nÈ 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).\r\n\r\n## Linux (Solo x64)\r\n### Ubuntu/Debian Pacchetto DEB\r\nScarica il pacchetto deb dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e installalo.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0\r\n\r\n### Red-Hat RPM Package\r\nScarica il pacchetto rpm dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e installalo.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0\r\n\r\n### ArchLinux Pacchetto AUR\r\nobs-rtspserver è disponibile anche come [Pacchetto AUR](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver).\r\nSe stai usando [yay](https://github.com/Jguer/yay) puoi installarlo con il seguente comando:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Altro\r\nScarica l'archivio tar.gz dalla [Pagina di rilascio](https://github.com/iamscottxu/obs-rtspserver/releases) e decomprimilo in \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Sostituisci {version} con l'ultima versione rilasciata, ad esempio: v2.2.0\r\n\r\n\r\n# Costruisci\r\n* Installa cmake, visual studio (solo su Windows) e qt.\r\n* Scarica e configura il codice sorgente da obs-studio.\r\n* Copia il codice sorgente in (codice sorgente obs-studio)/plugins/obs-rtspserver/\r\n* Aggiungi `add_subdirectory (obs-rtspserver)` a (codice sorgente obs-studio)/plugins/CMakeLists.txt.\r\n* Avvia la build obs-rtspserver.\r\n\r\n# FAQ\r\n* [Il plugin nel menu non è stato trovato](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Licenze software\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_ja-JP.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[obs-rtspserverを翻訳するのを助けてください！](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nこれはobs-studioのプラグインであり、RTSPストリームにエンコードして公開するものです。\r\n\r\n**サポートされているプラットフォーム**：Windows 10、Windows 11、Linux、およびmacOS\r\n\r\n**サポートされているOBS Studioのバージョン** : 30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# インストール\r\n## Windows\r\n[Release Page](https://github.com/iamscottxu/obs-rtspserver/releases) でインストーラーを見つけることができます。\r\n\r\n手動でインストールするために圧縮ファイルを使用したい場合は、それを解凍して（例：obs-rtspserver-v2.0.5-windows.zip）、obs-studioのインストールフォルダに配置してください。\r\n\r\n### winget パッケージ\r\nWindows 10 1709以降のオペレーティングシステムバージョンで、[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)がインストールされている場合は、次のコマンドを実行してインストールしてください。\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\n.macOSを使用する場合、[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)で.pkgインストーラを使用してインストールできます。\r\n\r\n## Linux (Only x64)\r\n### Ubuntu/Debian DEB パッケージ\r\ndebパッケージを[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からダウンロードしてインストールしてください。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* 「{version}」を最新のリリースバージョンで置き換えてください。例：v2.2.0\r\n\r\n### Red-Hat RPM パッケージ\r\nrpmパッケージを[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からダウンロードしてインストールしてください。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* {version}を最新のリリースバージョンであるv2.2.0などで置き換えてください。\r\n\r\n### ArchLinux AUR パッケージ\r\nobs-rtspserverは[AURパッケージ](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)としても利用可能です。\r\n[yay](https://github.com/Jguer/yay)を使用している場合は、次のコマンドを実行してインストールしてください。\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### 他の\r\n[リリースページ](https://github.com/iamscottxu/obs-rtspserver/releases)からtar.gzアーカイブをダウンロードし、\"/\"に展開してください。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* {version}を最新のリリースバージョンであるv2.2.0などで置き換えてください。\r\n\r\n\r\n# ビルド\r\n* cmake、visual studio（Windowsのみ）およびqtをインストールしてください。\r\n* ダウンロードして、obs-studioのソースコードを設定してください。\r\n* ソースコードを(obs-studioソースコード)/plugins/obs-rtspserver/にコピーしてください。\r\n* （obs-studioのソースコード）/plugins/CMakeLists.txtに`add_subdirectory(obs-rtspserver)`を追加してください。\r\n* Build obs-rtspserver.\r\n\r\n# FAQ\r\n* [メニューでプラグインが見つかりません](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# ライセンス\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_ko-KR.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[obs-rtspserver를 번역하는 데 도움을 주세요!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\n이 플러그인은 obs-studio에서 RTSP스트림을 인코딩 및 배포하는 플러그인입니다.\r\n\r\n**지원하는 운영체제** : Windows 10, Windows 11, Linux and macOS\r\n\r\n**지원되는 OBS Studio 버전** : 30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# 설치\r\n## Windows\r\n설치 파일은 [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases)에서 찾을 수 있습니다.\r\n\r\n직접 설치하려면 압축파일을 다운로드 받은 후 (예: obs-rtspserver-v2.0.5-windows.zip) obs-studio가 설치된 폴더에 압축을 푸십시오.\r\n\r\n### winget 패키지\r\nWindows 10 1709 이상 버전을 사용하고 있고 [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)가 설치되어 있다면 아래 명령어를 실행하여 바로 설치할 수 있습니다:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## macOS\r\nmacOS에서는 [Release Page](https://github.com/iamscottxu/obs-rtspserver/releases)에서 .pkg 설치파일을 찾아 설치할 수 있습니다.\r\n\r\n## Linux (x64만 지원)\r\n### 우분투/데비안 DEB 패키지\r\ndeb 패키지를 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 다운로드하여 설치하십시오.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0\r\n\r\n### 레드햇 RPM 패키지\r\nrpm 패키지를 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 다운로드하여 설치하십시오.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0\r\n\r\n### ArchLinux AUR 패키지\r\nobs-rtspserver는 [AUR 패키지](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)로도 사용할 수 있습니다.\r\n만약 [yay](https://github.com/Jguer/yay)를 사용한다면 다음 명령을 실행하여 설치하세요.\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### 다른\r\n\"/\"에서 [릴리스 페이지](https://github.com/iamscottxu/obs-rtspserver/releases)에서 tar.gz 아카이브를 다운로드하고 압축을 해제하세요.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* {version}를 최신 릴리스 버전으로 대체하십시오. 예: v2.2.0\r\n\r\n\r\n# 빌드\r\n* cmake, visual studio(only windows) 및 qt를 설치하십시오.\r\n* 다운로드하고 obs-studio의 소스 코드를 구성하세요.\r\n* 소스 코드를 (obs-studio 소스 코드)/plugins/obs-rtspserver/에 복사하세요.\r\n* (obs-studio 소스 코드)/plugins/CMakeLists.txt에 `add_subdirectory(obs-rtspserver)`를 추가하세요.\r\n* obs-rtspserver를 빌드하세요.\r\n\r\n# 자주 묻는 질문\r\n* [메뉴에서 플러그인을 찾을 수 없습니다](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# 라이선스\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_nl-NL.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Help translate obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nDit is een plug-in voor obs-studio die de uitvoer codeert en een RTSP-stream publiceert.\r\n\r\n**Ondersteunde besturingssystemen** : Windows 10, Windows 11, Linux and macOS\r\n\r\n**Ondersteunde OBS Studio-versie**: 30.0.0+\r\n\r\n[![Verpakkingsstatus](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Installatie\r\n## Windows\r\nU kunt het installatieprogramma gebruiken om het te installeren. Je vindt het installatieprogramma hier [release-pagina](https://github.com/iamscottxu/obs-rtspserver/releases).\r\n\r\nAls 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.\r\n\r\n### winget Pakket\r\nAls 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:\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## macOS\r\nU 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.\r\n\r\n## Linux (Alleen x64)\r\n### Ubuntu/Debian DEB-pakket\r\nDownload het deb-pakket van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en installeer het.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0\r\n\r\n### Red-Hat RPM-pakket\r\nDownload het rpm-pakket van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en installeer het.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0\r\n\r\n### ArchLinux AUR-pakket\r\nobs-rtspserver is ook beschikbaar als een [AUR-pakket](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)\r\nAls je [yay](https://github.com/Jguer/yay) gebruikt, voer dan gewoon dit uit om het te installeren:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Andere\r\nDownload het tar.gz-archief van de [Releasepagina](https://github.com/iamscottxu/obs-rtspserver/releases) en pak het uit naar \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Vervang {versie} door de laatste versie van de release, bijvoorbeeld: v2.2.0\r\n\r\n\r\n# Bouwen\r\n* Installeer cmake, visual studio (alleen Windows) en qt.\r\n* Download en configureer de broncode van obs-studio.\r\n* Kopieer de broncode naar (obs-studio broncode)/plugins/obs-rtspserver/\r\n* Voeg `add_subdirectory(obs-rtspserver)` toe aan (obs-studio broncode)/plugins/CMakeLists.txt\r\n* Bouw obs-rtspserver.\r\n\r\n# Veelgestelde vragen\r\n* [Kan de plugin niet vinden in het menu](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Licentie\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_ru-RU.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[Помогите перевести obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\nЭто плагин для obs-studio, кодирующий и публикующий поток RTSP.\r\n\r\n**Поддерживаемые платформы**: Windows 10, Windows 11, Linux и macOS\r\n\r\n**Поддерживаемая версия OBS Studio**: 30.0.0+\r\n\r\n[![Статус упаковки](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# Установить\r\n## Windows\r\nУстановщик можно найти на [странице релизов](https://github.com/iamscottxu/obs-rtspserver/releases).\r\n\r\nЕсли вы хотите использовать сжатый файл для ручной установки, вы можете распаковать его (например: obs-rtspserver-v2.0.5-windows.zip) и поместить его в папку установки вашей программы obs-studio.\r\n\r\n### winget Package\r\nЕсли версия операционной системы Windows 10 1709 или более поздняя и [app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1) был установлен, просто запустите это, чтобы установить его.\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## MacOS\r\nВы можете использовать установщик .pkg для установки, и установщик можно найти на [странице релизов](https://github.com/iamscottxu/obs-rtspserver/releases), если вы используете macOS.\r\n\r\n## Linux (Только x64)\r\n### Ubuntu/Debian DEB Package\r\nСкачайте deb-пакет с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и установите его.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* Замените {version} на последнюю версию релиза, например: v2.2.0\r\n\r\n### Red-Hat RPM Package\r\nСкачайте пакет rpm с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и установите его.\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* Замените {version} на последнюю версию релиза, например: v2.2.0\r\n\r\n### ArchLinux AUR Package\r\nobs-rtspserver также доступен в виде [AUR Package](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)\r\nЕсли вы используете [yay](https://github.com/Jguer/yay), просто выполните следующую команду для его установки:\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### Другое\r\nСкачайте архив tar.gz с [страницы релизов](https://github.com/iamscottxu/obs-rtspserver/releases) и распакуйте его в корневой каталог \"/\".\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* Замените {version} на последнюю версию релиза, например: v2.2.0\r\n\r\n\r\n# Создать\r\n* Установите cmake, visual studio (только для Windows) и qt.\r\n* Скачайте и настройте исходный код obs-studio.\r\n* Скопируйте исходный код в (исходный код obs-studio)/plugins/obs-rtspserver/\r\n* Добавьте `add_subdirectory(obs-rtspserver)` в (исходный код obs-studio)/plugins/CMakeLists.txt\r\n* Соберите obs-rtspserver.\r\n\r\n# ЧАВО\r\n* [Не удается найти плагин в меню](https://github.com/iamscottxu/obs-rtspserver/wiki/FAQ#cant-find-the-plugin-in-the-menu)\r\n\r\n# Лицензия\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_zh-CN.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[帮助翻译 obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\n这是一个 OBS Studio 插件,此插件可对输出进行编码并发布 RTSP 流。\r\n\r\n**支持的平台** : Windows 10、Windows 11、Linux和macOS\r\n\r\n**支持的 OBS Studio 版本**：30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# 安装\r\n## Windows\r\n可以使用安装程序进行安装，安装程序可以在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。\r\n\r\n如果要使用压缩文件手动安装，可以解压缩压缩文件（例如：obs-rtspserver-v2.0.5-windows.zip）把它放到你的 OBS Studio 安装文件夹里。\r\n\r\n### winget 软件包\r\n如果你使用 Windows 10 1709 和以后的版本，且已经安装了[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)后，可以运行以下命令进行安装：\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## macOS\r\n如果你使用的是 macOS 操作系统，您可以使用安装程序进行安装，安装程序 .pkg 可以在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。\r\n\r\n## Linux（仅 x64）\r\n### Ubuntu/Debian DEB 软件包\r\n在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 deb 软件包并安装。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* 将 {version} 替换成最新发布版本号，例如：v2.2.0\r\n\r\n### Red-Hat RPM 软件包\r\n在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 rpm 软件包并安装。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* 将 {version} 替换成最新发布版本号，例如：v2.2.0\r\n\r\n### ArchLinux AUR 软件包\r\nobs-rtspserver也可以作为[AUR软件包](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)提供。如果您使用[yay](https://github.com/Jguer/yay)，请运行以下命令进行安装：\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### 其他\r\n在[发布页面](https://github.com/iamscottxu/obs-rtspserver/releases)下载 tar.gz 压缩包并解压到 \"/\"。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* 将 {version} 替换成最新发布版本号，例如：v2.2.0\r\n\r\n\r\n# 生成\r\n* 安装 cmake 、 visual studio (仅 Windows )和 qt；\r\n* 下载并配置 OBS Studio 的源代码；\r\n* 将源代码复制到 (OBS Studio 源代码目录)/plugins/obs-rtspserver/ 中；\r\n* 添加 `add_subdirectory(obs-rtspserver)` 到 (OBS Studio 源代码目录)/plugins/CMakeLists.txt 中；\r\n* 生成 obs-rtspserver 。\r\n\r\n# 常见问题\r\n* [在菜单中找不到插件](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)\r\n\r\n# 许可信息\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "README_zh-TW.md",
    "content": "![Latest Release](https://img.shields.io/github/v/release/iamscottxu/obs-rtspserver.svg)\r\n![CI Release](https://github.com/iamscottxu/obs-rtspserver/workflows/CI%20Release/badge.svg)\r\n![Contributors](https://img.shields.io/github/contributors/iamscottxu/obs-rtspserver.svg)\r\n![Total Downloads](https://img.shields.io/github/downloads/iamscottxu/obs-rtspserver/total.svg)\r\n![License](https://img.shields.io/github/license/iamscottxu/obs-rtspserver.svg)\r\n\r\n\r\n🇨🇳 [简体中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-CN.md)\r\n🇭🇰 [繁體中文](//github.com/iamscottxu/obs-rtspserver/blob/master/README_zh-TW.md)\r\n🇯🇵 [日本語](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ja-JP.md)\r\n🇰🇷 [한국어](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ko-KR.md)\r\n🇪🇦 [Español](//github.com/iamscottxu/obs-rtspserver/blob/master/README_es-ES.md)\r\n🇫🇷 [Français](//github.com/iamscottxu/obs-rtspserver/blob/master/README_fr-FR.md)\r\n🇮🇹 [Italiano](//github.com/iamscottxu/obs-rtspserver/blob/master/README_it-IT.md)\r\n🇩🇪 [Deutsch](//github.com/iamscottxu/obs-rtspserver/blob/master/README_de-DE.md)\r\n🇳🇱 [Nederlands](//github.com/iamscottxu/obs-rtspserver/blob/master/README_nl-NL.md)\r\n🇷🇺 [Русский](//github.com/iamscottxu/obs-rtspserver/blob/master/README_ru-RU.md)\r\n\r\n<font size=\"5\">[幫助翻譯 obs-rtspserver!](https://www.transifex.com/scott-xu/obs-rtspserver)</font>\r\n\r\n# OBS-RTSPServer\r\n\r\n這是一個 OBS Studio 插件,此外掛程式可對輸出進行編碼並發布 RTSP 流。\r\n\r\n**支援的平台** : Windows 10、Windows 11、Linux和macOS\r\n\r\n**支援的 OBS Studio 版本**：30.0.0+\r\n\r\n[![Packaging status](https://repology.org/badge/vertical-allrepos/obs-rtspserver.svg)](https://repology.org/project/obs-rtspserver/versions)\r\n\r\n# 安裝\r\n## Windows\r\n可以使用安裝程式進行安裝，安裝程式可以在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。\r\n\r\n如果要使用壓縮檔案手動安裝，可以解壓縮壓縮檔案（例如：obs-rtspserver-v2.0.5-windows.zip）把它放到你的 OBS Studio 安裝資料夾裡。\r\n\r\n### winget 軟體包\r\n如果你使用 Windows 10 1709 和以後的版本，且已經安裝了[app-installer](https://www.microsoft.com/store/productId/9NBLGGH4NNS1)後，可以執行下列指令安裝：\r\n\r\n```powershell\r\nwinget install iamscottxu.obs-rtspserver\r\n```\r\n\r\n## macOS\r\n如果你使用的是 macOS 作業系統，您可以使用安裝程式進行安裝，安裝程式 .pkg 可以在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)中找到。\r\n\r\n## Linux（僅 x64）\r\n### Ubuntu/Debian DEB 軟體包\r\n在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 deb 軟體包並安裝。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.deb https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.deb\r\napt install -y obs-rtspserver-linux.deb\r\n```\r\n* 将 {version} 替换成最新发布版本号，例如：v2.2.0\r\n\r\n### Red-Hat RPM 軟體包\r\n在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 rpm 軟體包並安裝。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.rpm https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.rpm\r\nrpm -ivh obs-rtspserver-linux.rpm\r\n```\r\n* 將 {version} 替換成最新發布版本號，例如：v2.2.0\r\n\r\n### ArchLinux AUR 軟體包\r\nobs-rtspserver也可以作為[AUR軟體包](https://aur.archlinux.org/packages/?O=0&K=obs-rtspserver)提供。 如果您使用[yay](https://github.com/Jguer/yay)，請執行下列命令進行安裝：\r\n\r\n```bash\r\nyay -S obs-rtspserver\r\n```\r\n\r\n### 其他\r\n在[發佈頁面](https://github.com/iamscottxu/obs-rtspserver/releases)下載 tar.gz 壓縮套件並解壓縮到 \"/\"。\r\n\r\n```bash\r\nwget -O obs-rtspserver-linux.tar.gz https://github.com/iamscottxu/obs-rtspserver/releases/download/{version}/obs-rtspserver-{version}-linux.tar.gz\r\n#For all user\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C /\r\n#For local user\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/\r\nmkdir -p ~/.config/obs-studio/plugins/obs-rtspserver/data/\r\nmkdir -p ~/obs-rtspserver-linux\r\ntar -xzvf obs-rtspserver-linux.tar.gz -C ~/obs-rtspserver-linux/\r\nmv ~/obs-rtspserver-linux/usr/lib/obs-plugins/obs-rtspserver.so ~/.config/obs-studio/plugins/obs-rtspserver/bin/64bit/obs-rtspserver.so\r\nmv ~/obs-rtspserver-linux/usr/share/obs/obs-plugins/obs-rtspserver/locale ~/.config/obs-studio/plugins/obs-rtspserver/data/locale\r\nrm -rf ~/obs-rtspserver-linux\r\n```\r\n* 將 {version} 替換成最新發布版本號，例如：v2.2.0\r\n\r\n\r\n# 產生\r\n* 安裝 cmake 、 visual studio (僅 Windows )和 qt；\r\n* 下載並配置 OBS Studio 的源代碼；\r\n* 將原始碼複製到 (OBS Studio 原始碼目錄)/plugins/obs-rtspserver/ 中；\r\n* 加入 `add_subdirectory(obs-rtspserver)` 到 (OBS Studio 原始碼目錄)/plugins/CMakeLists.txt 中；\r\n* 產生 obs-rtspserver 。\r\n\r\n# 常見問題\r\n* [在選單中找不到外掛程式](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)\r\n\r\n# 許可資訊\r\n* [RtspServer](https://github.com/PHZ76/RtspServer/) - [MIT License](https://github.com/PHZ76/RtspServer/blob/master/LICENSE)\r\n* [Qt5](https://www.qt.io/) - [GPL version 2](https://doc.qt.io/qt-5/licensing.html)\r\n* [libb64](https://sourceforge.net/projects/libb64/) - [Public domain dedication](https://sourceforge.net/p/libb64/git/ci/master/tree/LICENSE)\r\n"
  },
  {
    "path": "bundle/installer-macos.pkgproj.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PACKAGES</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>MUST-CLOSE-APPLICATION-ITEMS</key>\n\t\t\t<array/>\n\t\t\t<key>MUST-CLOSE-APPLICATIONS</key>\n\t\t\t<false/>\n\t\t\t<key>PACKAGE_FILES</key>\n\t\t\t<dict>\n\t\t\t\t<key>DEFAULT_INSTALL_LOCATION</key>\n\t\t\t\t<string>/Library/Application Support/obs-studio/plugins</string>\n\t\t\t\t<key>HIERARCHY</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t<string>Applications</string>\n\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t<integer>509</integer>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<string>../@RELATIVE_INSTALL_PATH@/@CMAKE_PROJECT_NAME@</string>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>3</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</array>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<string>plugins</string>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>2</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>509</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>2</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t\t\t\t</array>\n\t\t\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t\t\t<string>obs-studio</string>\n\t\t\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t<integer>2</integer>\n\t\t\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t\t\t<integer>509</integer>\n\t\t\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t\t\t<integer>2</integer>\n\t\t\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t\t</array>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Application Support</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Automator</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Documentation</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Extensions</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Filesystems</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Frameworks</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Input Methods</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Internet Plug-Ins</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>LaunchAgents</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>LaunchDaemons</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>PreferencePanes</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Preferences</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Printers</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>PrivilegedHelperTools</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>1005</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>QuickLook</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>QuickTime</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Screen Savers</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Scripts</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Services</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Widgets</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t</array>\n\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t<string>Library</string>\n\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t\t\t<string>Shared</string>\n\t\t\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t\t\t<integer>1023</integer>\n\t\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t</array>\n\t\t\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t\t\t<integer>80</integer>\n\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t<string>Users</string>\n\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>GID</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t<string>/</string>\n\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t<key>PERMISSIONS</key>\n\t\t\t\t\t<integer>493</integer>\n\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t<key>UID</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t</dict>\n\t\t\t\t<key>PAYLOAD_TYPE</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>PRESERVE_EXTENDED_ATTRIBUTES</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>SHOW_INVISIBLE</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>SPLIT_FORKS</key>\n\t\t\t\t<true/>\n\t\t\t\t<key>TREAT_MISSING_FILES_AS_WARNING</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>VERSION</key>\n\t\t\t\t<integer>5</integer>\n\t\t\t</dict>\n\t\t\t<key>PACKAGE_SCRIPTS</key>\n\t\t\t<dict>\n\t\t\t\t<key>POSTINSTALL_PATH</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t</dict>\n\t\t\t\t<key>PREINSTALL_PATH</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t</dict>\n\t\t\t\t<key>RESOURCES</key>\n\t\t\t\t<array/>\n\t\t\t</dict>\n\t\t\t<key>PACKAGE_SETTINGS</key>\n\t\t\t<dict>\n\t\t\t\t<key>AUTHENTICATION</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>CONCLUSION_ACTION</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>FOLLOW_SYMBOLIC_LINKS</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>IDENTIFIER</key>\n\t\t\t\t<string>@MACOS_BUNDLEID@</string>\n\t\t\t\t<key>LOCATION</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t\t<key>NAME</key>\n\t\t\t\t<string>@CMAKE_PROJECT_NAME@</string>\n\t\t\t\t<key>OVERWRITE_PERMISSIONS</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>PAYLOAD_SIZE</key>\n\t\t\t\t<integer>-1</integer>\n\t\t\t\t<key>REFERENCE_PATH</key>\n\t\t\t\t<string></string>\n\t\t\t\t<key>RELOCATABLE</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>USE_HFS+_COMPRESSION</key>\n\t\t\t\t<false/>\n\t\t\t\t<key>VERSION</key>\n\t\t\t\t<string>@PROJECT_VERSION@</string>\n\t\t\t</dict>\n\t\t\t<key>TYPE</key>\n\t\t\t<integer>0</integer>\n\t\t\t<key>UUID</key>\n\t\t\t<string>7C03585F-BD40-434A-9436-FBFD8700B437</string>\n\t\t</dict>\n\t</array>\n\t<key>PROJECT</key>\n\t<dict>\n\t\t<key>PROJECT_COMMENTS</key>\n\t\t<dict>\n\t\t\t<key>NOTES</key>\n\t\t\t<data>\n\t\t\t</data>\n\t\t</dict>\n\t\t<key>PROJECT_PRESENTATION</key>\n\t\t<dict>\n\t\t\t<key>BACKGROUND</key>\n\t\t\t<dict>\n\t\t\t\t<key>APPAREANCES</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>DARK_AQUA</key>\n\t\t\t\t\t<dict/>\n\t\t\t\t\t<key>LIGHT_AQUA</key>\n\t\t\t\t\t<dict/>\n\t\t\t\t</dict>\n\t\t\t\t<key>SHARED_SETTINGS_FOR_ALL_APPAREANCES</key>\n\t\t\t\t<true/>\n\t\t\t</dict>\n\t\t\t<key>INSTALLATION TYPE</key>\n\t\t\t<dict>\n\t\t\t\t<key>HIERARCHIES</key>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>INSTALLER</key>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LIST</key>\n\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t<key>CHILDREN</key>\n\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t<key>DESCRIPTION</key>\n\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t<key>OPTIONS</key>\n\t\t\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t\t\t<key>HIDDEN</key>\n\t\t\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t\t\t<key>PACKAGE_UUID</key>\n\t\t\t\t\t\t\t\t<string>7C03585F-BD40-434A-9436-FBFD8700B437</string>\n\t\t\t\t\t\t\t\t<key>TITLE</key>\n\t\t\t\t\t\t\t\t<array/>\n\t\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t\t\t<key>UUID</key>\n\t\t\t\t\t\t\t\t<string>4F4C1C62-FC2C-4E46-8FBD-CD875E187DD2</string>\n\t\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<key>REMOVED</key>\n\t\t\t\t\t\t<dict/>\n\t\t\t\t\t</dict>\n\t\t\t\t</dict>\n\t\t\t\t<key>MODE</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t</dict>\n\t\t\t<key>INSTALLATION_STEPS</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewIntroductionController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>Introduction</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewReadMeController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>ReadMe</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewLicenseController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>License</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewDestinationSelectController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>TargetSelect</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewInstallationTypeController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>PackageSelection</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewInstallationController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>Install</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>\n\t\t\t\t\t<string>ICPresentationViewSummaryController</string>\n\t\t\t\t\t<key>INSTALLER_PLUGIN</key>\n\t\t\t\t\t<string>Summary</string>\n\t\t\t\t\t<key>LIST_TITLE_KEY</key>\n\t\t\t\t\t<string>InstallerSectionTitle</string>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t\t<key>INTRODUCTION</key>\n\t\t\t<dict>\n\t\t\t\t<key>LOCALIZATIONS</key>\n\t\t\t\t<array/>\n\t\t\t</dict>\n\t\t\t<key>LICENSE</key>\n\t\t\t<dict>\n\t\t\t\t<key>LOCALIZATIONS</key>\n\t\t\t\t<array>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>English</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>PATH</key>\n\t\t\t\t\t\t\t<string>LICENSE.txt</string>\n\t\t\t\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</dict>\n\t\t\t\t</array>\n\t\t\t\t<key>MODE</key>\n\t\t\t\t<integer>0</integer>\n\t\t\t</dict>\n\t\t\t<key>README</key>\n\t\t\t<dict>\n\t\t\t\t<key>LOCALIZATIONS</key>\n\t\t\t\t<array/>\n\t\t\t</dict>\n\t\t\t<key>SUMMARY</key>\n\t\t\t<dict>\n\t\t\t\t<key>LOCALIZATIONS</key>\n\t\t\t\t<array/>\n\t\t\t</dict>\n\t\t\t<key>TITLE</key>\n\t\t\t<dict>\n\t\t\t\t<key>LOCALIZATIONS</key>\n\t\t\t\t<array>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>English</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP Server Plugin</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Dutch</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP Server Plugin</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Simplified Chinese</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP 服务器插件</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Traditional Chinese</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP 伺服器外掛程式</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Japanese</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSPサーバープラグイン</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Spanish</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>Complemento servidor RTSP para OBS</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>German</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP Server Plugin</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>French</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>Module d'extension de serveur RTSP OBS</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Italian</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP Server Plugin</string>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>LANGUAGE</key>\n\t\t\t\t\t\t<string>Korean</string>\n\t\t\t\t\t\t<key>VALUE</key>\n\t\t\t\t\t\t<string>OBS RTSP 서버 플러그인</string>\n\t\t\t\t\t</dict>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t</dict>\n\t\t<key>PROJECT_REQUIREMENTS</key>\n\t\t<dict>\n\t\t\t<key>LIST</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>BEHAVIOR</key>\n\t\t\t\t\t<integer>3</integer>\n\t\t\t\t\t<key>DICTIONARY</key>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_FILES_CONDITION</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_FILES_DISK_TYPE</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_FILES_LIST</key>\n\t\t\t\t\t\t<array>\n\t\t\t\t\t\t\t<string>/Applications/OBS.app</string>\n\t\t\t\t\t\t</array>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_FILES_SELECTOR</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<key>IC_REQUIREMENT_CHECK_TYPE</key>\n\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t<key>IDENTIFIER</key>\n\t\t\t\t\t<string>fr.whitebox.Packages.requirement.files</string>\n\t\t\t\t\t<key>MESSAGE</key>\n\t\t\t\t\t<array/>\n\t\t\t\t\t<key>NAME</key>\n\t\t\t\t\t<string>Files</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>BEHAVIOR</key>\n\t\t\t\t\t<integer>3</integer>\n\t\t\t\t\t<key>DICTIONARY</key>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_OS_DISK_TYPE</key>\n\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_OS_DISTRIBUTION_TYPE</key>\n\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t<key>IC_REQUIREMENT_OS_MINIMUM_VERSION</key>\n\t\t\t\t\t\t<integer>101300</integer>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<key>IC_REQUIREMENT_CHECK_TYPE</key>\n\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t<key>IDENTIFIER</key>\n\t\t\t\t\t<string>fr.whitebox.Packages.requirement.os</string>\n\t\t\t\t\t<key>MESSAGE</key>\n\t\t\t\t\t<array/>\n\t\t\t\t\t<key>NAME</key>\n\t\t\t\t\t<string>Operating System</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t\t<key>RESOURCES</key>\n\t\t\t<array/>\n\t\t\t<key>ROOT_VOLUME_ONLY</key>\n\t\t\t<false/>\n\t\t</dict>\n\t\t<key>PROJECT_SETTINGS</key>\n\t\t<dict>\n\t\t\t<key>ADVANCED_OPTIONS</key>\n\t\t\t<dict>\n\t\t\t\t<key>installer-script.domains:enable_currentUserHome</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t\t<key>installer-script.domains:enable_localSystem</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t</dict>\n\t\t\t<key>BUILD_FORMAT</key>\n\t\t\t<integer>0</integer>\n\t\t\t<key>BUILD_PATH</key>\n\t\t\t<dict>\n\t\t\t\t<key>PATH</key>\n\t\t\t\t<string>../@RELATIVE_BUILD_PATH@</string>\n\t\t\t\t<key>PATH_TYPE</key>\n\t\t\t\t<integer>1</integer>\n\t\t\t</dict>\n\t\t\t<key>EXCLUDED_FILES</key>\n\t\t\t<array>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATTERNS_ARRAY</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.DS_Store</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>PROTECTED</key>\n\t\t\t\t\t<true/>\n\t\t\t\t\t<key>PROXY_NAME</key>\n\t\t\t\t\t<string>Remove .DS_Store files</string>\n\t\t\t\t\t<key>PROXY_TOOLTIP</key>\n\t\t\t\t\t<string>Remove \".DS_Store\" files created by the Finder.</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATTERNS_ARRAY</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.pbdevelopment</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>PROTECTED</key>\n\t\t\t\t\t<true/>\n\t\t\t\t\t<key>PROXY_NAME</key>\n\t\t\t\t\t<string>Remove .pbdevelopment files</string>\n\t\t\t\t\t<key>PROXY_TOOLTIP</key>\n\t\t\t\t\t<string>Remove \".pbdevelopment\" files created by ProjectBuilder or Xcode.</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATTERNS_ARRAY</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>CVS</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.cvsignore</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.cvspass</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.svn</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.git</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>.gitignore</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>PROTECTED</key>\n\t\t\t\t\t<true/>\n\t\t\t\t\t<key>PROXY_NAME</key>\n\t\t\t\t\t<string>Remove SCM metadata</string>\n\t\t\t\t\t<key>PROXY_TOOLTIP</key>\n\t\t\t\t\t<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATTERNS_ARRAY</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>classes.nib</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>designable.db</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>info.nib</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>0</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>PROTECTED</key>\n\t\t\t\t\t<true/>\n\t\t\t\t\t<key>PROXY_NAME</key>\n\t\t\t\t\t<string>Optimize nib files</string>\n\t\t\t\t\t<key>PROXY_TOOLTIP</key>\n\t\t\t\t\t<string>Remove \"classes.nib\", \"info.nib\" and \"designable.nib\" files within .nib bundles.</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>PATTERNS_ARRAY</key>\n\t\t\t\t\t<array>\n\t\t\t\t\t\t<dict>\n\t\t\t\t\t\t\t<key>REGULAR_EXPRESSION</key>\n\t\t\t\t\t\t\t<false/>\n\t\t\t\t\t\t\t<key>STRING</key>\n\t\t\t\t\t\t\t<string>Resources Disabled</string>\n\t\t\t\t\t\t\t<key>TYPE</key>\n\t\t\t\t\t\t\t<integer>1</integer>\n\t\t\t\t\t\t</dict>\n\t\t\t\t\t</array>\n\t\t\t\t\t<key>PROTECTED</key>\n\t\t\t\t\t<true/>\n\t\t\t\t\t<key>PROXY_NAME</key>\n\t\t\t\t\t<string>Remove Resources Disabled folders</string>\n\t\t\t\t\t<key>PROXY_TOOLTIP</key>\n\t\t\t\t\t<string>Remove \"Resources Disabled\" folders.</string>\n\t\t\t\t\t<key>STATE</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t\t<dict>\n\t\t\t\t\t<key>SEPARATOR</key>\n\t\t\t\t\t<true/>\n\t\t\t\t</dict>\n\t\t\t</array>\n\t\t\t<key>NAME</key>\n\t\t\t<string>@CMAKE_PROJECT_NAME@</string>\n\t\t\t<key>PAYLOAD_ONLY</key>\n\t\t\t<false/>\n\t\t\t<key>TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING</key>\n\t\t\t<false/>\n\t\t</dict>\n\t</dict>\n\t<key>TYPE</key>\n\t<integer>0</integer>\n\t<key>VERSION</key>\n\t<integer>2</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "bundle/macOS/Plugin-Info.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleName</key>\n\t<string>${MACOSX_PLUGIN_BUNDLE_NAME}</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>${MACOSX_PLUGIN_GUI_IDENTIFIER}</string>\n\t<key>CFBundleVersion</key>\n\t<string>${MACOSX_PLUGIN_BUNDLE_VERSION}</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${MACOSX_PLUGIN_SHORT_VERSION_STRING}</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleExecutable</key>\n\t<string>${MACOSX_PLUGIN_EXECUTABLE_NAME}</string>\n\t<key>CFBundlePackageType</key>\n\t<string>${MACOSX_PLUGIN_BUNDLE_TYPE}</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.13</string>\n</dict>\n</plist>"
  },
  {
    "path": "bundle/macOS/entitlements.plist",
    "content": "<!--?xml version=\"1.0\" encoding=\"UTF-8\"?-->\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>com.apple.security.cs.allow-unsigned-executable-memory</key>\n        <true/>\n        <key>com.apple.security.device.camera</key>\n        <true/>\n        <key>com.apple.security.device.audio-input</key>\n        <true/>\n        <key>com.apple.security.cs.disable-library-validation</key>\n        <true/>\n        <!-- Allows @executable_path to load libaries from within the .app bundle. -->\n        <key>com.apple.security.cs.allow-dyld-environment-variables</key>\n        <true/>\n    </dict>\n</plist>"
  },
  {
    "path": "data/locale/de-DE.ini",
    "content": "RtspServer=\"RTSP Server\"\nRstpServer.Description=\"OBS RTSP Server Plugin\"\nRtspServer.Properties=\"RTSP Server\"\nRtspServer.Properties.StartOutput=\"Starten\"\nRtspServer.Properties.StopOutput=\"Stoppen\"\nRtspServer.Properties.Options=\"Optionen\"\nRtspServer.Properties.Options.AutoStart=\"AutoStart\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Audiospuren: \"\nRtspServer.Properties.Target=\"Ziel\"\nRtspServer.Properties.Target.Multicast=\"Aktiviertes Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Kopieren\"\nRtspServer.Properties.Options.Output=\"Ausgabe-Optionen: \"\nRtspServer.Properties.Options.Output.Tip=\"Wenn Sie die Ausgabe-Optionen ändern wollen öffnen Sie: Datei->Einstellungen->Ausgabe->Streaming.\"\nRtspServer.Properties.Authentication=\"Authentifizierung\"\nRtspServer.Properties.Authentication.Enabled=\"Aktiviert\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Benutzername: \"\nRtspServer.Properties.Authentication.Password=\"Passwort: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Optional)\"\nRtspServer.Properties.Status=\"Status\"\nRtspServer.Properties.Status.TotalDataSent=\"Gesamtdatenausgabe: \"\nRtspServer.Properties.Status.Bitrate=\"Bitrate: \"\nRtspServer.Properties.Status.DroppedFrames=\"Dropped Frames:\"\nRtspServer.Properties.Version=\"Version: \"\n\nRtspOutput=\"RTSP Ausgabe\"\nRtspOutput.Error.BeginDataCapture=\"Datenerfassung konnte nicht gestartet werden\"\nRtspOutput.Error.InitEncoders=\"Kodierungsinitialisierungsfehler\"\nRtspOutput.Error.StartRtspServer=\"RTSP Server konnte nicht auf Port '%d' gestartet werden\"\nRtspOutput.Error.StartMulticast=\"Multicast-Start fehlgeschlagen.\"\nRtspOutput.Error.Encode=\"Kodierungsfehler\"\nRtspOutput.Error.Unknown=\"Unbekannter Fehler\"\nRtspOutput.Hotkey.StartOutput=\"Starten\"\nRtspOutput.Hotkey.StopOutput=\"Stoppen\"\nRtspOutput.Properties.Multicast=\"Aktiviertes Multicast\"\nRtspOutput.Properties.Port=\"Port\"\nRtspOutput.Properties.UrlSuffix=\"URL-Suffix\"\nRtspOutput.Properties.OutputAudio=\"Audioausgabe aktivieren\"\nRtspOutput.Properties.Authentication=\"Authentifizierung\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Benutzername\"\nRtspOutput.Properties.Authentication.Password=\"Passwort\"\n\nReset=\"Zurücksetzen\"\n\n"
  },
  {
    "path": "data/locale/en-US.ini",
    "content": "RtspServer=\"RTSP Server\"\nRstpServer.Description=\"OBS RTSP Server Plugin\"\nRtspServer.Properties=\"RTSP Server\"\nRtspServer.Properties.StartOutput=\"Start\"\nRtspServer.Properties.StopOutput=\"Stop\"\nRtspServer.Properties.Options=\"Options\"\nRtspServer.Properties.Options.AutoStart=\"Auto Start\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Audio Tracks: \"\nRtspServer.Properties.Target=\"Target\"\nRtspServer.Properties.Target.Multicast=\"Enabled Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Copy\"\nRtspServer.Properties.Options.Output=\"Output Options: \"\nRtspServer.Properties.Options.Output.Tip=\"Open File->Settings->Output->Streaming to change output options.\"\nRtspServer.Properties.Authentication=\"Authentication\"\nRtspServer.Properties.Authentication.Enabled=\"Enabled\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Username: \"\nRtspServer.Properties.Authentication.Password=\"Password: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Optional)\"\nRtspServer.Properties.Status=\"Status\"\nRtspServer.Properties.Status.TotalDataSent=\"Total Data Output: \"\nRtspServer.Properties.Status.Bitrate=\"Bitrate: \"\nRtspServer.Properties.Status.DroppedFrames=\"Dropped Frames: \"\nRtspServer.Properties.Version=\"Version: \"\n\nRtspOutput=\"RTSP Output\"\nRtspOutput.Error.BeginDataCapture=\"can't begin data capture\"\nRtspOutput.Error.InitEncoders=\"initialize encoders error\"\nRtspOutput.Error.StartRtspServer=\"starting RTSP server failed on port '%d'\"\nRtspOutput.Error.StartMulticast=\"starting multicast failed\"\nRtspOutput.Error.Encode=\"encode error\"\nRtspOutput.Error.Unknown=\"unknown error\"\nRtspOutput.Hotkey.StartOutput=\"Start Output\"\nRtspOutput.Hotkey.StopOutput=\"Stop Output\"\nRtspOutput.Properties.Multicast=\"Enabled Multicast\"\nRtspOutput.Properties.Port=\"Port\"\nRtspOutput.Properties.UrlSuffix=\"URL Suffix\"\nRtspOutput.Properties.OutputAudio=\"Enable Audio Output\"\nRtspOutput.Properties.Authentication=\"Authentication\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Username\"\nRtspOutput.Properties.Authentication.Password=\"Password\"\n\nReset=\"Reset\"\n\n"
  },
  {
    "path": "data/locale/es-ES.ini",
    "content": "RtspServer=\"Servidor RTSP\"\nRstpServer.Description=\"Complemento servidor RTSP para OBS\"\nRtspServer.Properties=\"Servidor RTSP\"\nRtspServer.Properties.StartOutput=\"Iniciar\"\nRtspServer.Properties.StopOutput=\"Parar\"\nRtspServer.Properties.Options=\"Opciones\"\nRtspServer.Properties.Options.AutoStart=\"Autoencendido\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Pistas de audio: \"\nRtspServer.Properties.Target=\"Objetivo\"\nRtspServer.Properties.Target.Multicast=\"Habilitado Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Copiar\"\nRtspServer.Properties.Options.Output=\"Opciones de salida: \"\nRtspServer.Properties.Options.Output.Tip=\"Si desea cambiar las opciones de salida, abra: Archivo->Configuración->Salida->Emisión.\"\nRtspServer.Properties.Authentication=\"Autenticación\"\nRtspServer.Properties.Authentication.Enabled=\"Activada\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Nombre de usuario: \"\nRtspServer.Properties.Authentication.Password=\"Contraseña: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Opcional)\"\nRtspServer.Properties.Status=\"Estatus\"\nRtspServer.Properties.Status.TotalDataSent=\"Salida de datos total: \"\nRtspServer.Properties.Status.Bitrate=\"Tasa de bits: \"\nRtspServer.Properties.Status.DroppedFrames=\"Frames caídos:\"\nRtspServer.Properties.Version=\"Versión: \"\n\nRtspOutput=\"Salida RTSP\"\nRtspOutput.Error.BeginDataCapture=\"No se pudo iniciar la adquisición de datos\"\nRtspOutput.Error.InitEncoders=\"Error de inicialización de codificación\"\nRtspOutput.Error.StartRtspServer=\"El servidor RTSP no se pudo iniciar en el puerto '%d'\"\nRtspOutput.Error.StartMulticast=\"error al iniciar la multidifusión\"\nRtspOutput.Error.Encode=\"Error de codificación\"\nRtspOutput.Error.Unknown=\"Error desconocido\"\nRtspOutput.Hotkey.StartOutput=\"Iniciar\"\nRtspOutput.Hotkey.StopOutput=\"Parar\"\nRtspOutput.Properties.Multicast=\"Habilitado Multicast\"\nRtspOutput.Properties.Port=\"Puerto de red\"\nRtspOutput.Properties.UrlSuffix=\"Sufijo de URL\"\nRtspOutput.Properties.OutputAudio=\"Habilitar salida de audio\"\nRtspOutput.Properties.Authentication=\"Autenticación\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Nombre de usuario\"\nRtspOutput.Properties.Authentication.Password=\"Contraseña\"\n\nReset=\"Reiniciar\"\n\n"
  },
  {
    "path": "data/locale/fr-FR.ini",
    "content": "RtspServer=\"Serveur RTSP\"\nRstpServer.Description=\"Module d'extension de serveur RTSP OBS\"\nRtspServer.Properties=\"Serveur RTSP\"\nRtspServer.Properties.StartOutput=\"Démarrer\"\nRtspServer.Properties.StopOutput=\"Arrêter\"\nRtspServer.Properties.Options=\"Options\"\nRtspServer.Properties.Options.AutoStart=\"Démarrage automatique\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Pistes audio: \"\nRtspServer.Properties.Target=\"Cible\"\nRtspServer.Properties.Target.Multicast=\"Activé Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Copier\"\nRtspServer.Properties.Options.Output=\"Options de sortie: \"\nRtspServer.Properties.Options.Output.Tip=\"Ouvrir le Fichier->Paramètres->Sortie->Streaming pour modifier les options de sortie.\"\nRtspServer.Properties.Authentication=\"Authentification\"\nRtspServer.Properties.Authentication.Enabled=\"Activée\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Nom d'utilisateur: \"\nRtspServer.Properties.Authentication.Password=\"Mot de passe: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Facultatif)\"\nRtspServer.Properties.Status=\"Statut\"\nRtspServer.Properties.Status.TotalDataSent=\"Sortie totale des données: \"\nRtspServer.Properties.Status.Bitrate=\"Débit binaire: \"\nRtspServer.Properties.Status.DroppedFrames=\"Images perdues :\"\nRtspServer.Properties.Version=\"Version: \"\n\nRtspOutput=\"Sortie RTSP\"\nRtspOutput.Error.BeginDataCapture=\"ne peut pas commencer la capture de données\"\nRtspOutput.Error.InitEncoders=\"Erreur d'initialisation des encodeurs\"\nRtspOutput.Error.StartRtspServer=\"le démarrage du serveur RTSP a échoué sur le port '%d'\"\nRtspOutput.Error.StartMulticast=\"échec du démarrage de la multidiffusion\"\nRtspOutput.Error.Encode=\"erreur d'encodage\"\nRtspOutput.Error.Unknown=\"erreur inconnue\"\nRtspOutput.Hotkey.StartOutput=\"Démarrer la sortie\"\nRtspOutput.Hotkey.StopOutput=\"Arrêter la sortie\"\nRtspOutput.Properties.Multicast=\"Activé Multicast\"\nRtspOutput.Properties.Port=\"Port\"\nRtspOutput.Properties.UrlSuffix=\"Suffixe d'URL\"\nRtspOutput.Properties.OutputAudio=\"Activer la sortie audio\"\nRtspOutput.Properties.Authentication=\"Authentification\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Nom d'utilisateur\"\nRtspOutput.Properties.Authentication.Password=\"Mot de passe\"\n\nReset=\"Réinitialiser\"\n\n"
  },
  {
    "path": "data/locale/it-IT.ini",
    "content": "RtspServer=\"RTSP Server\"\nRstpServer.Description=\"OBS RTSP Server Plugin\"\nRtspServer.Properties=\"RTSP Server\"\nRtspServer.Properties.StartOutput=\"Avvia\"\nRtspServer.Properties.StopOutput=\"Ferma\"\nRtspServer.Properties.Options=\"Opzioni\"\nRtspServer.Properties.Options.AutoStart=\"Avvio Automatico\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Tracce Audio: \"\nRtspServer.Properties.Target=\"Target\"\nRtspServer.Properties.Target.Multicast=\"Abilitato Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Copia\"\nRtspServer.Properties.Options.Output=\"Output Opzioni: \"\nRtspServer.Properties.Options.Output.Tip=\"Apri File->Impostazioni->Uscita->Dirette per cambiare le opzioni di uscita\"\nRtspServer.Properties.Authentication=\"Autenticazione\"\nRtspServer.Properties.Authentication.Enabled=\"Abilitato\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Nome utente: \"\nRtspServer.Properties.Authentication.Password=\"Parola d'ordine: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Opzionale)\"\nRtspServer.Properties.Status=\"Stato\"\nRtspServer.Properties.Status.TotalDataSent=\"Totale Dati in uscita: \"\nRtspServer.Properties.Status.Bitrate=\"Bitrate: \"\nRtspServer.Properties.Status.DroppedFrames=\"Dropped Frames:\"\nRtspServer.Properties.Version=\"Versione: \"\n\nRtspOutput=\"Uscita RTSP\"\nRtspOutput.Error.BeginDataCapture=\"impossibile iniziare l'acquisizione dei dati\"\nRtspOutput.Error.InitEncoders=\"inizializzare l'errore degli encoder\"\nRtspOutput.Error.StartRtspServer=\"avvio del server RTSP non riuscito sulla porta '%d'\"\nRtspOutput.Error.StartMulticast=\"l'avvio della trasmissione multicast è fallito\"\nRtspOutput.Error.Encode=\"errore di codifica\"\nRtspOutput.Error.Unknown=\"errore sconosciuto\"\nRtspOutput.Hotkey.StartOutput=\"Avvia Uscita\"\nRtspOutput.Hotkey.StopOutput=\"Ferma Uscita\"\nRtspOutput.Properties.Multicast=\"Abilitato Multicast\"\nRtspOutput.Properties.Port=\"Porta\"\nRtspOutput.Properties.UrlSuffix=\"Suffisso URL\"\nRtspOutput.Properties.OutputAudio=\"Abilita l'uscita audio\"\nRtspOutput.Properties.Authentication=\"Autenticazione\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Nome utente\"\nRtspOutput.Properties.Authentication.Password=\"Parola d'ordine\"\n\nReset=\"Ripristina\"\n\n"
  },
  {
    "path": "data/locale/ja-JP.ini",
    "content": "RtspServer=\"RTSPサーバー\"\nRstpServer.Description=\"OBS RTSPサーバープラグイン\"\nRtspServer.Properties=\"RTSPサーバー\"\nRtspServer.Properties.StartOutput=\"出力開始\"\nRtspServer.Properties.StopOutput=\"出力終了\"\nRtspServer.Properties.Options=\"オプション\"\nRtspServer.Properties.Options.AutoStart=\"自動起動\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"音声トラック: \"\nRtspServer.Properties.Target=\"サーバー\"\nRtspServer.Properties.Target.Multicast=\"マルチキャストを有効にする\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"コピー\"\nRtspServer.Properties.Options.Output=\"出力オプション: \"\nRtspServer.Properties.Options.Output.Tip=\"出力オプションを変更するには [ファイル]->[設定]->[出力]->[配信] を開きます。\"\nRtspServer.Properties.Authentication=\"認証\"\nRtspServer.Properties.Authentication.Enabled=\"認証を有効にする\"\nRtspServer.Properties.Authentication.Realm=\"レルム: \"\nRtspServer.Properties.Authentication.Username=\"ユーザー名: \"\nRtspServer.Properties.Authentication.Password=\"パスワード: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(オプション)\"\nRtspServer.Properties.Status=\"統計\"\nRtspServer.Properties.Status.TotalDataSent=\"出力データの合計: \"\nRtspServer.Properties.Status.Bitrate=\"ビットレート: \"\nRtspServer.Properties.Status.DroppedFrames=\"ドロップされたフレーム:\"\nRtspServer.Properties.Version=\"Version: \"\n\nRtspOutput=\"RTSP出力\"\nRtspOutput.Error.BeginDataCapture=\"データキャプチャを開始できません\"\nRtspOutput.Error.InitEncoders=\"エンコーダ初期化エラー\"\nRtspOutput.Error.StartRtspServer=\"ポート%dでのRTSPサーバーの起動に失敗しました\"\nRtspOutput.Error.StartMulticast=\"マルチキャストの開始に失敗しました\"\nRtspOutput.Error.Encode=\"エンコードエラー\"\nRtspOutput.Error.Unknown=\"不明なエラー\"\nRtspOutput.Hotkey.StartOutput=\"出力開始\"\nRtspOutput.Hotkey.StopOutput=\"出力終了\"\nRtspOutput.Properties.Multicast=\"マルチキャスト\"\nRtspOutput.Properties.Port=\"ポート\"\nRtspOutput.Properties.UrlSuffix=\"URLのサフィックス\"\nRtspOutput.Properties.OutputAudio=\"オーディオ出力を有効にする\"\nRtspOutput.Properties.Authentication=\"認証\"\nRtspOutput.Properties.Authentication.Realm=\"レルム\"\nRtspOutput.Properties.Authentication.Username=\"ユーザー名\"\nRtspOutput.Properties.Authentication.Password=\"パスワード\"\n\nReset=\"リセット\"\n\n"
  },
  {
    "path": "data/locale/ko-KR.ini",
    "content": "RtspServer=\"RTSP 서버\"\nRstpServer.Description=\"OBS RTSP 서버 플러그인\"\nRtspServer.Properties=\"RTSP 서버\"\nRtspServer.Properties.StartOutput=\"스타트\"\nRtspServer.Properties.StopOutput=\"중지\"\nRtspServer.Properties.Options=\"옵션\"\nRtspServer.Properties.Options.AutoStart=\"자동 실행\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"오디오 트랙: \"\nRtspServer.Properties.Target=\"표적\"\nRtspServer.Properties.Target.Multicast=\"활성화된 멀티캐스트\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"복사\"\nRtspServer.Properties.Options.Output=\"출력 옵션: \"\nRtspServer.Properties.Options.Output.Tip=\"[파일]->[설정]->[출력]->[방송] 을 열어 출력 옵션을 변경합니다.\"\nRtspServer.Properties.Authentication=\"인증\"\nRtspServer.Properties.Authentication.Enabled=\"활성화 됨\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"사용자 이름: \"\nRtspServer.Properties.Authentication.Password=\"비밀번호: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(옵션)\"\nRtspServer.Properties.Status=\"상태\"\nRtspServer.Properties.Status.TotalDataSent=\"총 데이터 출력: \"\nRtspServer.Properties.Status.Bitrate=\"비트 레이트: \"\nRtspServer.Properties.Status.DroppedFrames=\"드롭된 프레임:\"\nRtspServer.Properties.Version=\"버전: \"\n\nRtspOutput=\"RTSP 출력\"\nRtspOutput.Error.BeginDataCapture=\"데이터 캡처를 시작할 수 없습니다.\"\nRtspOutput.Error.InitEncoders=\"인코더 초기화 오류\"\nRtspOutput.Error.StartRtspServer=\"포트에서 RTSP 서버 시작 실패 '%d'\"\nRtspOutput.Error.StartMulticast=\"멀티캐스트 시작 실패\"\nRtspOutput.Error.Encode=\"인코딩 오류\"\nRtspOutput.Error.Unknown=\"알수없는 오류\"\nRtspOutput.Hotkey.StartOutput=\"시작 출력\"\nRtspOutput.Hotkey.StopOutput=\"출력 중지\"\nRtspOutput.Properties.Multicast=\"활성화된 멀티캐스트\"\nRtspOutput.Properties.Port=\"포트\"\nRtspOutput.Properties.UrlSuffix=\"URL 접미사\"\nRtspOutput.Properties.OutputAudio=\"오디오 출력 활성화\"\nRtspOutput.Properties.Authentication=\"인증\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"사용자 이름\"\nRtspOutput.Properties.Authentication.Password=\"비밀번호\"\n\nReset=\"초기화\"\n\n"
  },
  {
    "path": "data/locale/nl-NL.ini",
    "content": "RtspServer=\"RTSP Server\"\nRstpServer.Description=\"OBS RTSP Server Plugin\"\nRtspServer.Properties=\"RTSP Server\"\nRtspServer.Properties.StartOutput=\"Starten\"\nRtspServer.Properties.StopOutput=\"Stoppen\"\nRtspServer.Properties.Options=\"Opties\"\nRtspServer.Properties.Options.AutoStart=\"AutoStart\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Audiotracks: \"\nRtspServer.Properties.Target=\"Doelwit\"\nRtspServer.Properties.Target.Multicast=\"Ingeschakelde Multicast\"\nRtspServer.Properties.Target.Address=\"URL: \"\nRtspServer.Properties.Target.Address.Copy=\"Kopiëren\"\nRtspServer.Properties.Options.Output=\"Uitvoeropties: \"\nRtspServer.Properties.Options.Output.Tip=\"Als u de uitvoeropties wilt wijzigen, opent u: Bestand->Instellingen->Uitvoer->Streamen.\"\nRtspServer.Properties.Authentication=\"Authenticatie\"\nRtspServer.Properties.Authentication.Enabled=\"Enabled\"\nRtspServer.Properties.Authentication.Realm=\"Realm: \"\nRtspServer.Properties.Authentication.Username=\"Gebruikersnaam: \"\nRtspServer.Properties.Authentication.Password=\"Wachtwoord: \"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Optioneel)\"\nRtspServer.Properties.Status=\"Toestand\"\nRtspServer.Properties.Status.TotalDataSent=\"Totale gegevensoutput: \"\nRtspServer.Properties.Status.Bitrate=\"Bitsnelheid: \"\nRtspServer.Properties.Status.DroppedFrames=\"Dropped Frames:\"\nRtspServer.Properties.Version=\"Versie: \"\n\nRtspOutput=\"RTSP Uitvoer\"\nRtspOutput.Error.BeginDataCapture=\"Data-acquisitie kan niet worden gestart\"\nRtspOutput.Error.InitEncoders=\"initialiseer encoderfout\"\nRtspOutput.Error.StartRtspServer=\"RTSP-server kan niet worden gestart op poort '%d'\"\nRtspOutput.Error.StartMulticast=\"starten van multicast mislukt\"\nRtspOutput.Error.Encode=\"Coderingsfout\"\nRtspOutput.Error.Unknown=\"Onbekende fout\"\nRtspOutput.Hotkey.StartOutput=\"Starten\"\nRtspOutput.Hotkey.StopOutput=\"Stoppen\"\nRtspOutput.Properties.Multicast=\"Ingeschakelde Multicast\"\nRtspOutput.Properties.Port=\"Poort\"\nRtspOutput.Properties.UrlSuffix=\"Url-achtervoegsel\"\nRtspOutput.Properties.OutputAudio=\"Audio-uitvoer inschakelen\"\nRtspOutput.Properties.Authentication=\"Authenticatie\"\nRtspOutput.Properties.Authentication.Realm=\"Realm\"\nRtspOutput.Properties.Authentication.Username=\"Gebruikersnaam\"\nRtspOutput.Properties.Authentication.Password=\"Wachtwoord\"\n\nReset=\"Terugzetten\"\n\n"
  },
  {
    "path": "data/locale/ru-RU.ini",
    "content": "RtspServer=\"RTSP Сервер\"\nRstpServer.Description=\"OBS RTSP Сервер Плагин\"\nRtspServer.Properties=\"RTSP Сервер\"\nRtspServer.Properties.StartOutput=\"Начать\"\nRtspServer.Properties.StopOutput=\"Остановить\"\nRtspServer.Properties.Options=\"Опции\"\nRtspServer.Properties.Options.AutoStart=\"Автоматический запуск\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"Аудио дорожки:\"\nRtspServer.Properties.Target=\"Цель\"\nRtspServer.Properties.Target.Multicast=\"Включен многоадресный режим\"\nRtspServer.Properties.Target.Address=\"URL:\"\nRtspServer.Properties.Target.Address.Copy=\"Копировать\"\nRtspServer.Properties.Options.Output=\"Варианты вывода:\"\nRtspServer.Properties.Options.Output.Tip=\"Откройте Файл->Настройки->Вывод->Потоковая передача, чтобы изменить параметры вывода.\"\nRtspServer.Properties.Authentication=\"Аутентификация\"\nRtspServer.Properties.Authentication.Enabled=\"Включено\"\nRtspServer.Properties.Authentication.Realm=\"Царство:\"\nRtspServer.Properties.Authentication.Username=\"Имя пользователя:\"\nRtspServer.Properties.Authentication.Password=\"Пароль:\"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"(Опционально)\"\nRtspServer.Properties.Status=\"Статус\"\nRtspServer.Properties.Status.TotalDataSent=\"Общий объем данных:\"\nRtspServer.Properties.Status.Bitrate=\"Битрейт:\"\nRtspServer.Properties.Status.DroppedFrames=\"Сброшенные кадры:\"\nRtspServer.Properties.Version=\"Версия:\"\n\nRtspOutput=\"RTSP Выход\"\nRtspOutput.Error.BeginDataCapture=\"не могу начать захват данных\"\nRtspOutput.Error.InitEncoders=\"Ошибка инициализации кодировщиков\"\nRtspOutput.Error.StartRtspServer=\"запуск сервера RTSP не удался на порту '%d'\"\nRtspOutput.Error.StartMulticast=\"начало многоадресной передачи не удалось\"\nRtspOutput.Error.Encode=\"ошибка кодирования\"\nRtspOutput.Error.Unknown=\"неизвестная ошибка\"\nRtspOutput.Hotkey.StartOutput=\"Начало вывода\"\nRtspOutput.Hotkey.StopOutput=\"Остановить вывод\"\nRtspOutput.Properties.Multicast=\"Включен многоадресный режим\"\nRtspOutput.Properties.Port=\"Порт\"\nRtspOutput.Properties.UrlSuffix=\"URL суффикс\"\nRtspOutput.Properties.OutputAudio=\"Включить аудиовыход\"\nRtspOutput.Properties.Authentication=\"Аутентификация\"\nRtspOutput.Properties.Authentication.Realm=\"Царство\"\nRtspOutput.Properties.Authentication.Username=\"Имя пользователя\"\nRtspOutput.Properties.Authentication.Password=\"Пароль\"\n\nReset=\"Сбросить\"\n\n"
  },
  {
    "path": "data/locale/zh-CN.ini",
    "content": "RtspServer=\"RTSP 服务器\"\nRstpServer.Description=\"OBS RTSP 服务器插件\"\nRtspServer.Properties=\"RTSP 服务器\"\nRtspServer.Properties.StartOutput=\"启动\"\nRtspServer.Properties.StopOutput=\"停止\"\nRtspServer.Properties.Options=\"选项\"\nRtspServer.Properties.Options.AutoStart=\"自动启动\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"音轨：\"\nRtspServer.Properties.Target=\"目标\"\nRtspServer.Properties.Target.Multicast=\"启用组播\"\nRtspServer.Properties.Target.Address=\"URL：\"\nRtspServer.Properties.Target.Address.Copy=\"复制\"\nRtspServer.Properties.Options.Output=\"输出选项：\"\nRtspServer.Properties.Options.Output.Tip=\"打开 文件->设置->输出->串流 以更改输出选项。\"\nRtspServer.Properties.Authentication=\"身份认证\"\nRtspServer.Properties.Authentication.Enabled=\"启用\"\nRtspServer.Properties.Authentication.Realm=\"领域：\"\nRtspServer.Properties.Authentication.Username=\"用户名：\"\nRtspServer.Properties.Authentication.Password=\"密码：\"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"（可选）\"\nRtspServer.Properties.Status=\"状态\"\nRtspServer.Properties.Status.TotalDataSent=\"总数据输出：\"\nRtspServer.Properties.Status.Bitrate=\"比特率：\"\nRtspServer.Properties.Status.DroppedFrames=\"丢弃的帧：\"\nRtspServer.Properties.Version=\"版本：\"\n\nRtspOutput=\"RTSP 输出\"\nRtspOutput.Error.BeginDataCapture=\"无法开始数据捕获\"\nRtspOutput.Error.InitEncoders=\"初始化编码器时发生错误\"\nRtspOutput.Error.StartRtspServer=\"在端口“%d”上启动RTSP服务器失败\"\nRtspOutput.Error.StartMulticast=\"组播启动失败\"\nRtspOutput.Error.Encode=\"编码错误\"\nRtspOutput.Error.Unknown=\"未知错误\"\nRtspOutput.Hotkey.StartOutput=\"启动输出\"\nRtspOutput.Hotkey.StopOutput=\"停止输出\"\nRtspOutput.Properties.Multicast=\"启用组播\"\nRtspOutput.Properties.Port=\"端口\"\nRtspOutput.Properties.UrlSuffix=\"URL 后缀\"\nRtspOutput.Properties.OutputAudio=\"启用音频输出\"\nRtspOutput.Properties.Authentication=\"身份认证\"\nRtspOutput.Properties.Authentication.Realm=\"领域\"\nRtspOutput.Properties.Authentication.Username=\"用户名\"\nRtspOutput.Properties.Authentication.Password=\"密码\"\n\nReset=\"重置\"\n\n"
  },
  {
    "path": "data/locale/zh-TW.ini",
    "content": "RtspServer=\"RTSP 伺服器\"\nRstpServer.Description=\"OBS RTSP 伺服器外掛程式\"\nRtspServer.Properties=\"RTSP 伺服器\"\nRtspServer.Properties.StartOutput=\"開始\"\nRtspServer.Properties.StopOutput=\"停止\"\nRtspServer.Properties.Options=\"選項\"\nRtspServer.Properties.Options.AutoStart=\"自動開始\"\nRtspServer.Properties.Options.EnabledAudioTracks=\"音軌：\"\nRtspServer.Properties.Target=\"目標\"\nRtspServer.Properties.Target.Multicast=\"啟用組播\"\nRtspServer.Properties.Target.Address=\"URL：\"\nRtspServer.Properties.Target.Address.Copy=\"拷貝\"\nRtspServer.Properties.Options.Output=\"輸出選項：\"\nRtspServer.Properties.Options.Output.Tip=\"打開 檔案->設定->輸出->串流 以更改輸出選項。\"\nRtspServer.Properties.Authentication=\"身份驗證\"\nRtspServer.Properties.Authentication.Enabled=\"啟用\"\nRtspServer.Properties.Authentication.Realm=\"領域：\"\nRtspServer.Properties.Authentication.Username=\"用戶名：\"\nRtspServer.Properties.Authentication.Password=\"密碼：\"\nRtspServer.Properties.Authentication.PasswordPlaceholder=\"（可選）\"\nRtspServer.Properties.Status=\"狀態\"\nRtspServer.Properties.Status.TotalDataSent=\"總數據輸出：\"\nRtspServer.Properties.Status.Bitrate=\"位元速率：\"\nRtspServer.Properties.Status.DroppedFrames=\"落幀數：\"\nRtspServer.Properties.Version=\"版本：\"\n\nRtspOutput=\"RTSP 輸出\"\nRtspOutput.Error.BeginDataCapture=\"無法開始數據捕獲\"\nRtspOutput.Error.InitEncoders=\"初始化編碼器錯誤\"\nRtspOutput.Error.StartRtspServer=\"連接埠 %d 上啟動RTSP伺服器失敗\"\nRtspOutput.Error.StartMulticast=\"組播啟動失敗\"\nRtspOutput.Error.Encode=\"編碼錯誤\"\nRtspOutput.Error.Unknown=\"未知錯誤\"\nRtspOutput.Hotkey.StartOutput=\"開始輸出\"\nRtspOutput.Hotkey.StopOutput=\"停止輸出\"\nRtspOutput.Properties.Multicast=\"啟用組播\"\nRtspOutput.Properties.Port=\"連接埠\"\nRtspOutput.Properties.UrlSuffix=\"URL 後綴\"\nRtspOutput.Properties.OutputAudio=\"啟用音訊輸出\"\nRtspOutput.Properties.Authentication=\"身份驗證\"\nRtspOutput.Properties.Authentication.Realm=\"領域\"\nRtspOutput.Properties.Authentication.Username=\"用戶名\"\nRtspOutput.Properties.Authentication.Password=\"密碼\"\n\nReset=\"重置\"\n\n"
  },
  {
    "path": "external/BuildHelper.cmake",
    "content": "set(OBS_PLUGIN_OBS_SOURCE_DIR ${OBS_SOURCE_DIR})\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${CMAKE_CURRENT_SOURCE_DIR}/external\")\n\nset(CMAKE_INSTALL_PREFIX \"${CMAKE_SOURCE_DIR}/release\")\nset(CMAKE_PREFIX_PATH \"${CMAKE_PREFIX_PATH};${QTDIR};${DepsPath}\")\n\nset(CPACK_PACKAGE_FILE_NAME \"${CMAKE_PROJECT_NAME}-${OBS_PLUGUN_GIT_TAG}-linux\")\nset(CPACK_PACKAGING_INSTALL_PREFIX \"/usr\")\n#set(CPACK_SOURCE_PACKAGE_FILE_NAME \"${OBS_PLUGIN_PACKAGE_FILE_NAME}\")\nset(MACOSX_PLUGIN_GUI_IDENTIFIER \"${MACOS_BUNDLEID}\")\nset(MACOSX_PLUGIN_BUNDLE_VERSION \"${OBS_PLUGUN_LONG_VERSION}\")\nset(MACOSX_PLUGIN_SHORT_VERSION_STRING \"${OBS_PLUGUN_VERSION}\")\n\nfind_package(libobs REQUIRED)\n#add_library(OBS::libobs STATIC IMPORTED GLOBAL)\n#set_target_properties(OBS::libobs PROPERTIES\n#    IMPORTED_LOCATION \"${LIBOBS_LIB}\"\n#    )\nadd_library(OBS::libobs STATIC IMPORTED GLOBAL)\nif (LIBOBS_LIB MATCHES \"/([^/]+)\\\\.framework$\")\n    set(_libobs_fw \"${LIBOBS_LIB}/${CMAKE_MATCH_1}\")\n    if(EXISTS \"${_libobs_fw}.tbd\")\n        string(APPEND _libobs_fw \".tbd\")\n    endif()\n    message(\"${_libobs_fw}\")\n    set_target_properties(OBS::libobs PROPERTIES\n        IMPORTED_LOCATION \"${_libobs_fw}\"\n    )\nelse()\n    set_target_properties(OBS::libobs PROPERTIES\n        IMPORTED_LOCATION \"${LIBOBS_LIB}\"\n    )\nendif()\nadd_library(libobs ALIAS OBS::libobs)\n\nfind_package(obs-frontend-api REQUIRED)\nadd_library(OBS::obs-frontend-api STATIC IMPORTED GLOBAL)\nset_target_properties(OBS::obs-frontend-api PROPERTIES\n    IMPORTED_LOCATION \"${OBS_FRONTEND_API_LIB}\"\n    )\nadd_library(obs-frontend-api ALIAS OBS::obs-frontend-api)\n\ninclude(\"${CMAKE_CURRENT_SOURCE_DIR}/external/ObsPluginHelpers.cmake\")\n\nif(OS_WINDOWS)\n    if(MSVC)\n        target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE /W3)\n    endif()\nelseif(OS_MACOS)\n    configure_file(\n\t\t${CMAKE_SOURCE_DIR}/bundle/installer-macos.pkgproj.in\n\t\t${CMAKE_SOURCE_DIR}/bundle/installer-macos.generated.pkgproj)\n\n    target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall)\nelse()\n    target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wall)\nendif()\n"
  },
  {
    "path": "external/Findlibobs.cmake",
    "content": "# This module can be copied and used by external plugins for OBS\n#\n# Once done these will be defined:\n#\n#  LIBOBS_FOUND\n#  LIBOBS_INCLUDE_DIRS\n#  LIBOBS_LIBRARIES\n\nif(${CMAKE_SYSTEM_NAME} STREQUAL \"Darwin\")\n  set(OS_MACOS ON)\n  set(OS_POSIX ON)\nelseif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux|FreeBSD|OpenBSD\")\n  set(OS_POSIX ON)\n  string(TOUPPER \"${CMAKE_SYSTEM_NAME}\" _SYSTEM_NAME_U)\n  set(OS_${_SYSTEM_NAME_U} ON)\nelseif(${CMAKE_SYSTEM_NAME} STREQUAL \"Windows\")\n  set(OS_WINDOWS ON)\n  set(OS_POSIX OFF)\nendif()\n\nfind_package(PkgConfig QUIET)\nif (PKG_CONFIG_FOUND)\n\tpkg_check_modules(_OBS QUIET obs libobs)\nendif()\n\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n\tset(_lib_suffix 64)\nelse()\n\tset(_lib_suffix 32)\nendif()\n\nif(DEFINED CMAKE_BUILD_TYPE)\n\tif(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n\t\tset(_build_type_base \"debug\")\n\telse()\n\t\tset(_build_type_base \"release\")\n\tendif()\nendif()\n\nfind_path(LIBOBS_INCLUDE_DIR\n\tNAMES obs.h\n\tHINTS\n\t\tENV OBS_SOURCE_DIR${_lib_suffix}\n\t\tENV OBS_SOURCE_DIR\n\t\t${OBS_SOURCE_DIR}\n\tPATHS\n\t\t/usr/include /usr/local/include /opt/local/include /sw/include\n\tPATH_SUFFIXES\n\t\tlibobs\n\t)\n\nfunction(find_obs_lib base_name repo_build_path lib_name)\n\tstring(TOUPPER \"${base_name}\" base_name_u)\n\n\tif(DEFINED _build_type_base)\n\t\tset(_build_type_${repo_build_path} \"${_build_type_base}/${repo_build_path}\")\n\t\tset(_build_type_${repo_build_path}${_lib_suffix} \"${_build_type_base}${_lib_suffix}/${repo_build_path}\")\n\tendif()\n\n\tif(OS_MACOS)\n      set(_find_library_names \n\t\t${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib)\n\telse()\n\t  set(_find_library_names \n\t    ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name})\n    endif()\n\tfind_library(${base_name_u}_LIB\n\t\tNAMES ${_find_library_names}\n\t\tHINTS\n\t\t\tENV OBS_SOURCE_DIR${_lib_suffix}\n\t\t\tENV OBS_SOURCE_DIR\n\t\t\t${OBS_SOURCE_DIR}\n\t\t\t${_${base_name_u}_LIBRARY_DIRS}\n\t\tPATHS\n\t\t\t/usr/lib /usr/local/lib /opt/local/lib /sw/lib\n\t\tPATH_SUFFIXES\n\t\t\tlib${_lib_suffix} lib\n\t\t\tlibs${_lib_suffix} libs\n\t\t\tbin${_lib_suffix} bin\n\t\t\t../lib${_lib_suffix} ../lib\n\t\t\t../libs${_lib_suffix} ../libs\n\t\t\t../bin${_lib_suffix} ../bin\n\t\t\t# base repo non-msvc-specific search paths\n\t\t\t${_build_type_${repo_build_path}}\n\t\t\t${_build_type_${repo_build_path}${_lib_suffix}}\n\t\t\tplugin_build/${repo_build_path}\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}\n\t\t\t# base repo msvc-specific search paths on windows\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}/Debug\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}/RelWithDebInfo\n\t\t\tplugin_build/${repo_build_path}/Debug\n\t\t\tplugin_build/${repo_build_path}/RelWithDebInfo\n\t\t)\nendfunction()\n\nfind_obs_lib(LIBOBS libobs obs)\n\nif(MSVC)\n\tfind_obs_lib(W32_PTHREADS deps/w32-pthreads w32-pthreads)\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(libobs DEFAULT_MSG LIBOBS_LIB LIBOBS_INCLUDE_DIR)\nmark_as_advanced(LIBOBS_INCLUDE_DIR LIBOBS_LIB)\n\nif(LIBOBS_FOUND)\n\tif(MSVC)\n\t\tif (NOT DEFINED W32_PTHREADS_LIB)\n\t\t\tmessage(FATAL_ERROR \"Could not find the w32-pthreads library\" )\n\t\tendif()\n\n\t\tset(W32_PTHREADS_INCLUDE_DIR ${LIBOBS_INCLUDE_DIR}/../deps/w32-pthreads)\n\tendif()\n\n\tset(LIBOBS_INCLUDE_DIRS ${LIBOBS_INCLUDE_DIR} ${W32_PTHREADS_INCLUDE_DIR})\n\tset(LIBOBS_LIBRARIES ${LIBOBS_LIB} ${W32_PTHREADS_LIB})\n\t# include(${LIBOBS_INCLUDE_DIR}/../cmake/external/ObsPluginHelpers.cmake)\n\n\t# allows external plugins to easily use/share common dependencies that are often included with libobs (such as FFmpeg)\n\tif(NOT DEFINED INCLUDED_LIBOBS_CMAKE_MODULES)\n\t\tset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${LIBOBS_INCLUDE_DIR}/../cmake/Modules/\")\n\t\tset(INCLUDED_LIBOBS_CMAKE_MODULES true)\n\tendif()\nelse()\n\tmessage(FATAL_ERROR \"Could not find the libobs library\" )\nendif()\n"
  },
  {
    "path": "external/Findobs-frontend-api.cmake",
    "content": "# This module can be copied and used by external plugins for OBS\n#\n# Once done these will be defined:\n#\n#  OBS_FRONTEND_API_FOUND\n#  OBS_FRONTEND_API_INCLUDE_DIRS\n#  OBS_FRONTEND_API_LIBRARIES\n\nif(${CMAKE_SYSTEM_NAME} STREQUAL \"Darwin\")\n  set(OS_MACOS ON)\n  set(OS_POSIX ON)\nelseif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux|FreeBSD|OpenBSD\")\n  set(OS_POSIX ON)\n  string(TOUPPER \"${CMAKE_SYSTEM_NAME}\" _SYSTEM_NAME_U)\n  set(OS_${_SYSTEM_NAME_U} ON)\nelseif(${CMAKE_SYSTEM_NAME} STREQUAL \"Windows\")\n  set(OS_WINDOWS ON)\n  set(OS_POSIX OFF)\nendif()\n\nfind_package(PkgConfig QUIET)\nif (PKG_CONFIG_FOUND)\n\tpkg_check_modules(_OBS QUIET obs obs-frontend-api)\nendif()\n\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n\tset(_lib_suffix 64)\nelse()\n\tset(_lib_suffix 32)\nendif()\n\nif(DEFINED CMAKE_BUILD_TYPE)\n\tif(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n\t\tset(_build_type_base \"debug\")\n\telse()\n\t\tset(_build_type_base \"release\")\n\tendif()\nendif()\n\nfind_path(OBS_FRONTEND_API_INCLUDE_DIR\n\tNAMES obs-frontend-api.h\n\tHINTS\n\t\tENV OBS_SOURCE_DIR${_lib_suffix}\n\t\tENV OBS_SOURCE_DIR\n\t\t${OBS_SOURCE_DIR}\n\tPATHS\n\t\t/usr/include /usr/local/include /opt/local/include /sw/include\n\tPATH_SUFFIXES\n        UI/obs-frontend-api\n\t)\n\nfunction(find_obs_lib base_name repo_build_path lib_name)\n\tstring(TOUPPER \"${base_name}\" base_name_u)\n\n\tif(DEFINED _build_type_base)\n\t\tset(_build_type_${repo_build_path} \"${_build_type_base}/${repo_build_path}\")\n\t\tset(_build_type_${repo_build_path}${_lib_suffix} \"${_build_type_base}${_lib_suffix}/${repo_build_path}\")\n\tendif()\n\n\tif(OS_MACOS)\n      set(_find_library_names \n\t\t${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib)\n\telse()\n\t  set(_find_library_names \n\t    ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name})\n    endif()\n\tfind_library(${base_name_u}_LIB\n\t\tNAMES ${_find_library_names}\n\t\tHINTS\n\t\t\tENV OBS_SOURCE_DIR${_lib_suffix}\n\t\t\tENV OBS_SOURCE_DIR\n\t\t\t${OBS_SOURCE_DIR}\n\t\t\t${_${base_name_u}_LIBRARY_DIRS}\n\t\tPATHS\n\t\t\t/usr/lib /usr/local/lib /opt/local/lib /sw/lib\n\t\tPATH_SUFFIXES\n\t\t\tlib${_lib_suffix} lib\n\t\t\tlibs${_lib_suffix} libs\n\t\t\tbin${_lib_suffix} bin\n\t\t\t../lib${_lib_suffix} ../lib\n\t\t\t../libs${_lib_suffix} ../libs\n\t\t\t../bin${_lib_suffix} ../bin\n\t\t\t# base repo non-msvc-specific search paths\n\t\t\t${_build_type_${repo_build_path}}\n\t\t\t${_build_type_${repo_build_path}${_lib_suffix}}\n\t\t\tplugin_build/${repo_build_path}\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}\n\t\t\t# base repo msvc-specific search paths on windows\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}/Debug\n\t\t\tplugin_build${_lib_suffix}/${repo_build_path}/RelWithDebInfo\n\t\t\tplugin_build/${repo_build_path}/Debug\n\t\t\tplugin_build/${repo_build_path}/RelWithDebInfo\n\t\t)\nendfunction()\n\nfind_obs_lib(OBS_FRONTEND_API UI/obs-frontend-api obs-frontend-api)\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(obs-frontend-api DEFAULT_MSG OBS_FRONTEND_API_LIB OBS_FRONTEND_API_INCLUDE_DIR)\nmark_as_advanced(OBS_FRONTEND_API_INCLUDE_DIR OBS_FRONTEND_API_LIB)\n\nif(OBS-FRONTEND-API_FOUND)\n\tset(OBS_FRONTEND_API_INCLUDE_DIRS ${OBS_FRONTEND_API_INCLUDE_DIR})\n\tset(OBS_FRONTEND_API_LIBRARIES ${OBS_FRONTEND_API_LIB})\n\t# include(${OBS_FRONTEND_API_INCLUDE_DIR}/../../cmake/external/ObsPluginHelpers.cmake)\n\n\t# allows external plugins to easily use/share common dependencies that are often included with obs-frontend-api (such as FFmpeg)\n\tif(NOT DEFINED INCLUDED_OBS_FRONTEND_API_CMAKE_MODULES)\n\t\tset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${OBS_FRONTEND_API_INCLUDE_DIR}/../../cmake/Modules/\")\n\t\tset(INCLUDED_OBS_FRONTEND_API_CMAKE_MODULES true)\n\tendif()\nelse()\n\tmessage(FATAL_ERROR \"Could not find the obs-frontend-api library\" )\nendif()\n"
  },
  {
    "path": "external/GitInfoHelper.cmake",
    "content": "\n\nfunction(get_git_version git_tag_name git_tag_version_name git_tag_short_version_name git_tag_long_version_name)\n\texecute_process(COMMAND git describe --tags --always --dirty=-dev\n\t\tWORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n\t\tTIMEOUT 10\n\t\tOUTPUT_VARIABLE git_tag\n\t\tOUTPUT_STRIP_TRAILING_WHITESPACE)\n\n\texecute_process(COMMAND git rev-list HEAD --count\n\t\tWORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n\t\tTIMEOUT 10\n\t\tOUTPUT_VARIABLE git_rev_list_count\n\t\tOUTPUT_STRIP_TRAILING_WHITESPACE)\n    \n\tstring(REGEX MATCH \"[0-9]+.[0-9]+.[0-9]+(-[a-z0-9]+)*$\" git_tag_version \"${git_tag}\")\n\tstring(REGEX MATCH \"^[0-9]+.[0-9]+.[0-9]+\"  git_tag_short_version \"${git_tag_version}\")\n\n\t#if(\"${git_tag_version}\" MATCHES \"-[0-9]+-g\")\n\t#\tstring(REGEX MATCH \"-[0-9]+-g\"  _git_tag_tweak_version_temp \"${git_tag_version}\")\n\t#\tstring(REGEX MATCH \"[0-9]+\"  _git_tag_tweak_version \"${_git_tag_tweak_version_temp}\")\n\t#else()\n\t#\tset(_git_tag_tweak_version \"0\")\n\t#endif()\n\n\tif(\"${git_rev_list_count}\" MATCHES \"^[0-9]+$\")\n\t\tset(_git_tag_tweak_version \"${git_rev_list_count}\")\n    else()\n\t\tset(_git_tag_tweak_version \"0\")\n\tendif()\n\n\tif(\"${git_tag_short_version}\" STREQUAL \"\")\n\t\tset(git_tag_long_version \"0.0.1.${_git_tag_tweak_version}\")\n\telse()\n\t\tset(git_tag_long_version \"${git_tag_short_version}.${_git_tag_tweak_version}\")\n\tendif()\n\n\tset(${git_tag_name} \"${git_tag}\" PARENT_SCOPE)\n\tset(${git_tag_version_name} \"${git_tag_version}\" PARENT_SCOPE)\n\tset(${git_tag_short_version_name} \"${git_tag_short_version}\" PARENT_SCOPE)\n\tset(${git_tag_long_version_name} \"${git_tag_long_version}\" PARENT_SCOPE)\n\n\tmessage(\"Git Tag:\\t${git_tag}\")\n\tmessage(\"Git Tag Version:\\t${git_tag_version}\")\n\tmessage(\"Git Tag Short Version:\\t${git_tag_short_version}\")\n\tmessage(\"Git Tag Long Version:\\t${git_tag_long_version}\")\nendfunction(get_git_version)\n"
  },
  {
    "path": "external/ObsPluginHelpers.cmake",
    "content": "if(POLICY CMP0087)\n  cmake_policy(SET CMP0087 NEW)\nendif()\n\nset(OBS_STANDALONE_PLUGIN_DIR ${CMAKE_SOURCE_DIR}/release)\n\ninclude(GNUInstallDirs)\nif(${CMAKE_SYSTEM_NAME} STREQUAL \"Darwin\")\n  set(OS_MACOS ON)\n  set(OS_POSIX ON)\nelseif(${CMAKE_SYSTEM_NAME} MATCHES \"Linux|FreeBSD|OpenBSD\")\n  set(OS_POSIX ON)\n  string(TOUPPER \"${CMAKE_SYSTEM_NAME}\" _SYSTEM_NAME_U)\n  set(OS_${_SYSTEM_NAME_U} ON)\nelseif(${CMAKE_SYSTEM_NAME} STREQUAL \"Windows\")\n  set(OS_WINDOWS ON)\n  set(OS_POSIX OFF)\nendif()\n\n# Old-Style plugin detected, find \"modern\" libobs variant instead and set global include directories\n# to fix \"bad\" plugin behavior\nif(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs)\n  message(\n    DEPRECATION\n      \"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.\"\n  )\n  find_package(libobs REQUIRED)\n  if(TARGET OBS::libobs)\n    set_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE)\n    message(STATUS \"OBS: Using modern libobs target\")\n\n    add_library(libobs ALIAS OBS::libobs)\n    if(OS_WINDOWS)\n      add_library(w32-pthreads ALIAS OBS::w32-pthreads)\n    endif()\n  endif()\nendif()\n\n# Set macOS and Windows specific if default value is used\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS))\n  set(CMAKE_INSTALL_PREFIX\n      ${OBS_STANDALONE_PLUGIN_DIR}\n      CACHE STRING \"Directory to install OBS plugin after building\" FORCE)\nendif()\n\n# Set default build type to RelWithDebInfo and specify allowed alternative values\nif(NOT CMAKE_BUILD_TYPE)\n  set(CMAKE_BUILD_TYPE\n      \"RelWithDebInfo\"\n      CACHE STRING \"OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]\" FORCE)\n  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel)\nendif()\n\n# Set default Qt version to AUTO, preferring an available Qt6 with a fallback to Qt5\nif(NOT QT_VERSION)\n  set(QT_VERSION\n      AUTO\n      CACHE STRING \"OBS Qt version [AUTO, 6, 5]\" FORCE)\n  set_property(CACHE QT_VERSION PROPERTY STRINGS AUTO 6 5)\nendif()\n\n# Macro to find best possible Qt version for use with the project:\n#\n# * Use QT_VERSION value as a hint for desired Qt version\n# * If \"AUTO\" was specified, prefer Qt6 over Qt5\n# * Creates versionless targets of desired component if none had been created by Qt itself (Qt\n#   versions < 5.15)\n#\nmacro(find_qt)\n  set(multiValueArgs COMPONENTS COMPONENTS_WIN COMPONENTS_MAC COMPONENTS_LINUX)\n  cmake_parse_arguments(FIND_QT \"\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN})\n\n  # Do not use versionless targets in the first step to avoid Qt::Core being clobbered by later\n  # opportunistic find_package runs\n  set(QT_NO_CREATE_VERSIONLESS_TARGETS ON)\n\n  # Loop until _QT_VERSION is set or FATAL_ERROR aborts script execution early\n  while(NOT _QT_VERSION)\n    if(QT_VERSION STREQUAL AUTO AND NOT _QT_TEST_VERSION)\n      set(_QT_TEST_VERSION 6)\n    elseif(NOT QT_VERSION STREQUAL AUTO)\n      set(_QT_TEST_VERSION ${QT_VERSION})\n    endif()\n\n    find_package(\n      Qt${_QT_TEST_VERSION}\n      COMPONENTS Core\n      QUIET)\n\n    if(TARGET Qt${_QT_TEST_VERSION}::Core)\n      set(_QT_VERSION\n          ${_QT_TEST_VERSION}\n          CACHE INTERNAL \"\")\n      message(STATUS \"Qt version found: ${_QT_VERSION}\")\n      unset(_QT_TEST_VERSION)\n      break()\n    elseif(QT_VERSION STREQUAL AUTO)\n      if(_QT_TEST_VERSION EQUAL 6)\n        message(WARNING \"Qt6 was not found, falling back to Qt5\")\n        set(_QT_TEST_VERSION 5)\n        continue()\n      endif()\n    endif()\n    message(FATAL_ERROR \"Neither Qt6 nor Qt5 found.\")\n  endwhile()\n\n  # Enable versionless targets for the remaining Qt components\n  set(QT_NO_CREATE_VERSIONLESS_TARGETS OFF)\n\n  set(_QT_COMPONENTS ${FIND_QT_COMPONENTS})\n  if(OS_WINDOWS)\n    list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_WIN})\n  elseif(OS_MACOS)\n    list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_MAC})\n  else()\n    list(APPEND _QT_COMPONENTS ${FIND_QT_COMPONENTS_LINUX})\n  endif()\n\n  find_package(\n    Qt${_QT_VERSION}\n    COMPONENTS ${_QT_COMPONENTS}\n    REQUIRED)\n\n  list(APPEND _QT_COMPONENTS Core)\n\n  if(\"Gui\" IN_LIST FIND_QT_COMPONENTS_LINUX)\n    list(APPEND _QT_COMPONENTS \"GuiPrivate\")\n  endif()\n\n  # Check for versionless targets of each requested component and create if necessary\n  foreach(_COMPONENT IN LISTS _QT_COMPONENTS)\n    if(NOT TARGET Qt::${_COMPONENT} AND TARGET Qt${_QT_VERSION}::${_COMPONENT})\n      add_library(Qt::${_COMPONENT} INTERFACE IMPORTED)\n      set_target_properties(Qt::${_COMPONENT} PROPERTIES INTERFACE_LINK_LIBRARIES\n                                                         Qt${_QT_VERSION}::${_COMPONENT})\n    endif()\n  endforeach()\nendmacro()\n\n# Set relative path variables for file configurations\nfile(RELATIVE_PATH RELATIVE_INSTALL_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_INSTALL_PREFIX})\nfile(RELATIVE_PATH RELATIVE_BUILD_PATH ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})\n\nif(OS_POSIX)\n  # Set default GCC/clang compile options:\n  #\n  # * Treat warnings as errors\n  # * Enable extra warnings, https://clang.llvm.org/docs/DiagnosticsReference.html#wextra\n  # * Warning about usage of variable length array,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wvla\n  # * Warning about bad format specifiers,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wformat\n  # * Warning about non-strings used as format strings,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wformat-security\n  # * Warning about non-exhaustive switch blocks,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wswitch\n  # * Warning about unused parameters,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wunused-parameter\n  # * DISABLE warning about unused functions,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wunused-function\n  # * DISABLE warning about missing field initializers,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-field-initializers\n  # * DISABLE strict aliasing optimisations\n  # * C ONLY - treat implicit function declarations (use before declare) as errors,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wimplicit-function-declaration\n  # * C ONLY - DISABLE warning about missing braces around subobject initalizers,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wmissing-braces\n  # * C ONLY, Clang ONLY - Warning about implicit conversion of NULL to another type,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wnull-conversion\n  # * C & C++, Clang ONLY - Disable warning about integer conversion losing precision,\n  #   https://clang.llvm.org/docs/DiagnosticsReference.html#wshorten-64-to-32\n  # * C++, GCC ONLY - Warning about implicit conversion of NULL to another type\n  # * Enable color diagnostics on Clang (CMAKE_COLOR_DIAGNOSTICS available in CMake 3.24)\n  target_compile_options(\n    ${CMAKE_PROJECT_NAME}\n    PRIVATE\n      -Werror\n      -Wextra\n      -Wvla\n      -Wformat\n      -Wformat-security\n      -Wswitch\n      -Wunused-parameter\n      -Wno-unused-function\n      -Wno-missing-field-initializers\n      -fno-strict-aliasing\n      \"$<$<COMPILE_LANGUAGE:C>:-Werror-implicit-function-declaration;-Wno-missing-braces>\"\n      \"$<$<COMPILE_LANG_AND_ID:C,AppleClang,Clang>:-Wnull-conversion;-Wno-error=shorten-64-to-32;-fcolor-diagnostics>\"\n      \"$<$<COMPILE_LANG_AND_ID:CXX,AppleClang,Clang>:-Wnull-conversion;-Wno-error=shorten-64-to-32;-fcolor-diagnostics>\"\n      \"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Wconversion-null>\"\n      \"$<$<CONFIG:DEBUG>:-DDEBUG=1;-D_DEBUG=1>\")\n\n  # GCC 12.1.0 has a regression bug which trigger maybe-uninitialized warnings where there is not.\n  # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562)\n  if(CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" AND CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL \"12.1.0\")\n    target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE -Wno-error=maybe-uninitialized)\n  endif()\n\n  if(NOT CCACHE_SET)\n    # Try to find and enable ccache\n    find_program(CCACHE_PROGRAM \"ccache\")\n    set(CCACHE_SUPPORT\n        ON\n        CACHE BOOL \"Enable ccache support\")\n    mark_as_advanced(CCACHE_PROGRAM)\n    if(CCACHE_PROGRAM AND CCACHE_SUPPORT)\n      set(CMAKE_CXX_COMPILER_LAUNCHER\n          ${CCACHE_PROGRAM}\n          CACHE INTERNAL \"\")\n      set(CMAKE_C_COMPILER_LAUNCHER\n          ${CCACHE_PROGRAM}\n          CACHE INTERNAL \"\")\n      set(CMAKE_OBJC_COMPILER_LAUNCHER\n          ${CCACHE_PROGRAM}\n          CACHE INTERNAL \"\")\n      set(CMAKE_OBJCXX_COMPILER_LAUNCHER\n          ${CCACHE_PROGRAM}\n          CACHE INTERNAL \"\")\n      set(CMAKE_CUDA_COMPILER_LAUNCHER\n          ${CCACHE_PROGRAM}\n          CACHE INTERNAL \"\") # CMake 3.9+\n      set(CCACHE_SET\n          ON\n          CACHE INTERNAL \"\")\n    endif()\n  endif()\nendif()\n\n# Set required C++ standard to C++17\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\n\n# Get lowercase host architecture for easier comparison\nif(MSVC_CXX_ARCHITECTURE_ID)\n  string(TOLOWER ${MSVC_CXX_ARCHITECTURE_ID} _HOST_ARCH)\nelse()\n  string(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} _HOST_ARCH)\nendif()\n\nif(_HOST_ARCH MATCHES \"i[3-6]86|x86|x64|x86_64|amd64\" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL\n                                                          \"arm64\")\n  # Enable MMX, SSE and SSE2 on compatible host systems (assuming no cross-compile)\n  set(ARCH_SIMD_FLAGS -mmmx -msse -msse2)\nelseif(_HOST_ARCH MATCHES \"arm64|arm64e|aarch64\")\n  # Enable available built-in SIMD support in Clang and GCC\n  if(CMAKE_C_COMPILER_ID MATCHES \"^(Apple)?Clang|GNU\" OR CMAKE_CXX_COMPILER_ID MATCHES\n                                                         \"^(Apple)?Clang|GNU\")\n    include(CheckCCompilerFlag)\n    include(CheckCXXCompilerFlag)\n\n    check_c_compiler_flag(\"-fopenmp-simd\" C_COMPILER_SUPPORTS_OPENMP_SIMD)\n    check_cxx_compiler_flag(\"-fopenmp-simd\" CXX_COMPILER_SUPPORTS_OPENMP_SIMD)\n    target_compile_options(\n      ${CMAKE_PROJECT_NAME}\n      PRIVATE\n        -DSIMDE_ENABLE_OPENMP\n        \"$<$<AND:$<COMPILE_LANGUAGE:C>,$<BOOL:C_COMPILER_SUPPORTS_OPENMP_SIMD>>:-fopenmp-simd>\"\n        \"$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<BOOL:CXX_COMPILER_SUPPORTS_OPENMP_SIMD>>:-fopenmp-simd>\")\n  endif()\nendif()\n\n# macOS specific settings\nif(OS_MACOS)\n  # Set macOS-specific C++ standard library\n  target_compile_options(\n    ${CMAKE_PROJECT_NAME}\n    PRIVATE \"$<$<COMPILE_LANG_AND_ID:OBJC,AppleClang,Clang>:-fcolor-diagnostics>\" -stdlib=libc++)\n\n  # Set build architecture to host architecture by default\n  if(NOT CMAKE_OSX_ARCHITECTURES)\n    set(CMAKE_OSX_ARCHITECTURES\n        ${CMAKE_HOST_SYSTEM_PROCESSOR}\n        CACHE STRING \"Build architecture for macOS\" FORCE)\n  endif()\n  set_property(CACHE CMAKE_OSX_ARCHITECTURES PROPERTY STRINGS arm64 x86_64 \"arm64;x86_64\")\n\n  # Set deployment target to 11.0 for Apple Silicon or 10.15 for Intel and Universal builds\n  if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)\n    set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=arm64] \"11.0\")\n    set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET[arch=x86_64] \"10.15\")\n\n    if(\"${CMAKE_OSX_ARCHITECTURES}\" STREQUAL \"arm64\")\n      set(_MACOS_DEPLOYMENT_TARGET \"11.0\")\n    else()\n      set(_MACOS_DEPLOYMENT_TARGET \"10.15\")\n    endif()\n\n    set(CMAKE_OSX_DEPLOYMENT_TARGET\n        ${_MACOS_DEPLOYMENT_TARGET}\n        CACHE STRING\n              \"Minimum macOS version to target for deployment (at runtime); newer APIs weak linked\"\n              FORCE)\n    unset(_MACOS_DEPLOYMENT_TARGET)\n  endif()\n\n  set_property(CACHE CMAKE_OSX_DEPLOYMENT_TARGET PROPERTY STRINGS 13.0 12.0 11.0 10.15)\n\n  # Override macOS install directory\n  if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n    set(CMAKE_INSTALL_PREFIX\n        ${CMAKE_BINARY_DIR}/install\n        CACHE STRING \"Directory to install OBS to after building\" FORCE)\n  endif()\n\n  # Set up codesigning for Xcode builds with team IDs or standalone builds with developer identity\n  if(NOT OBS_BUNDLE_CODESIGN_TEAM)\n    if(NOT OBS_BUNDLE_CODESIGN_IDENTITY)\n      set(OBS_BUNDLE_CODESIGN_IDENTITY\n          \"-\"\n          CACHE STRING \"OBS code signing identity for macOS\" FORCE)\n    endif()\n    set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ${OBS_BUNDLE_CODESIGN_IDENTITY})\n  else()\n    # Team ID specified, warn if Xcode generator is not used and fall back to ad-hoc signing\n    if(NOT XCODE)\n      message(\n        WARNING\n          \"Code signing with a team identifier is only supported with the Xcode generator. Using ad-hoc code signature instead.\"\n      )\n      if(NOT OBS_BUNDLE_CODESIGN_IDENTITY)\n        set(OBS_BUNDLE_CODESIGN_IDENTITY\n            \"-\"\n            CACHE STRING \"OBS code signing identity for macOS\" FORCE)\n      endif()\n    else()\n      unset(OBS_BUNDLE_CODESIGN_IDENTITY)\n      set_property(CACHE OBS_BUNDLE_CODESIGN_TEAM PROPERTY HELPSTRING\n                                                           \"OBS code signing team for macOS\")\n      set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic)\n      set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${OBS_BUNDLE_CODESIGN_TEAM})\n    endif()\n  endif()\n\n  # Set path to entitlements property list for codesigning. Entitlements should match the host\n  # binary, in this case OBS.app.\n  set(OBS_CODESIGN_ENTITLEMENTS\n      ${CMAKE_SOURCE_DIR}/bundle/macos/entitlements.plist\n      CACHE INTERNAL \"Path to codesign entitlements plist\")\n  # Enable linker codesigning by default. Building OBS or plugins on host systems older than macOS\n  # 10.15 is not supported\n  set(OBS_CODESIGN_LINKER\n      ON\n      CACHE BOOL \"Enable linker codesigning on macOS (macOS 11+ required)\")\n\n  # Tell Xcode to pretend the linker signed binaries so that editing with install_name_tool\n  # preserves ad-hoc signatures. This option is supported by codesign on macOS 11 or higher. See\n  # CMake Issue 21854: https://gitlab.kitware.com/cmake/cmake/-/issues/21854\n  if(OBS_CODESIGN_LINKER)\n    set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS \"-o linker-signed\")\n  endif()\n\n  # Set default options for bundling on macOS\n  set(CMAKE_MACOSX_RPATH ON)\n  set(CMAKE_SKIP_BUILD_RPATH OFF)\n  set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)\n  set(CMAKE_INSTALL_RPATH \"@executable_path/../Frameworks/\")\n  set(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF)\n\n  set(OBS_PLUGIN_DESTINATION \"bin\")\n\tset(OBS_DATA_DESTINATION \"data\")\n\n  # Helper function for plugin targets (macOS version)\n  function(setup_plugin_target target)\n    # Sanity check for required bundle information\n    #\n    # * Bundle identifier\n    # * Bundle version\n    # * Short version string\n    #if(NOT DEFINED MACOSX_PLUGIN_GUI_IDENTIFIER)\n    #  message(\n    #    FATAL_ERROR\n    #      \"No 'MACOSX_PLUGIN_GUI_IDENTIFIER' set, but is required to build plugin bundles on macOS - example: 'com.yourname.pluginname'\"\n    #  )\n    #endif()\n\n    #if(NOT DEFINED MACOSX_PLUGIN_BUNDLE_VERSION)\n    #  message(\n    #    FATAL_ERROR\n    #      \"No 'MACOSX_PLUGIN_BUNDLE_VERSION' set, but is required to build plugin bundles on macOS - example: '25'\"\n    #  )\n    #endif()\n\n    #if(NOT DEFINED MACOSX_PLUGIN_SHORT_VERSION_STRING)\n    #  message(\n    #    FATAL_ERROR\n    #      \"No 'MACOSX_PLUGIN_SHORT_VERSION_STRING' set, but is required to build plugin bundles on macOS - example: '1.0.2'\"\n    #  )\n    #endif()\n\n    # Set variables for automatic property list generation\n    #set(MACOSX_PLUGIN_BUNDLE_NAME\n    #    \"${target}\"\n    #    PARENT_SCOPE)\n    #set(MACOSX_PLUGIN_BUNDLE_VERSION\n    #    \"${MACOSX_PLUGIN_BUNDLE_VERSION}\"\n    #    PARENT_SCOPE)\n    #set(MACOSX_PLUGIN_SHORT_VERSION_STRING\n    #    \"${MACOSX_PLUGIN_SHORT_VERSION_STRING}\"\n    #    PARENT_SCOPE)\n    #set(MACOSX_PLUGIN_EXECUTABLE_NAME\n    #    \"${target}\"\n    #    PARENT_SCOPE)\n    #set(MACOSX_PLUGIN_BUNDLE_TYPE\n    #    \"BNDL\"\n    #    PARENT_SCOPE)\n\n    # Set installation target to install prefix root (default for bundles)\n    #install(\n    #  TARGETS ${target}\n    #  LIBRARY DESTINATION \".\"\n    #          COMPONENT obs_plugins\n    #          NAMELINK_COMPONENT ${target}_Development)\n\n    # Set prefix to empty string to avoid automatic naming of generated library, i.e.\n    # \"lib<YOUR_PLUGIN_NAME>\"\n    set_target_properties(${target} PROPERTIES PREFIX \"\")\n    \n    install(\n       TARGETS ${target}\n\t\t\t  RUNTIME DESTINATION \"${target}/${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\t  LIBRARY DESTINATION \"${target}/${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\t\tNAMELINK_COMPONENT ${target}_Development)\n\n    if(TARGET Qt::Core)\n      # Framework version has changed between Qt5 (uses wrong numerical version) and Qt6 (uses\n      # correct alphabetical version)\n      if(${_QT_VERSION} EQUAL 5)\n        set(_QT_FW_VERSION \"${QT_VERSION}\")\n      else()\n        set(_QT_FW_VERSION \"A\")\n      endif()\n\n      # Set up install-time command to fix Qt library references to point into OBS.app bundle\n      set(_COMMAND\n          \"${CMAKE_INSTALL_NAME_TOOL} \\\\\n        -change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/${QT_VERSION}/QtWidgets @rpath/QtWidgets.framework/Versions/${_QT_FW_VERSION}/QtWidgets \\\\\n        -change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/${QT_VERSION}/QtCore @rpath/QtCore.framework/Versions/${_QT_FW_VERSION}/QtCore \\\\\n        -change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/${QT_VERSION}/QtGui @rpath/QtGui.framework/Versions/${_QT_FW_VERSION}/QtGui \\\\\n        \\\\\\\"\\${CMAKE_INSTALL_PREFIX}/${target}.plugin/Contents/MacOS/${target}\\\\\\\"\")\n      install(CODE \"execute_process(COMMAND /bin/sh -c \\\"${_COMMAND}\\\")\" COMPONENT obs_plugins)\n      unset(_QT_FW_VERSION)\n    endif()\n\n    # Set macOS bundle properties\n    #set_target_properties(\n    #  ${target}\n    #  PROPERTIES PREFIX \"\"\n    #             BUNDLE ON\n    #             BUNDLE_EXTENSION \"plugin\"\n    #             OUTPUT_NAME ${target}\n    #             MACOSX_BUNDLE_INFO_PLIST\n    #             #\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/Plugin-Info.plist.in\"\n    #             \"${CMAKE_SOURCE_DIR}/bundle/macOS/Plugin-Info.plist.in\"\n    #             XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER \"${MACOSX_PLUGIN_GUI_IDENTIFIER}\"\n    #             XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS\n    #             #\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bundle/macOS/entitlements.plist\")\n    #             \"${CMAKE_SOURCE_DIR}/bundle/macOS/entitlements.plist\")\n\n    # If not building with Xcode, manually code-sign the plugin\n    if(NOT XCODE)\n    #  set(_COMMAND\n    #      \"/usr/bin/codesign --force \\\\\n    #      --sign \\\\\\\"${OBS_BUNDLE_CODESIGN_IDENTITY}\\\\\\\" \\\\\n    #      --options runtime \\\\\n    #      --entitlements \\\\\\\"${CMAKE_SOURCE_DIR}/bundle/macOS/entitlements.plist\\\\\\\" \\\\\n    #      \\\\\\\"\\${CMAKE_INSTALL_PREFIX}/${target}.plugin\\\\\\\"\")\n      install(CODE \"execute_process(COMMAND /bin/sh -c \\\"${_COMMAND}\\\")\" COMPONENT obs_plugins)\n    endif()\n\n    install_bundle_resources(${target})\n  endfunction()\n\n  # Helper function to add resources from \"data\" directory as bundle resources\n  function(install_bundle_resources target)\n    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data)\n    #  file(GLOB_RECURSE _DATA_FILES \"${CMAKE_CURRENT_SOURCE_DIR}/data/*\")\n    #  foreach(_DATA_FILE IN LISTS _DATA_FILES)\n    #    file(RELATIVE_PATH _RELATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${_DATA_FILE})\n    #    get_filename_component(_RELATIVE_PATH ${_RELATIVE_PATH} PATH)\n    #    target_sources(${target} PRIVATE ${_DATA_FILE})\n    #    set_source_files_properties(${_DATA_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION\n    #                                                         Resources/${_RELATIVE_PATH})\n    #    string(REPLACE \"\\\\\" \"\\\\\\\\\" _GROUP_NAME ${_RELATIVE_PATH})\n    #    source_group(\"Resources\\\\${_GROUP_NAME}\" FILES ${_DATA_FILE})\n    #  endforeach()\n    install(DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/data/\"\n\t\t\t\tDESTINATION \"${target}/${OBS_DATA_DESTINATION}\"\n\t\t\t\tUSE_SOURCE_PERMISSIONS)\n    endif()\n  endfunction()\nelse()\n  # Check for target architecture (64bit vs 32bit)\n  if(CMAKE_SIZEOF_VOID_P EQUAL 8)\n    set(_ARCH_SUFFIX 64)\n  else()\n    set(_ARCH_SUFFIX 32)\n  endif()\n  set(OBS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/rundir)\n\n  # Unix specific settings\n  if(OS_POSIX)\n    # Paths to binaries and plugins differ between portable and non-portable builds on Linux\n    option(LINUX_PORTABLE \"Build portable version (Linux)\" ON)\n    if(NOT LINUX_PORTABLE)\n      set(OBS_LIBRARY_DESTINATION \"${CMAKE_INSTALL_LIBDIR}/${CMAKE_LIBRARY_ARCHITECTURE}\")\n\t\t\tset(OBS_PLUGIN_DESTINATION \"${OBS_LIBRARY_DESTINATION}/obs-plugins\")\n\t\t\tset(CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/lib\")\n\t\t\tset(OBS_DATA_DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/obs\")\n    else()\n      set(OBS_LIBRARY_DESTINATION bin/${_ARCH_SUFFIX}bit)\n      set(OBS_PLUGIN_DESTINATION obs-plugins/${_ARCH_SUFFIX}bit)\n      set(CMAKE_INSTALL_RPATH \"$ORIGIN/\" \"${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}\")\n      set(OBS_DATA_DESTINATION \"data\")\n    endif()\n\n    # Setup Linux-specific CPack values for \"deb\" package generation\n    if(OS_LINUX)\n      set(CPACK_PACKAGE_NAME \"${CMAKE_PROJECT_NAME}\")\n      set(CPACK_DEBIAN_PACKAGE_MAINTAINER \"${LINUX_MAINTAINER_EMAIL}\")\n      set(CPACK_PACKAGE_VERSION \"${OBS_PLUGUIN_VERSION}\")\n      set(CPACK_PACKAGE_CONTACT \"${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>\")\n      set(CPACK_PACKAGE_VENDOR \"${LINUX_MAINTAINER}\")\n      set(CPACK_GENERATOR \"DEB\" \"TGZ\" \"RPM\")\n\n      set(CPACK_DEBIAN_PACKAGE_DEPENDS\n      \"obs-studio (>= 28.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)\")\n      set(CPACK_DEBIAN_PACKAGE_SECTION \"video\")\n\n      set(CPACK_RPM_PACKAGE_REQUIRES \"obs-studio >= 28.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0\")\n      set(CPACK_RPM_PACKAGE_GROUP \"Video\")\n      set(CPACK_RPM_PACKAGE_LICENSE \"GPL-2.0\")\n\n      #set(CPACK_PACKAGE_FILE_NAME \"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-linux-x86_64\")\n\n      #set(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_SOURCE_DIR}/release)\n\n      if(NOT LINUX_PORTABLE)\n        set(CPACK_SET_DESTDIR ON)\n      endif()\n      include(CPack)\n    endif()\n    # Windows specific settings\n  else()\n    set(OBS_LIBRARY_DESTINATION \"bin/${_ARCH_SUFFIX}bit\")\n    set(OBS_LIBRARY32_DESTINATION \"bin/32bit\")\n    set(OBS_LIBRARY64_DESTINATION \"bin/64bit\")\n    set(OBS_PLUGIN_DESTINATION \"obs-plugins/${_ARCH_SUFFIX}bit\")\n    set(OBS_PLUGIN32_DESTINATION \"obs-plugins/32bit\")\n    set(OBS_PLUGIN64_DESTINATION \"obs-plugins/64bit\")\n\n    set(OBS_DATA_DESTINATION \"data\")\n\n    if(MSVC)\n      # Set default Visual Studio CL.exe compile options.\n      #\n      # * Enable building with multiple processes,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/mp-build-with-multiple-processes?view=msvc-170\n      # * Enable lint-like warnings,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=msvc-170\n      # * Enable treating all warnings as errors,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=msvc-170\n      # * RelWithDebInfo ONLY - Enable expanding of all functions not explicitly marked for no\n      #   inlining,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/ob-inline-function-expansion?view=msvc-170\n      # * Enable UNICODE support,\n      #   https://docs.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings#unicode-and-ansi-functions\n      # * DISABLE warnings about using POSIX function names,\n      #   https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?view=msvc-170#posix-function-names\n      # * DISABLE warnings about unsafe CRT library functions,\n      #   https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?view=msvc-170#unsafe-crt-library-functions\n      # * DISABLE warnings about nonstandard nameless structs/unions,\n      #   https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-4-c4201?view=msvc-170\n      target_compile_options(\n        ${CMAKE_PROJECT_NAME}\n        PRIVATE /MP\n                /W3\n                /WX\n                /wd4201\n                \"$<$<CONFIG:RELWITHDEBINFO>:/Ob2>\"\n                \"$<$<CONFIG:DEBUG>:/DDEBUG=1;/D_DEBUG=1>\"\n                /DUNICODE\n                /D_UNICODE\n                /D_CRT_SECURE_NO_WARNINGS\n                /D_CRT_NONSTDC_NO_WARNINGS)\n\n      # Set default Visual Studio linker options.\n      #\n      # * Enable removal of functions and data that are never used,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170\n      # * Enable treating all warnings as errors,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/wx-treat-linker-warnings-as-errors?view=msvc-170\n      # * x64 ONLY - DISABLE creation of table of safe exception handlers,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers?view=msvc-170\n      # * Debug ONLY - DISABLE incremental linking,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/incremental-link-incrementally?view=msvc-170\n      # * RelWithDebInfo ONLY - Disable incremental linking, but enable COMDAT folding,\n      #   https://docs.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170\n      target_link_options(\n        ${CMAKE_PROJECT_NAME}\n        PRIVATE\n        \"LINKER:/OPT:REF\"\n        \"LINKER:/WX\"\n        \"$<$<NOT:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>>:LINKER\\:/SAFESEH\\:NO>\"\n        \"$<$<CONFIG:DEBUG>:LINKER\\:/INCREMENTAL\\:NO>\"\n        \"$<$<CONFIG:RELWITHDEBINFO>:LINKER\\:/INCREMENTAL\\:NO;/OPT\\:ICF>\")\n    endif()\n  endif()\n\n  # Helper function for plugin targets (Windows and Linux version)\n  function(setup_plugin_target target)\n    # Set prefix to empty string to avoid automatic naming of generated library, i.e.\n    # \"lib<YOUR_PLUGIN_NAME>\"\n    set_target_properties(${target} PROPERTIES PREFIX \"\")\n\n    # Set install directories\n    install(\n      TARGETS ${target}\n      RUNTIME DESTINATION \"${OBS_PLUGIN_DESTINATION}\" COMPONENT ${target}_Runtime\n      LIBRARY DESTINATION \"${OBS_PLUGIN_DESTINATION}\"\n              COMPONENT ${target}_Runtime\n              NAMELINK_COMPONENT ${target}_Development)\n\n    # Set rundir install directory\n    install(\n      FILES $<TARGET_FILE:${target}>\n      DESTINATION $<CONFIG>/${OBS_PLUGIN_DESTINATION}\n      COMPONENT obs_rundir\n      EXCLUDE_FROM_ALL)\n\n    if(OS_WINDOWS)\n      # Set install directory for optional PDB symbol files\n      install(\n        FILES $<TARGET_PDB_FILE:${target}>\n        CONFIGURATIONS \"RelWithDebInfo\" \"Debug\"\n        DESTINATION ${OBS_PLUGIN_DESTINATION}\n        COMPONENT ${target}_Runtime\n        OPTIONAL)\n\n      # Set rundir install directory for optional PDB symbol files\n      install(\n        FILES $<TARGET_PDB_FILE:${target}>\n        CONFIGURATIONS \"RelWithDebInfo\" \"Debug\"\n        DESTINATION $<CONFIG>/${OBS_PLUGIN_DESTINATION}\n        COMPONENT obs_rundir\n        OPTIONAL EXCLUDE_FROM_ALL)\n    endif()\n\n    # Add resources from data directory\n    setup_target_resources(${target} obs-plugins/${target})\n\n    # Set up plugin for testing in available OBS build on Windows\n    if(OS_WINDOWS AND DEFINED OBS_BUILD_DIR)\n      setup_target_for_testing(${target} obs-plugins/${target})\n    endif()\n\n    # Custom command to install generated plugin into rundir\n    add_custom_command(\n      TARGET ${target}\n      POST_BUILD\n      COMMAND\n        \"${CMAKE_COMMAND}\" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR}\n        -DCMAKE_INSTALL_COMPONENT=obs_rundir -DCMAKE_INSTALL_CONFIG_NAME=$<CONFIG> -P\n        ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake\n      COMMENT \"Installing to plugin rundir\"\n      VERBATIM)\n  endfunction()\n\n  # Helper function to add resources from \"data\" directory\n  function(setup_target_resources target destination)\n    if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data)\n      install(\n        DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/\n        DESTINATION ${OBS_DATA_DESTINATION}/${destination}\n        USE_SOURCE_PERMISSIONS\n        COMPONENT obs_plugins)\n\n      install(\n        DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data\n        DESTINATION $<CONFIG>/${OBS_DATA_DESTINATION}/${destination}\n        USE_SOURCE_PERMISSIONS\n        COMPONENT obs_rundir\n        EXCLUDE_FROM_ALL)\n    endif()\n  endfunction()\n\n  if(OS_WINDOWS)\n    # Additional Windows-only helper function to copy plugin to existing OBS development directory:\n    #\n    # Copies plugin with associated PDB symbol files as well as contents of data directory into the\n    # OBS rundir as specified by \"OBS_BUILD_DIR\".\n    function(setup_target_for_testing target destination)\n      install(\n        FILES $<TARGET_FILE:${target}>\n        DESTINATION $<CONFIG>/${OBS_PLUGIN_DESTINATION}\n        COMPONENT obs_testing\n        EXCLUDE_FROM_ALL)\n\n      install(\n        FILES $<TARGET_PDB_FILE:${target}>\n        CONFIGURATIONS \"RelWithDebInfo\" \"Debug\"\n        DESTINATION $<CONFIG>/${OBS_PLUGIN_DESTINATION}\n        COMPONENT obs_testing\n        OPTIONAL EXCLUDE_FROM_ALL)\n\n      install(\n        DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/\n        DESTINATION $<CONFIG>/${OBS_DATA_DESTINATION}/${destination}\n        USE_SOURCE_PERMISSIONS\n        COMPONENT obs_testing\n        EXCLUDE_FROM_ALL)\n\n      add_custom_command(\n        TARGET ${target}\n        POST_BUILD\n        COMMAND\n          \"${CMAKE_COMMAND}\" -DCMAKE_INSTALL_PREFIX=${OBS_BUILD_DIR}/rundir\n          -DCMAKE_INSTALL_COMPONENT=obs_testing -DCMAKE_INSTALL_CONFIG_NAME=$<CONFIG> -P\n          ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake\n        COMMENT \"Installing to OBS test directory\"\n        VERBATIM)\n    endfunction()\n  endif()\nendif()"
  },
  {
    "path": "external/ObsPluginHelpers.cmake.bak",
    "content": "if(POLICY CMP0087)\n\tcmake_policy(SET CMP0087 NEW)\nendif()\n\nset(OBS_STANDALONE_PLUGIN_DIR \"${CMAKE_SOURCE_DIR}/release\")\nset(INCLUDED_LIBOBS_CMAKE_MODULES ON)\n\ninclude(GNUInstallDirs)\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Darwin\")\n\tset(OS_MACOS ON)\n\tset(OS_POSIX ON)\nelseif(\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux|FreeBSD|OpenBSD\")\n\tset(OS_POSIX ON)\n\tstring(TOUPPER \"${CMAKE_SYSTEM_NAME}\" _SYSTEM_NAME_U)\n\tset(OS_${_SYSTEM_NAME_U} ON)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Windows\")\n\tset(OS_WINDOWS ON)\n\tset(OS_POSIX OFF)\nendif()\n\n# Old-Style plugin detected, find \"modern\" libobs variant instead and set\n# global include directories to fix \"bad\" plugin behaviour\nif(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs)\n\tmessage(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.\")\n\tfind_package(libobs REQUIRED)\n\tif(TARGET OBS::libobs)\n\t\tset_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE)\n\t\tmessage(STATUS \"OBS: Using modern libobs target\")\n\n\t\tadd_library(libobs ALIAS OBS::libobs)\n\t\tif(OS_WINDOWS)\n\t\t\tadd_library(w32-pthreads ALIAS OBS::w32-pthreads)\n\t\tendif()\n\tendif()\nendif()\n\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS))\n\tset(CMAKE_INSTALL_PREFIX \"${OBS_STANDALONE_PLUGIN_DIR}\" CACHE STRING \"Directory to install OBS plugin after building\" FORCE)\nendif()\n\nif(NOT CMAKE_BUILD_TYPE)\n\tset(CMAKE_BUILD_TYPE \"RelWithDebInfo\" CACHE STRING\n\t\t\"OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]\" FORCE)\n\tset_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel)\nendif()\n\nfile(RELATIVE_PATH RELATIVE_INSTALL_PATH \"${CMAKE_SOURCE_DIR}\" \"${CMAKE_INSTALL_PREFIX}\")\nfile(RELATIVE_PATH RELATIVE_BUILD_PATH \"${CMAKE_SOURCE_DIR}\" \"${CMAKE_BINARY_DIR}\")\n\n# Set-up OS-specific environment and helper functions\nif(OS_MACOS)\n\tif(NOT CMAKE_OSX_ARCHITECTURES)\n\t\tset(CMAKE_OSX_ARCHITECTURES \"x86_64\" CACHE STRING \"OBS plugin build architecture for macOS - x86_64 required at least\" FORCE)\n\tendif()\n\n\tif(NOT CMAKE_OSX_DEPLOYMENT_TARGET)\n\t\tset(CMAKE_OSX_DEPLOYMENT_TARGET \"10.13\" CACHE STRING \"OBS plugin deployment target for macOS - 10.13+ required\" FORCE)\n\tendif()\n\n\tif(NOT DEFINED OBS_CODESIGN_LINKER)\n\t\tset(OBS_CODESIGN_LINKER ON CACHE BOOL \"Enable linker code-signing on macOS (v11+ required\" FORCE)\n\tendif()\n\n\tif(NOT DEFINED OBS_BUNDLE_CODESIGN_IDENTITY)\n\t\tset(OBS_BUNDLE_CODESIGN_IDENTITY \"-\" CACHE STRING \"Codesign identity for macOS\" FORCE)\n\tendif()\n\n\t# Xcode configuration\n\tif(XCODE)\n\t\t# Tell Xcode to pretend the linker signed binaries so that\n\t\t# editing with install_name_tool preserves ad-hoc signatures.\n\t\t# See CMake Issue 21854.\n\t\t# This option is supported by codesign on macOS 11 or higher.\n\t\tset(CMAKE_XCODE_GENERATE_SCHEME ON)\n\t\tif(OBS_CODESIGN_LINKER)\n\t\t\tset(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS \"-o linker-signed\")\n\t\tendif()\n\tendif()\n\n\t# Set default options for bundling on macOS\n\tset(CMAKE_MACOSX_RPATH ON)\n\tset(CMAKE_SKIP_BUILD_RPATH OFF)\n\tset(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)\n\tset(CMAKE_INSTALL_RPATH \"@executable_path/../Frameworks/\")\n\tset(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF)\n\n\tfunction(setup_plugin_target target)\n\t\tif(NOT DEFINED MACOSX_PLUGIN_GUI_IDENTIFIER)\n\t\t\tmessage(FATAL_ERROR \"No 'MACOSX_PLUGIN_GUI_IDENTIFIER' set, but is required to build plugin bundles on macOS - example: 'com.yourname.pluginname'\")\n\t\tendif()\n\n\t\tif(NOT DEFINED MACOSX_PLUGIN_BUNDLE_VERSION)\n\t\t\tmessage(FATAL_ERROR \"No 'MACOSX_PLUGIN_BUNDLE_VERSION' set, but is required to build plugin bundles on macOS - example: '25'\")\n\t\tendif()\n\n\t\tif(NOT DEFINED MACOSX_PLUGIN_SHORT_VERSION_STRING)\n\t\t\tmessage(FATAL_ERROR \"No 'MACOSX_PLUGIN_SHORT_VERSION_STRING' set, but is required to build plugin bundles on macOS - example: '1.0.2'\")\n\t\tendif()\n\n\t\tset(MACOSX_PLUGIN_BUNDLE_NAME \"${target}\" PARENT_SCOPE)\n\t\tset(MACOSX_PLUGIN_BUNDLE_VERSION \"${MACOSX_BUNDLE_BUNDLE_VERSION}\" PARENT_SCOPE)\n\t\tset(MACOSX_PLUGIN_SHORT_VERSION_STRING \"${MACOSX_BUNDLE_SHORT_VERSION_STRING}\" PARENT_SCOPE)\n\t\tset(MACOSX_PLUGIN_EXECUTABLE_NAME \"${target}\" PARENT_SCOPE)\n\t\tset(MACOSX_PLUGIN_BUNDLE_TYPE \"BNDL\" PARENT_SCOPE)\n\n\t\tinstall(TARGETS ${target}\n\t\t\tLIBRARY DESTINATION \".\"\n\t\t\t\tCOMPONENT obs_plugins\n\t\t\t\tNAMELINK_COMPONENT ${target}_Development)\n\n\t\tinstall(CODE\n\t\t\t\"execute_process(COMMAND /bin/sh -c \\\"install_name_tool \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets.framework/Versions/5/QtWidgets \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore.framework/Versions/5/QtCore \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui.framework/Versions/5/QtGui \\\\\n\t\t\t${CMAKE_INSTALL_PREFIX}/${target}.plugin/Contents/MacOS/${target}\\\")\")\n\n\t\tif(NOT XCODE)\n\t\t\tinstall(CODE\n\t\t\t\t\"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\\\")\")\n\t\tendif()\n\n\t\tset_target_properties(${target} PROPERTIES\n\t\t\tBUNDLE ON\n\t\t\tBUNDLE_EXTENSION \"plugin\"\n\t\t\tOUTPUT_NAME ${target}\n\t\t\tMACOSX_BUNDLE_INFO_PLIST \"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/Plugin-Info.plist.in\"\n\t\t\tXCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER \"${MACOSX_PLUGIN_GUI_IDENTIFIER}\"\n\t\t\tXCODE_ATTRIBUTE_CODE_SIGN_IDENTITY \"${OBS_BUNDLE_CODESIGN_IDENTITY}\"\n\t\t\tXCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS \"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../bundle/macOS/entitlements.plist\")\n\n\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\tCOMMAND /bin/sh -c \"codesign --force --sign \\\"-\\\" $<$<BOOL:${OBS_CODESIGN_LINKER}>:--options linker-signed >\\\"$<TARGET_BUNDLE_DIR:${target}>\\\"\"\n\t\t\tCOMMENT \"Codesigning ${target}\"\n\t\t\tVERBATIM)\n\n\t\tinstall_bundle_resources(${target})\n\tendfunction()\n\n\tfunction(install_bundle_resources target)\n\t\tif(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/data\")\n\t\t\tfile(GLOB_RECURSE _DATA_FILES \"${CMAKE_CURRENT_SOURCE_DIR}/data/*\")\n\t\t\tforeach(_DATA_FILE IN LISTS _DATA_FILES)\n\t\t\t\tfile(RELATIVE_PATH _RELATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data/ ${_DATA_FILE})\n\t\t\t\tget_filename_component(_RELATIVE_PATH \"${_RELATIVE_PATH}\" PATH)\n\t\t\t\ttarget_sources(${target}\n\t\t\t\t\tPRIVATE ${_DATA_FILE})\n\t\t\t\tset_source_files_properties(${_DATA_FILE} PROPERTIES\n\t\t\t\t\tMACOSX_PACKAGE_LOCATION \"Resources/${_RELATIVE_PATH}\")\n\t\t\t\tstring(REPLACE \"\\\\\" \"\\\\\\\\\" _GROUP_NAME \"${_RELATIVE_PATH}\")\n\t\t\t\tsource_group(\"Resources\\\\${_GROUP_NAME}\" FILES ${_DATA_FILE})\n\t\t\tendforeach()\n\t\tendif()\n\tendfunction()\nelse()\n\tif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\tset(_ARCH_SUFFIX 64)\n\telse()\n\t\tset(_ARCH_SUFFIX 32)\n\tendif()\n\tset(OBS_OUTPUT_DIR \"${CMAKE_BINARY_DIR}/rundir\")\n\n\tif(OS_POSIX)\n\t\tIF(NOT LINUX_PORTABLE)\n\t\t\tset(OBS_LIBRARY_DESTINATION \"${CMAKE_INSTALL_LIBDIR}\")\n\t\t\tset(OBS_PLUGIN_DESTINATION \"${OBS_LIBRARY_DESTINATION}/obs-plugins\")\n\t\t\tset(CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/lib\")\n\t\t\tset(OBS_DATA_DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/obs\")\n\t\telse()\n\t\t\tset(OBS_LIBRARY_DESTINATION \"bin/${_ARCH_SUFFIX}bit\")\n\t\t\tset(OBS_PLUGIN_DESTINATION \"obs-plugins/${_ARCH_SUFFIX}bit\")\n\t\t\tset(CMAKE_INSTALL_RPATH \"$ORIGIN/\" \"${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}\")\n\t\t\tset(OBS_DATA_DESTINATION \"data\")\n\t\tendif()\n\n\t\tif(OS_LINUX)\n\t\t\tset(CPACK_PACKAGE_NAME \"${CMAKE_PROJECT_NAME}\")\n\t\t\tset(CPACK_PACKAGE_VERSION \"${OBS_PLUGUIN_VERSION}\")\n\t\t\tset(CPACK_PACKAGE_CONTACT \"${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>\")\n\t\t\tset(CPACK_PACKAGE_VENDOR \"${LINUX_MAINTAINER}\")\n\t\t\tset(CPACK_GENERATOR \"DEB\" \"TGZ\" \"RPM\")\n\n\t\t\tset(CPACK_DEBIAN_PACKAGE_DEPENDS\n\t\t\t\t\"obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)\")\n\t\t\tset(CPACK_DEBIAN_PACKAGE_SECTION \"video\")\n\n\t\t\tset(CPACK_RPM_PACKAGE_REQUIRES \"obs-studio >= 27.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0\")\n\t\t\tset(CPACK_RPM_PACKAGE_GROUP \"Video\")\n\t\t\tset(CPACK_RPM_PACKAGE_LICENSE \"GPL-2.0\")\n\n\t\t\tif(NOT LINUX_PORTABLE)\n\t\t\t\t#set(CPACK_SET_DESTDIR ON)\n\t\t\tendif()\n\t\t\tinclude(CPack)\n\t\tendif()\n\telse()\n\t\tset(OBS_LIBRARY_DESTINATION \"bin/${_ARCH_SUFFIX}bit\")\n\t\tset(OBS_LIBRARY32_DESTINATION \"bin/32bit\")\n\t\tset(OBS_LIBRARY64_DESTINATION \"bin/64bit\")\n\t\tset(OBS_PLUGIN_DESTINATION \"obs-plugins/${_ARCH_SUFFIX}bit\")\n\t\tset(OBS_PLUGIN32_DESTINATION \"obs-plugins/32bit\")\n\t\tset(OBS_PLUGIN64_DESTINATION \"obs-plugins/64bit\")\n\n\t\tset(OBS_DATA_DESTINATION \"data\")\n\tendif()\n\n\tfunction(setup_plugin_target target)\n\t\tset_target_properties(${target} PROPERTIES\n\t\t\tPREFIX \"\")\n\n\t\tinstall(TARGETS ${target}\n\t\t\tRUNTIME DESTINATION \"${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\tLIBRARY DESTINATION \"${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\t\tNAMELINK_COMPONENT ${target}_Development)\n\n\t\tif(OS_WINDOWS)\n\t\t\tinstall(FILES \n\t\t\t\t\"$<TARGET_PDB_FILE:${PROJECT_NAME}>\" DESTINATION \"${OBS_PLUGIN_DESTINATION}\" \n\t\t\t\tOPTIONAL)\n\t\tendif()\n\n\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy\n\t\t\t\t\"$<TARGET_FILE:${target}>\"\n\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\tVERBATIM)\n\n\t\tif(OS_WINDOWS)\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E \"$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,copy,true>\"\n\t\t\t\t\t\"$<TARGET_PDB_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tVERBATIM)\n\t\tendif()\n\n\t\tif(MSVC)\n\t\t\ttarget_link_options(${target}\n\t\t\t\tPRIVATE\n\t\t\t\t\t\"LINKER:/OPT:REF\"\n\t\t\t\t\t\"$<$<NOT:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>>:LINKER\\:/SAFESEH\\:NO>\"\n\t\t\t\t\t\"$<$<CONFIG:DEBUG>:LINKER\\:/INCREMENTAL:NO>\"\n\t\t\t\t\t\"$<$<CONFIG:RELWITHDEBINFO>:LINKER\\:/INCREMENTAL:NO>\")\n\t\tendif()\n\n\t\tsetup_target_resources(\"${target}\" \"obs-plugins/${target}\")\n\n\t\tif(OS_WINDOWS AND DEFINED OBS_BUILD_DIR)\n\t\t\tsetup_target_for_testing(${target})\n\t\tendif()\n\tendfunction()\n\n\tfunction(setup_target_resources target destination)\n\t\tif(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/data\")\n\t\t\tinstall(DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/data/\"\n\t\t\t\tDESTINATION \"${OBS_DATA_DESTINATION}/${destination}\"\n\t\t\t\tUSE_SOURCE_PERMISSIONS)\n\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy_directory\n\t\t\t\t\t\"${CMAKE_CURRENT_SOURCE_DIR}/data\"\n\t\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_DATA_DESTINATION}/${destination}\"\n\t\t\t\tVERBATIM)\n\t\tendif()\n\tendfunction()\n\n\tif(OS_WINDOWS)\n\t\tfunction(setup_target_for_testing target)\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy\n\t\t\t\t\t\"$<TARGET_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E \"$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,copy,true>\"\n\t\t\t\t\t\"$<TARGET_PDB_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E make_directory\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_DATA_DESTINATION}/obs-plugins/${target}\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy_directory\n\t\t\t\t\t\"${CMAKE_CURRENT_SOURCE_DIR}/data\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_DATA_DESTINATION}/obs-plugins/${target}\"\n\t\t\t\tVERBATIM)\n\t\tendfunction()\n\tendif()\nendif()\n"
  },
  {
    "path": "external/ObsPluginHelpers.cmake.bak.bak",
    "content": "if(POLICY CMP0087)\n\tcmake_policy(SET CMP0087 NEW)\nendif()\n\nset(OBS_STANDALONE_PLUGIN_DIR \"${CMAKE_SOURCE_DIR}/release\")\nset(INCLUDED_LIBOBS_CMAKE_MODULES ON)\n\ninclude(GNUInstallDirs)\nif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Darwin\")\n\tset(OS_MACOS ON)\n\tset(OS_POSIX ON)\nelseif(\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux|FreeBSD|OpenBSD\")\n\tset(OS_POSIX ON)\n\tstring(TOUPPER \"${CMAKE_SYSTEM_NAME}\" _SYSTEM_NAME_U)\n\tset(OS_${_SYSTEM_NAME_U} ON)\nelseif(\"${CMAKE_SYSTEM_NAME}\" STREQUAL \"Windows\")\n\tset(OS_WINDOWS ON)\n\tset(OS_POSIX OFF)\nendif()\n\n# Old-Style plugin detected, find \"modern\" libobs variant instead and set\n# global include directories to fix \"bad\" plugin behaviour\nif(DEFINED LIBOBS_INCLUDE_DIR AND NOT TARGET OBS::libobs)\n\tmessage(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.\")\n\tfind_package(libobs REQUIRED)\n\tif(TARGET OBS::libobs)\n\t\tset_target_properties(OBS::libobs PROPERTIES IMPORTED_GLOBAL TRUE)\n\t\tmessage(STATUS \"OBS: Using modern libobs target\")\n\n\t\tadd_library(libobs ALIAS OBS::libobs)\n\t\tif(OS_WINDOWS)\n\t\t\tadd_library(w32-pthreads ALIAS OBS::w32-pthreads)\n\t\tendif()\n\tendif()\nendif()\n\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND (OS_WINDOWS OR OS_MACOS))\n\tset(CMAKE_INSTALL_PREFIX \"${OBS_STANDALONE_PLUGIN_DIR}\" CACHE STRING \"Directory to install OBS plugin after building\" FORCE)\nendif()\n\nif(NOT CMAKE_BUILD_TYPE)\n\tset(CMAKE_BUILD_TYPE \"RelWithDebInfo\" CACHE STRING\n\t\t\"OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]\" FORCE)\n\tset_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel)\nendif()\n\nfile(RELATIVE_PATH RELATIVE_INSTALL_PATH \"${CMAKE_SOURCE_DIR}\" \"${CMAKE_INSTALL_PREFIX}\")\nfile(RELATIVE_PATH RELATIVE_BUILD_PATH \"${CMAKE_SOURCE_DIR}\" \"${CMAKE_BINARY_DIR}\")\n\n# Set-up OS-specific environment and helper functions\nif(OS_MACOS)\n\tif(NOT CMAKE_OSX_ARCHITECTURES)\n\t\tset(CMAKE_OSX_ARCHITECTURES \"x86_64\" CACHE STRING \"OBS plugin build architecture for macOS - x86_64 required at least\" FORCE)\n\tendif()\n\n\tif(NOT CMAKE_OSX_DEPLOYMENT_TARGET)\n\t\tset(CMAKE_OSX_DEPLOYMENT_TARGET \"10.13\" CACHE STRING \"OBS plugin deployment target for macOS - 10.13+ required\" FORCE)\n\tendif()\n\n\tif(NOT DEFINED OBS_CODESIGN_LINKER)\n\t\tset(OBS_CODESIGN_LINKER ON CACHE BOOL \"Enable linker code-signing on macOS (v11+ required\" FORCE)\n\tendif()\n\n\tif(NOT DEFINED OBS_BUNDLE_CODESIGN_IDENTITY)\n\t\tset(OBS_BUNDLE_CODESIGN_IDENTITY \"-\" CACHE STRING \"Codesign identity for macOS\" FORCE)\n\tendif()\n\n\t# Xcode configuration\n\tif(XCODE)\n\t\t# Tell Xcode to pretend the linker signed binaries so that\n\t\t# editing with install_name_tool preserves ad-hoc signatures.\n\t\t# See CMake Issue 21854.\n\t\t# This option is supported by codesign on macOS 11 or higher.\n\t\tset(CMAKE_XCODE_GENERATE_SCHEME ON)\n\t\tif(OBS_CODESIGN_LINKER)\n\t\t\tset(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS \"-o linker-signed\")\n\t\tendif()\n\tendif()\n\n\t# Set default options for bundling on macOS\n\tset(CMAKE_MACOSX_RPATH ON)\n\tset(CMAKE_SKIP_BUILD_RPATH OFF)\n\tset(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)\n\tset(CMAKE_INSTALL_RPATH \"@executable_path/../Frameworks/\")\n\tset(CMAKE_INSTALL_RPATH_USE_LINK_PATH OFF)\n\n\tset(OBS_PLUGIN_DESTINATION \"bin\")\n\tset(OBS_DATA_DESTINATION \"data\")\n\n\tfunction(setup_plugin_target target)\n\t\tinstall(TARGETS ${target}\n\t\t\tRUNTIME DESTINATION \"${target}/${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\tLIBRARY DESTINATION \"${target}/${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\t\tNAMELINK_COMPONENT ${target}_Development)\n\n\t\tinstall(CODE\n\t\t\t\"execute_process(COMMAND /bin/sh -c \\\"install_name_tool \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets.framework/Versions/5/QtWidgets \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore.framework/Versions/5/QtCore \\\\\n\t\t\t-change ${CMAKE_PREFIX_PATH}/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui.framework/Versions/5/QtGui \\\\\n\t\t\t${CMAKE_INSTALL_PREFIX}/${target}/bin\\\")\")\n\n\t\tinstall(CODE\n\t\t\t\"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\\\")\")\n\n\t\tsetup_target_resources(\"${target}\" \"${target}\")\n\tendfunction()\n\n\tfunction(setup_target_resources target destination)\n\t\tif(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/data\")\n\t\t\tinstall(DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/data/\"\n\t\t\t\tDESTINATION \"${destination}/${OBS_DATA_DESTINATION}\"\n\t\t\t\tUSE_SOURCE_PERMISSIONS)\n\t\tendif()\n\tendfunction()\nelse()\n\tif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\tset(_ARCH_SUFFIX 64)\n\telse()\n\t\tset(_ARCH_SUFFIX 32)\n\tendif()\n\tset(OBS_OUTPUT_DIR \"${CMAKE_BINARY_DIR}/rundir\")\n\n\tif(OS_POSIX)\n\t\tIF(NOT LINUX_PORTABLE)\n\t\t\tset(OBS_LIBRARY_DESTINATION \"${CMAKE_INSTALL_LIBDIR}\")\n\t\t\tset(OBS_PLUGIN_DESTINATION \"${OBS_LIBRARY_DESTINATION}/obs-plugins\")\n\t\t\tset(CMAKE_INSTALL_RPATH \"${CMAKE_INSTALL_PREFIX}/lib\")\n\t\t\tset(OBS_DATA_DESTINATION \"${CMAKE_INSTALL_DATAROOTDIR}/obs\")\n\t\telse()\n\t\t\tset(OBS_LIBRARY_DESTINATION \"bin/${_ARCH_SUFFIX}bit\")\n\t\t\tset(OBS_PLUGIN_DESTINATION \"obs-plugins/${_ARCH_SUFFIX}bit\")\n\t\t\tset(CMAKE_INSTALL_RPATH \"$ORIGIN/\" \"${CMAKE_INSTALL_PREFIX}/${OBS_LIBRARY_DESTINATION}\")\n\t\t\tset(OBS_DATA_DESTINATION \"data\")\n\t\tendif()\n\n\t\tif(OS_LINUX)\n\t\t\tset(CPACK_PACKAGE_NAME \"${CMAKE_PROJECT_NAME}\")\n\t\t\tset(CPACK_PACKAGE_VERSION \"${OBS_PLUGUIN_VERSION}\")\n\t\t\tset(CPACK_PACKAGE_CONTACT \"${LINUX_MAINTAINER} <${LINUX_MAINTAINER_EMAIL}>\")\n\t\t\tset(CPACK_PACKAGE_VENDOR \"${LINUX_MAINTAINER}\")\n\t\t\tset(CPACK_GENERATOR \"DEB\" \"TGZ\" \"RPM\")\n\n\t\t\tset(CPACK_DEBIAN_PACKAGE_DEPENDS\n\t\t\t\t\"obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)\")\n\t\t\tset(CPACK_DEBIAN_PACKAGE_SECTION \"video\")\n\n\t\t\tset(CPACK_RPM_PACKAGE_REQUIRES \"obs-studio >= 27.0.0, libQt5Core5 >= 5.9.0, libQt5Gui5 >= 5.3.0, libQt5Widgets5 >= 5.7.0\")\n\t\t\tset(CPACK_RPM_PACKAGE_GROUP \"Video\")\n\t\t\tset(CPACK_RPM_PACKAGE_LICENSE \"GPL-2.0\")\n\n\t\t\tif(NOT LINUX_PORTABLE)\n\t\t\t\t#set(CPACK_SET_DESTDIR ON)\n\t\t\tendif()\n\t\t\tinclude(CPack)\n\t\tendif()\n\telse()\n\t\tset(OBS_LIBRARY_DESTINATION \"bin/${_ARCH_SUFFIX}bit\")\n\t\tset(OBS_LIBRARY32_DESTINATION \"bin/32bit\")\n\t\tset(OBS_LIBRARY64_DESTINATION \"bin/64bit\")\n\t\tset(OBS_PLUGIN_DESTINATION \"obs-plugins/${_ARCH_SUFFIX}bit\")\n\t\tset(OBS_PLUGIN32_DESTINATION \"obs-plugins/32bit\")\n\t\tset(OBS_PLUGIN64_DESTINATION \"obs-plugins/64bit\")\n\n\t\tset(OBS_DATA_DESTINATION \"data\")\n\tendif()\n\n\tfunction(setup_plugin_target target)\n\t\tset_target_properties(${target} PROPERTIES\n\t\t\tPREFIX \"\")\n\n\t\tinstall(TARGETS ${target}\n\t\t\tRUNTIME DESTINATION \"${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\tLIBRARY DESTINATION \"${OBS_PLUGIN_DESTINATION}\"\n\t\t\t\tCOMPONENT ${target}_Runtime\n\t\t\t\tNAMELINK_COMPONENT ${target}_Development)\n\n\t\tif(OS_WINDOWS)\n\t\t\tinstall(FILES \n\t\t\t\t\"$<TARGET_PDB_FILE:${PROJECT_NAME}>\" DESTINATION \"${OBS_PLUGIN_DESTINATION}\" \n\t\t\t\tOPTIONAL)\n\t\tendif()\n\n\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy\n\t\t\t\t\"$<TARGET_FILE:${target}>\"\n\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\tVERBATIM)\n\n\t\tif(OS_WINDOWS)\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E \"$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,copy,true>\"\n\t\t\t\t\t\"$<TARGET_PDB_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tVERBATIM)\n\t\tendif()\n\n\t\tif(MSVC)\n\t\t\ttarget_link_options(${target}\n\t\t\t\tPRIVATE\n\t\t\t\t\t\"LINKER:/OPT:REF\"\n\t\t\t\t\t\"$<$<NOT:$<EQUAL:${CMAKE_SIZEOF_VOID_P},8>>:LINKER\\:/SAFESEH\\:NO>\"\n\t\t\t\t\t\"$<$<CONFIG:DEBUG>:LINKER\\:/INCREMENTAL:NO>\"\n\t\t\t\t\t\"$<$<CONFIG:RELWITHDEBINFO>:LINKER\\:/INCREMENTAL:NO>\")\n\t\tendif()\n\n\t\tsetup_target_resources(\"${target}\" \"obs-plugins/${target}\")\n\n\t\tif(OS_WINDOWS AND DEFINED OBS_BUILD_DIR)\n\t\t\tsetup_target_for_testing(${target})\n\t\tendif()\n\tendfunction()\n\n\tfunction(setup_target_resources target destination)\n\t\tif(EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/data\")\n\t\t\tinstall(DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/data/\"\n\t\t\t\tDESTINATION \"${OBS_DATA_DESTINATION}/${destination}\"\n\t\t\t\tUSE_SOURCE_PERMISSIONS)\n\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy_directory\n\t\t\t\t\t\"${CMAKE_CURRENT_SOURCE_DIR}/data\"\n\t\t\t\t\t\"${OBS_OUTPUT_DIR}/$<CONFIG>/${OBS_DATA_DESTINATION}/${destination}\"\n\t\t\t\tVERBATIM)\n\t\tendif()\n\tendfunction()\n\n\tif(OS_WINDOWS)\n\t\tfunction(setup_target_for_testing target)\n\t\t\tadd_custom_command(TARGET ${target} POST_BUILD\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy\n\t\t\t\t\t\"$<TARGET_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E \"$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,copy,true>\"\n\t\t\t\t\t\"$<TARGET_PDB_FILE:${target}>\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_PLUGIN_DESTINATION}/$<TARGET_FILE_NAME:${target}>\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E make_directory\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_DATA_DESTINATION}/obs-plugins/${target}\"\n\t\t\t\tCOMMAND \"${CMAKE_COMMAND}\" -E copy_directory\n\t\t\t\t\t\"${CMAKE_CURRENT_SOURCE_DIR}/data\"\n\t\t\t\t\t\"${OBS_BUILD_DIR}/rundir/$<CONFIG>/${OBS_DATA_DESTINATION}/obs-plugins/${target}\"\n\t\t\t\tVERBATIM)\n\t\tendfunction()\n\tendif()\nendif()\n"
  },
  {
    "path": "helper.h",
    "content": "#include <vector>\n#include <string>\n#include <obs-module.h>\n#include <util/config-file.h>\n#include <util/platform.h>\n#include <obs-avc.h>\n\n#pragma once\n\n#define CONFIG_SECTIION \"RstpOutput\"\n#define HOTKEY_CONFIG_SECTIION \"Hotkeys\"\n\nenum encoder_codec { UNKNOW = 0, H264 = 1, HEVC = 2, AV1 = 3, AAC = 4 };\n\n[[maybe_unused]] static bool make_config_dir()\n{\n\tauto path = obs_module_config_path(\"\");\n\tauto ret = os_mkdirs(path);\n\tbfree(path);\n\treturn ret == MKDIR_SUCCESS || ret == MKDIR_EXISTS;\n}\n\n[[maybe_unused]] static obs_data_t *rtsp_output_read_data()\n{\n\tobs_data_t *data;\n\tauto path = obs_module_config_path(\"rtsp_output.json\");\n\tdata = obs_data_create_from_json_file_safe(path, \"bak\");\n\tbfree(path);\n\treturn data;\n}\n\n[[maybe_unused]] static bool rtsp_output_save_data(obs_data_t *data)\n{\n\tif (!make_config_dir())\n\t\treturn false;\n\tauto path = obs_module_config_path(\"rtsp_output.json\");\n\tauto ret = obs_data_save_json_safe(data, path, \"tmp\", \"bak\");\n\tbfree(path);\n\treturn ret;\n}\n\n[[maybe_unused]] static config_t *rtsp_properties_open_config()\n{\n\tif (!make_config_dir())\n\t\treturn nullptr;\n\tauto path = obs_module_config_path(\"config.ini\");\n\tconfig_t *config;\n\tauto ret = config_open(&config, path, CONFIG_OPEN_ALWAYS);\n\tbfree(path);\n\tif (ret)\n\t\treturn nullptr;\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AutoStart\", false);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack1\", true);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack2\", false);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack3\", false);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack4\", false);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack5\", false);\n\tconfig_set_default_bool(config, CONFIG_SECTIION, \"AudioTrack6\", false);\n\treturn config;\n}\n\n[[maybe_unused]] static std::string string_format(char const *format, ...)\n{\n\tva_list argp;\n\tva_start(argp, format);\n\tauto size = (size_t)vsnprintf(nullptr, 0, format, argp) + 1;\n\tva_end(argp);\n\tauto buf = std::vector<char>(size);\n\tva_start(argp, format);\n\tvsnprintf(buf.data(), size, format, argp);\n\tva_end(argp);\n\treturn std::string(buf.data(), buf.data() + size - 1);\n}\n\n[[maybe_unused]] static std::string rtsp_properties_get_data_volume_display(uint64_t total_bytes)\n{\n\tconst uint64_t kb = 1024;\n\tconst uint64_t mb = kb * 1024;\n\tconst uint64_t gb = mb * 1024;\n\tconst uint64_t tb = gb * 1024;\n\tif (total_bytes == 0)\n\t\treturn \"0.0 MB\";\n\tif (total_bytes < kb) {\n\t\treturn string_format(\"%lu bytes\", total_bytes);\n\t}\n\tif (total_bytes < mb) {\n\t\treturn string_format(\"%.1f KB\", double(total_bytes) / kb);\n\t}\n\tif (total_bytes < gb) {\n\t\treturn string_format(\"%.1f MB\", double(total_bytes) / mb);\n\t}\n\tif (total_bytes < tb) {\n\t\treturn string_format(\"%.1f GB\", double(total_bytes) / gb);\n\t}\n\treturn string_format(\"%.1f TB\", double(total_bytes) / tb);\n}\n\n[[maybe_unused]] static encoder_codec get_encoder_codec(const obs_encoder_t *encoder)\n{\n\tconst char *const codec = obs_encoder_get_codec(encoder);\n\tif (strcmp(codec, \"h264\") == 0) {\n\t\treturn encoder_codec::H264;\n\t}\n\tif (strcmp(codec, \"hevc\") == 0) {\n\t\treturn encoder_codec::HEVC;\n\t}\n\tif (strcmp(codec, \"av1\") == 0) {\n\t\treturn encoder_codec::AV1;\n\t}\n\tif (strcmp(codec, \"aac\") == 0) {\n\t\treturn encoder_codec::AAC;\n\t}\n\treturn UNKNOW;\n}\n"
  },
  {
    "path": "installer/function/unprevious.nsi",
    "content": "﻿;Uninstall Previous\nFunction UninstallPrevious\n\n    ; Check for uninstaller.\n    ReadRegStr $R0 HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"UninstallString\"\n\tStrCpy $R1 $INSTDIR\n\tReadRegStr $R1 HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"InstallLocation\"\n\n    ${If} $R0 == \"\"\n        Return\n    ${EndIf}\n\n    DetailPrint $(LANGTEXT_REMOVING_PREV)\n\n    ; Run the uninstaller silently.\n    ExecWait '\"$R0\" /S _?=$R1' $0\n\n\t${If} $0 != 0\n\t\tAbort $(LANGTEXT_REMOVING_PREV_FAILED)\n    ${EndIf}\n\nFunctionEnd\n\n; eof\n"
  },
  {
    "path": "installer/installer.nsi",
    "content": "﻿Unicode true\n\n; Define your application name\n!define APPNAME \"obs-rtspserver\"\n!define DISPLAYNAME $(LANGTEXT_DISPLAYNAME)\n!ifndef APPVERSION\n!define APPVERSION ${FVERSION}\n!define SHORTVERSION ${VERSION}\n!define PLANTFORMNAME ${PLANTFORM}\n!endif\n!define APPNAMEANDVERSION \"${DISPLAYNAME} v${SHORTVERSION}\"\n\n; Main Install settings\nName \"${APPNAMEANDVERSION}\"\nInstallDir \"$PROGRAMFILES64\\obs-studio\"\nInstallDirRegKey HKLM \"Software\\OBS Studio\" \"\"\nOutFile \"..\\obs-rtspserver-v${SHORTVERSION}-windows-${PLANTFORMNAME}-installer.exe\"\n\n; Use compression\nSetCompressor LZMA\n\n; Modern interface settings\n!include MUI2.nsh\n\n; Include nsDialogs\n!include nsDialogs.nsh\n\n; Include library for dll stuff\n!include Library.nsh\n\n; Include uninstall Previous function\n!include .\\function\\unprevious.nsi\n\n!define MUI_ICON \"obs.ico\"\n!define MUI_UNICON \"obs.ico\"\n;!define MUI_HEADERIMAGE\n;!define MUI_HEADERIMAGE_BITMAP \"\"\n;!define MUI_HEADERIMAGE_RIGHT\n!define MUI_ABORTWARNING\n\n!define MUI_PAGE_HEADER_TEXT $(LANGTEXT_HEADER_TEXT)\n!define MUI_PAGE_HEADER_SUBTEXT $(LANGTEXT_HEADER_SUBTEXT)\n!define MUI_LICENSEPAGE_TEXT_TOP $(LANGTEXT_LICENSEPAGE_TEXT_TOP)\n!define MUI_LICENSEPAGE_TEXT_BOTTOM \" \"\n!define MUI_LICENSEPAGE_BUTTON $(LANGTEXT_LICENSEPAGE_BUTTON)\n!define MUI_FINISHPAGE_NOAUTOCLOSE\n!define MUI_HEADER_TRANSPARENT_TEXT\n\n!insertmacro MUI_PAGE_WELCOME\n!insertmacro MUI_PAGE_LICENSE \"LICENSE\"\n!insertmacro MUI_PAGE_INSTFILES\n!insertmacro MUI_PAGE_FINISH\n\n!insertmacro MUI_UNPAGE_WELCOME\n!insertmacro MUI_UNPAGE_COMPONENTS\n!insertmacro MUI_UNPAGE_CONFIRM\n!insertmacro MUI_UNPAGE_INSTFILES\n!insertmacro MUI_UNPAGE_FINISH\n\n!include .\\locale.nsi\n\n!insertmacro MUI_RESERVEFILE_LANGDLL\n\nSection -SecUninstallPrevious\n\n    Call UninstallPrevious\n\t\nSectionEnd\n\nSection \"!OBS RTSP Server\"\n\n\tSectionIn RO\n\t; Set Section properties\n\tSetOverwrite on\n\t; Set Section Files and Shortcuts\n\tSetOutPath \"$INSTDIR\\\"\n\tFile /r \"..\\release\\\"\n\t\nSectionEnd\n\nSection -FinishSection\n\n\tWriteRegStr HKLM \"Software\\${APPNAME}\" \"\" \"$INSTDIR\"\n\tWriteRegStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"DisplayName\" \"${DISPLAYNAME}\"\n\tWriteRegExpandStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"InstallLocation\" \"$INSTDIR\"\n\tWriteRegExpandStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"UninstallString\" \"$INSTDIR\\uninstall_obs-rtspserver.exe\"\n\tWriteRegExpandStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"DisplayIcon\" \"$INSTDIR\\uninstall_obs-rtspserver.exe,0\"\n\tWriteRegStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"URLInfoAbout\" \"https://obsproject.com/forum/resources/obs-rtspserver.1037/\"\n\tWriteRegStr HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\" \"DisplayVersion\" \"${SHORTVERSION}\"\n\tWriteUninstaller \"$INSTDIR\\uninstall_obs-rtspserver.exe\"\n\t\nSectionEnd\n\n;Uninstall section\nSection \"!un.OBS RTSP Server\"\n\n\tSectionIn RO\n\n\t; Clean up obs-rtspserver\n\tDelete \"$INSTDIR\\obs-plugins\\64bit\\obs-rtspserver.dll\"\n\tDelete \"$INSTDIR\\obs-plugins\\64bit\\obs-rtspserver.pdb\"\n\tDelete \"$INSTDIR\\obs-plugins\\32bit\\obs-rtspserver.dll\"\n\tDelete \"$INSTDIR\\obs-plugins\\32bit\\obs-rtspserver.pdb\"\n\n\t; Remove data directory\n\tRMDir /r \"$INSTDIR\\data\\obs-plugins\\obs-rtspserver\\\"\n\nSectionEnd\n\nSection -un.FinishSection\n\n\t;Remove from registry...\n\tDeleteRegKey HKLM \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${APPNAME}\"\n\tDeleteRegKey HKLM \"SOFTWARE\\${APPNAME}\"\n\t\n\t; Delete self\n\tDelete \"$INSTDIR\\uninstall_obs-rtspserver.exe\"\n\nSectionEnd\n\nFunction .onInit\n\n\t!insertmacro MUI_LANGDLL_DISPLAY\n\nFunctionEnd\n\n;version information\nVIAddVersionKey /LANG=${LANG_ENGLISH} \"ProductName\" \"OBS Studio\"\nVIAddVersionKey /LANG=${LANG_ENGLISH} \"FileDescription\" \"OBS RTSP Server Plugin Installer\"\nVIAddVersionKey /LANG=${LANG_ENGLISH} \"OriginalFilename\" \"obs-rtspserver-v${SHORTVERSION}-windows-installer.exe\"\nVIAddVersionKey /LANG=${LANG_ENGLISH} \"ProductVersion\" \"${APPVERSION}\"\n\nVIProductVersion \"${APPVERSION}\""
  },
  {
    "path": "installer/locale/de-DE.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_GERMAN} \"OBS RTSP Server Plugin\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_GERMAN} \"Lizenzinformationen\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_GERMAN} \"Bitte überprüfen Sie die Lizenzbedingungen, bevor Sie ${DISPLAYNAME} installieren.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_GERMAN} \"Drücken Sie auf die Bild Unten-Taste oder scrollen Sie, um den Rest der Lizenz anzuzeigen.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_GERMAN} \"&Weiter >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_GERMAN} \"Vorherige Installation wird entfernt...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_GERMAN} \"Fehler beim Entfernen der vorherigen Installation.\"\n"
  },
  {
    "path": "installer/locale/en-US.nsi",
    "content": "﻿LangString LANGTEXT_DISPLAYNAME ${LANG_ENGLISH} \"OBS RTSP Server Plugin\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_ENGLISH} \"License Information\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_ENGLISH} \"Please review the license terms before installing ${DISPLAYNAME}.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_ENGLISH} \"Press Page Down or scroll to see the rest of the license.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_ENGLISH} \"&Next >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_ENGLISH} \"Removing previous installation...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_ENGLISH} \"Failed to remove previous installation.\"\n"
  },
  {
    "path": "installer/locale/es-ES.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_SPANISH} \"OBS RTSP Server Plugin\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_SPANISH} \"Información de licencia\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_SPANISH} \"Revise los términos de la licencia antes de instalar ${DISPLAYNAME}.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_SPANISH} \"Presione Av Pág o desplácese para ver el resto de la licencia.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_SPANISH} \"&Siguiente >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_SPANISH} \"Eliminando la instalación anterior ...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_SPANISH} \"No se pudo eliminar la instalación anterior.\"\n"
  },
  {
    "path": "installer/locale/fr-FR.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_FRENCH} \"Module d'extension de serveur RTSP OBS\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_FRENCH} \"Informations de licence\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_FRENCH} \"Veuillez vérifier les termes de la licence avant d'installer ${DISPLAYNAME}.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_FRENCH} \"Appuyez sur le bouton page bas ou faites défiler pour afficher le reste de la licence.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_FRENCH} \"&Suivant >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_FRENCH} \"Suppression de l'installation précédente...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_FRENCH} \"Échec de la suppression de l'installation précédente.\"\n"
  },
  {
    "path": "installer/locale/it-IT.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_ITALIAN} \"OBS RTSP Server Plugin\"\r\nLangString LANGTEXT_HEADER_TEXT ${LANG_ITALIAN} \"Informazioni sulla licenza\"\r\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_ITALIAN} \"Si prega di rivedere i termini della licenza prima di installare ${DISPLAYNAME}.\"\r\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_ITALIAN} \"Premi [Pagina Giù] o scorri per vedere il resto della licenza.\"\r\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_ITALIAN} \"&Seguente >\"\r\nLangString LANGTEXT_REMOVING_PREV ${LANG_ITALIAN} \"Rimozione dell'installazione precedente...\"\r\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_ITALIAN} \"Impossibile rimuovere l'installazione precedente.\"\r\n"
  },
  {
    "path": "installer/locale/ja-JP.nsi",
    "content": "﻿LangString LANGTEXT_DISPLAYNAME ${LANG_JAPANESE} \"OBS RTSPサーバープラグイン\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_JAPANESE} \"ライセンス情報\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_JAPANESE} \"${DISPLAYNAME} をインストールする前にライセンス条項を確認してください。\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_JAPANESE} \"「PageDown」を押すか、スクロールして残りのライセンスを表示します。\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_JAPANESE} \"&次 >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_JAPANESE} \"以前のインストールを削除します...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_JAPANESE} \"以前のインストールを削除できませんでした。\"\n"
  },
  {
    "path": "installer/locale/ko-KR.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_KOREAN} \"OBS RTSP 서버 플러그인\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_KOREAN} \"라이선스 정보\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_KOREAN} \"${DISPLAYNAME}를 설치하기 전에 사용 조건을 검토하십시오.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_KOREAN} \"[Page Down]을 누르거나 스크롤하여 나머지 라이선스를 확인합니다.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_KOREAN} \"&다음 >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_KOREAN} \"이전 설치 제거 중 ...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_KOREAN} \"이전 설치를 제거하지 못했습니다.\"\n"
  },
  {
    "path": "installer/locale/nl-NL.nsi",
    "content": "LangString LANGTEXT_DISPLAYNAME ${LANG_DUTCH} \"OBS RTSP Server Plugin\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_DUTCH} \"Licentie-informatie\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_DUTCH} \"Lees de licentievoorwaarden voordat u gaat installeren ${DISPLAYNAME}.\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_DUTCH} \"Druk op Page Down of scroll om de rest van de licentie te zien.\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_DUTCH} \"&Volgende >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_DUTCH} \"Vorige installatie verwijderen...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_DUTCH} \"Vorige installatie kan niet worden verwijderd.\"\n"
  },
  {
    "path": "installer/locale/zh-CN.nsi",
    "content": "﻿LangString LANGTEXT_DISPLAYNAME ${LANG_SIMPCHINESE} \"OBS RTSP 服务器插件\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_SIMPCHINESE} \"许可证信息\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_SIMPCHINESE} \"请在安装 ${DISPLAYNAME} 之前查看许可条款。\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_SIMPCHINESE} \"按 Page Down 或滚动查看许可证的其余部分。\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_SIMPCHINESE} \"下一步(&N) >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_SIMPCHINESE} \"正在删除以前的安装...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_SIMPCHINESE} \"无法删除以前的安装。\"\n"
  },
  {
    "path": "installer/locale/zh-TW.nsi",
    "content": "﻿LangString LANGTEXT_DISPLAYNAME ${LANG_TRADCHINESE} \"OBS RTSP 伺服器外掛程式\"\nLangString LANGTEXT_HEADER_TEXT ${LANG_TRADCHINESE} \"許可證資訊\"\nLangString LANGTEXT_HEADER_SUBTEXT ${LANG_TRADCHINESE} \"請在安裝 ${DISPLAYNAME} 之前查看許可條款。\"\nLangString LANGTEXT_LICENSEPAGE_TEXT_TOP ${LANG_TRADCHINESE} \"按 Page Down 或滾動查看許可證的其餘部分。\"\nLangString LANGTEXT_LICENSEPAGE_BUTTON ${LANG_TRADCHINESE} \"下一步(&N) >\"\nLangString LANGTEXT_REMOVING_PREV ${LANG_TRADCHINESE} \"正在删除以前的安裝...\"\nLangString LANGTEXT_REMOVING_PREV_FAILED ${LANG_TRADCHINESE} \"無法刪除以前的安裝。\"\n"
  },
  {
    "path": "installer/locale.nsi",
    "content": "﻿; Set languages (first is default language)\n!insertmacro MUI_LANGUAGE \"English\"\n!insertmacro MUI_LANGUAGE \"SimpChinese\"\n!insertmacro MUI_LANGUAGE \"TradChinese\"\n!insertmacro MUI_LANGUAGE \"German\"\n!insertmacro MUI_LANGUAGE \"Spanish\"\n!insertmacro MUI_LANGUAGE \"Dutch\"\n!insertmacro MUI_LANGUAGE \"French\"\n!insertmacro MUI_LANGUAGE \"Japanese\"\n!insertmacro MUI_LANGUAGE \"Italian\"\n\n; Locale List\n!include .\\locale\\en-US.nsi\n!include .\\locale\\zh-CN.nsi\n!include .\\locale\\zh-TW.nsi\n!include .\\locale\\de-DE.nsi\n!include .\\locale\\es-ES.nsi\n!include .\\locale\\nl-NL.nsi\n!include .\\locale\\fr-FR.nsi\n!include .\\locale\\ja-JP.nsi\n!include .\\locale\\it-IT.nsi\n"
  },
  {
    "path": "rtsp-server/3rdpart/libb64/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5)\nproject(libb64)\n\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\ninclude_directories(\"libb64/include\")\n\nfile(GLOB libb64_SOURCES\n\tlibb64/src/*.c)\n\nfile(GLOB libb64_HEADERS\n\tlibb64/include/b64/*.h)\n\nadd_library(libb64 STATIC\n\t${libb64_SOURCES}\n\t${libb64_HEADERS})\n"
  },
  {
    "path": "rtsp-server/3rdpart/md5/COPYING",
    "content": "Main Library:\n\nCopyright (c) 2014, Peter Thorson. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of the WebSocket++ Project nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nBundled Libraries:\n\n****** Base 64 Library (base64/base64.hpp) ******\nbase64.hpp is a repackaging of the base64.cpp and base64.h files into a\nsingle header suitable for use as a header only library. This conversion was\ndone by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to\nthe code are redistributed under the same license as the original, which is\nlisted below.\n\nbase64.cpp and base64.h\n\nCopyright (C) 2004-2008 René Nyffenegger\n\nThis source code is provided 'as-is', without any express or implied\nwarranty. In no event will the author be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this source code must not be misrepresented; you must not\n  claim that you wrote the original source code. If you use this source code\n  in a product, an acknowledgment in the product documentation would be\n  appreciated but is not required.\n\n2. Altered source versions must be plainly marked as such, and must not be\n  misrepresented as being the original source code.\n\n3. This notice may not be removed or altered from any source distribution.\n\nRené Nyffenegger rene.nyffenegger@adp-gmbh.ch\n\n****** SHA1 Library (sha1/sha1.hpp) ******\nsha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1\nlibrary (http://code.google.com/p/smallsha1/) into a single header suitable for\nuse as a header only library. This conversion was done by Peter Thorson\n(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed\nunder the same license as the original, which is listed below.\n\n Copyright (c) 2011, Micael Hildenborg\n All rights reserved.\n\n Redistribution and use in source and binary forms, with or without\n modification, are permitted provided that the following conditions are met:\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above copyright\n      notice, this list of conditions and the following disclaimer in the\n      documentation and/or other materials provided with the distribution.\n    * Neither the name of Micael Hildenborg nor the\n      names of its contributors may be used to endorse or promote products\n      derived from this software without specific prior written permission.\n\n THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY\n EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY\n DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n****** MD5 Library (common/md5.hpp) ******\nmd5.hpp is a reformulation of the md5.h and md5.c code from\nhttp://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to\nfunction as a component of a header only library. This conversion was done by\nPeter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The\nchanges are released under the same license as the original (listed below)\n\nCopyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.\n\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n claim that you wrote the original software. If you use this software\n in a product, an acknowledgment in the product documentation would be\n appreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\n misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\nL. Peter Deutsch\nghost@aladdin.com\n\n****** UTF8 Validation logic (utf8_validation.hpp) ******\nutf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann\n<bjoern@hoehrmann.de>. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for\ndetails.\n\nThe original license:\n\nCopyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "rtsp-server/3rdpart/md5/md5.hpp",
    "content": "/*\n  md5.hpp is a reformulation of the md5.h and md5.c code from\n  http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to\n  function as a component of a header only library. This conversion was done by\n  Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The\n  changes are released under the same license as the original (listed below)\n*/\n/*\n  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n  L. Peter Deutsch\n  ghost@aladdin.com\n\n */\n/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */\n/*\n  Independent implementation of MD5 (RFC 1321).\n\n  This code implements the MD5 Algorithm defined in RFC 1321, whose\n  text is available at\n    http://www.ietf.org/rfc/rfc1321.txt\n  The code is derived from the text of the RFC, including the test suite\n  (section A.5) but excluding the rest of Appendix A.  It does not include\n  any code or documentation that is identified in the RFC as being\n  copyrighted.\n\n  The original and principal author of md5.h is L. Peter Deutsch\n  <ghost@aladdin.com>.  Other authors are noted in the change history\n  that follows (in reverse chronological order):\n\n  2002-04-13 lpd Removed support for non-ANSI compilers; removed\n    references to Ghostscript; clarified derivation from RFC 1321;\n    now handles byte order either statically or dynamically.\n  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.\n  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);\n    added conditionalization for C++ compilation from Martin\n    Purschke <purschke@bnl.gov>.\n  1999-05-03 lpd Original version.\n */\n\n#ifndef WEBSOCKETPP_COMMON_MD5_HPP\n#define WEBSOCKETPP_COMMON_MD5_HPP\n\n/*\n * This package supports both compile-time and run-time determination of CPU\n * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be\n * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is\n * defined as non-zero, the code will be compiled to run only on big-endian\n * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to\n * run on either big- or little-endian CPUs, but will run slightly less\n * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.\n */\n\n#include <stddef.h>\n#include <string>\n#include <cstring>\n#include <chrono>\n#include <cstdint>\n#include <random>\n\n//namespace websocketpp {\n/// Provides MD5 hashing functionality\nnamespace md5 {\n\ntypedef unsigned char md5_byte_t; /* 8-bit byte */\ntypedef unsigned int md5_word_t;  /* 32-bit word */\n\n/* Define the state of the MD5 Algorithm. */\ntypedef struct md5_state_s {\n\tmd5_word_t count[2]; /* message length in bits, lsw first */\n\tmd5_word_t abcd[4];  /* digest buffer */\n\tmd5_byte_t buf[64];  /* accumulate block */\n} md5_state_t;\n\n/* Initialize the algorithm. */\ninline void md5_init(md5_state_t *pms);\n\n/* Append a string to the message. */\ninline void md5_append(md5_state_t *pms, md5_byte_t const *data, size_t nbytes);\n\n/* Finish the message and return the digest. */\ninline void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);\n\n#undef ZSW_MD5_BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */\n#ifdef ARCH_IS_BIG_ENDIAN\n#define ZSW_MD5_BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)\n#else\n#define ZSW_MD5_BYTE_ORDER 0\n#endif\n\n#define ZSW_MD5_T_MASK ((md5_word_t)~0)\n#define ZSW_MD5_T1 /* 0xd76aa478 */ (ZSW_MD5_T_MASK ^ 0x28955b87)\n#define ZSW_MD5_T2 /* 0xe8c7b756 */ (ZSW_MD5_T_MASK ^ 0x173848a9)\n#define ZSW_MD5_T3 0x242070db\n#define ZSW_MD5_T4 /* 0xc1bdceee */ (ZSW_MD5_T_MASK ^ 0x3e423111)\n#define ZSW_MD5_T5 /* 0xf57c0faf */ (ZSW_MD5_T_MASK ^ 0x0a83f050)\n#define ZSW_MD5_T6 0x4787c62a\n#define ZSW_MD5_T7 /* 0xa8304613 */ (ZSW_MD5_T_MASK ^ 0x57cfb9ec)\n#define ZSW_MD5_T8 /* 0xfd469501 */ (ZSW_MD5_T_MASK ^ 0x02b96afe)\n#define ZSW_MD5_T9 0x698098d8\n#define ZSW_MD5_T10 /* 0x8b44f7af */ (ZSW_MD5_T_MASK ^ 0x74bb0850)\n#define ZSW_MD5_T11 /* 0xffff5bb1 */ (ZSW_MD5_T_MASK ^ 0x0000a44e)\n#define ZSW_MD5_T12 /* 0x895cd7be */ (ZSW_MD5_T_MASK ^ 0x76a32841)\n#define ZSW_MD5_T13 0x6b901122\n#define ZSW_MD5_T14 /* 0xfd987193 */ (ZSW_MD5_T_MASK ^ 0x02678e6c)\n#define ZSW_MD5_T15 /* 0xa679438e */ (ZSW_MD5_T_MASK ^ 0x5986bc71)\n#define ZSW_MD5_T16 0x49b40821\n#define ZSW_MD5_T17 /* 0xf61e2562 */ (ZSW_MD5_T_MASK ^ 0x09e1da9d)\n#define ZSW_MD5_T18 /* 0xc040b340 */ (ZSW_MD5_T_MASK ^ 0x3fbf4cbf)\n#define ZSW_MD5_T19 0x265e5a51\n#define ZSW_MD5_T20 /* 0xe9b6c7aa */ (ZSW_MD5_T_MASK ^ 0x16493855)\n#define ZSW_MD5_T21 /* 0xd62f105d */ (ZSW_MD5_T_MASK ^ 0x29d0efa2)\n#define ZSW_MD5_T22 0x02441453\n#define ZSW_MD5_T23 /* 0xd8a1e681 */ (ZSW_MD5_T_MASK ^ 0x275e197e)\n#define ZSW_MD5_T24 /* 0xe7d3fbc8 */ (ZSW_MD5_T_MASK ^ 0x182c0437)\n#define ZSW_MD5_T25 0x21e1cde6\n#define ZSW_MD5_T26 /* 0xc33707d6 */ (ZSW_MD5_T_MASK ^ 0x3cc8f829)\n#define ZSW_MD5_T27 /* 0xf4d50d87 */ (ZSW_MD5_T_MASK ^ 0x0b2af278)\n#define ZSW_MD5_T28 0x455a14ed\n#define ZSW_MD5_T29 /* 0xa9e3e905 */ (ZSW_MD5_T_MASK ^ 0x561c16fa)\n#define ZSW_MD5_T30 /* 0xfcefa3f8 */ (ZSW_MD5_T_MASK ^ 0x03105c07)\n#define ZSW_MD5_T31 0x676f02d9\n#define ZSW_MD5_T32 /* 0x8d2a4c8a */ (ZSW_MD5_T_MASK ^ 0x72d5b375)\n#define ZSW_MD5_T33 /* 0xfffa3942 */ (ZSW_MD5_T_MASK ^ 0x0005c6bd)\n#define ZSW_MD5_T34 /* 0x8771f681 */ (ZSW_MD5_T_MASK ^ 0x788e097e)\n#define ZSW_MD5_T35 0x6d9d6122\n#define ZSW_MD5_T36 /* 0xfde5380c */ (ZSW_MD5_T_MASK ^ 0x021ac7f3)\n#define ZSW_MD5_T37 /* 0xa4beea44 */ (ZSW_MD5_T_MASK ^ 0x5b4115bb)\n#define ZSW_MD5_T38 0x4bdecfa9\n#define ZSW_MD5_T39 /* 0xf6bb4b60 */ (ZSW_MD5_T_MASK ^ 0x0944b49f)\n#define ZSW_MD5_T40 /* 0xbebfbc70 */ (ZSW_MD5_T_MASK ^ 0x4140438f)\n#define ZSW_MD5_T41 0x289b7ec6\n#define ZSW_MD5_T42 /* 0xeaa127fa */ (ZSW_MD5_T_MASK ^ 0x155ed805)\n#define ZSW_MD5_T43 /* 0xd4ef3085 */ (ZSW_MD5_T_MASK ^ 0x2b10cf7a)\n#define ZSW_MD5_T44 0x04881d05\n#define ZSW_MD5_T45 /* 0xd9d4d039 */ (ZSW_MD5_T_MASK ^ 0x262b2fc6)\n#define ZSW_MD5_T46 /* 0xe6db99e5 */ (ZSW_MD5_T_MASK ^ 0x1924661a)\n#define ZSW_MD5_T47 0x1fa27cf8\n#define ZSW_MD5_T48 /* 0xc4ac5665 */ (ZSW_MD5_T_MASK ^ 0x3b53a99a)\n#define ZSW_MD5_T49 /* 0xf4292244 */ (ZSW_MD5_T_MASK ^ 0x0bd6ddbb)\n#define ZSW_MD5_T50 0x432aff97\n#define ZSW_MD5_T51 /* 0xab9423a7 */ (ZSW_MD5_T_MASK ^ 0x546bdc58)\n#define ZSW_MD5_T52 /* 0xfc93a039 */ (ZSW_MD5_T_MASK ^ 0x036c5fc6)\n#define ZSW_MD5_T53 0x655b59c3\n#define ZSW_MD5_T54 /* 0x8f0ccc92 */ (ZSW_MD5_T_MASK ^ 0x70f3336d)\n#define ZSW_MD5_T55 /* 0xffeff47d */ (ZSW_MD5_T_MASK ^ 0x00100b82)\n#define ZSW_MD5_T56 /* 0x85845dd1 */ (ZSW_MD5_T_MASK ^ 0x7a7ba22e)\n#define ZSW_MD5_T57 0x6fa87e4f\n#define ZSW_MD5_T58 /* 0xfe2ce6e0 */ (ZSW_MD5_T_MASK ^ 0x01d3191f)\n#define ZSW_MD5_T59 /* 0xa3014314 */ (ZSW_MD5_T_MASK ^ 0x5cfebceb)\n#define ZSW_MD5_T60 0x4e0811a1\n#define ZSW_MD5_T61 /* 0xf7537e82 */ (ZSW_MD5_T_MASK ^ 0x08ac817d)\n#define ZSW_MD5_T62 /* 0xbd3af235 */ (ZSW_MD5_T_MASK ^ 0x42c50dca)\n#define ZSW_MD5_T63 0x2ad7d2bb\n#define ZSW_MD5_T64 /* 0xeb86d391 */ (ZSW_MD5_T_MASK ^ 0x14792c6e)\n\nstatic void md5_process(md5_state_t *pms, md5_byte_t const *data /*[64]*/)\n{\n\tmd5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2],\n\t\t   d = pms->abcd[3];\n\tmd5_word_t t;\n#if ZSW_MD5_BYTE_ORDER > 0\n\t/* Define storage only for big-endian CPUs. */\n\tmd5_word_t X[16];\n#else\n\t/* Define storage for little-endian or both types of CPUs. */\n\tmd5_word_t xbuf[16];\n\tmd5_word_t const *X;\n#endif\n\n\t{\n#if ZSW_MD5_BYTE_ORDER == 0\n\t\t/*\n     * Determine dynamically whether this is a big-endian or\n     * little-endian machine, since we can use a more efficient\n     * algorithm on the latter.\n     */\n\t\tstatic int const w = 1;\n\n\t\tif (*((md5_byte_t const *)&w)) /* dynamic little-endian */\n#endif\n#if ZSW_MD5_BYTE_ORDER <= 0 /* little-endian */\n\t\t{\n\t\t\t/*\n         * On little-endian machines, we can process properly aligned\n         * data without copying it.\n         */\n\t\t\tif (!((data - (md5_byte_t const *)0) & 3)) {\n\t\t\t\t/* data are properly aligned */\n\t\t\t\tX = (md5_word_t const *)data;\n\t\t\t} else {\n\t\t\t\t/* not aligned */\n\t\t\t\tstd::memcpy(xbuf, data, 64);\n\t\t\t\tX = xbuf;\n\t\t\t}\n\t\t}\n#endif\n#if ZSW_MD5_BYTE_ORDER == 0\n\t\telse /* dynamic big-endian */\n#endif\n#if ZSW_MD5_BYTE_ORDER >= 0 /* big-endian */\n\t\t{\n\t\t\t/*\n         * On big-endian machines, we must arrange the bytes in the\n         * right order.\n         */\n\t\t\tconst md5_byte_t *xp = data;\n\t\t\tint i;\n\n#if ZSW_MD5_BYTE_ORDER == 0\n\t\t\tX = xbuf; /* (dynamic only) */\n#else\n#define xbuf X /* (static only) */\n#endif\n\t\t\tfor (i = 0; i < 16; ++i, xp += 4)\n\t\t\t\txbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) +\n\t\t\t\t\t  (xp[3] << 24);\n\t\t}\n#endif\n\t}\n\n#define ZSW_MD5_ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))\n\n\t/* Round 1. */\n\t/* Let [abcd k s i] denote the operation\n       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */\n#define ZSW_MD5_F(x, y, z) (((x) & (y)) | (~(x) & (z)))\n#define SET(a, b, c, d, k, s, Ti)               \\\n\tt = a + ZSW_MD5_F(b, c, d) + X[k] + Ti; \\\n\ta = ZSW_MD5_ROTATE_LEFT(t, s) + b\n\t/* Do the following 16 operations. */\n\tSET(a, b, c, d, 0, 7, ZSW_MD5_T1);\n\tSET(d, a, b, c, 1, 12, ZSW_MD5_T2);\n\tSET(c, d, a, b, 2, 17, ZSW_MD5_T3);\n\tSET(b, c, d, a, 3, 22, ZSW_MD5_T4);\n\tSET(a, b, c, d, 4, 7, ZSW_MD5_T5);\n\tSET(d, a, b, c, 5, 12, ZSW_MD5_T6);\n\tSET(c, d, a, b, 6, 17, ZSW_MD5_T7);\n\tSET(b, c, d, a, 7, 22, ZSW_MD5_T8);\n\tSET(a, b, c, d, 8, 7, ZSW_MD5_T9);\n\tSET(d, a, b, c, 9, 12, ZSW_MD5_T10);\n\tSET(c, d, a, b, 10, 17, ZSW_MD5_T11);\n\tSET(b, c, d, a, 11, 22, ZSW_MD5_T12);\n\tSET(a, b, c, d, 12, 7, ZSW_MD5_T13);\n\tSET(d, a, b, c, 13, 12, ZSW_MD5_T14);\n\tSET(c, d, a, b, 14, 17, ZSW_MD5_T15);\n\tSET(b, c, d, a, 15, 22, ZSW_MD5_T16);\n#undef SET\n\n\t/* Round 2. */\n\t/* Let [abcd k s i] denote the operation\n          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */\n#define ZSW_MD5_G(x, y, z) (((x) & (z)) | ((y) & ~(z)))\n#define SET(a, b, c, d, k, s, Ti)               \\\n\tt = a + ZSW_MD5_G(b, c, d) + X[k] + Ti; \\\n\ta = ZSW_MD5_ROTATE_LEFT(t, s) + b\n\t/* Do the following 16 operations. */\n\tSET(a, b, c, d, 1, 5, ZSW_MD5_T17);\n\tSET(d, a, b, c, 6, 9, ZSW_MD5_T18);\n\tSET(c, d, a, b, 11, 14, ZSW_MD5_T19);\n\tSET(b, c, d, a, 0, 20, ZSW_MD5_T20);\n\tSET(a, b, c, d, 5, 5, ZSW_MD5_T21);\n\tSET(d, a, b, c, 10, 9, ZSW_MD5_T22);\n\tSET(c, d, a, b, 15, 14, ZSW_MD5_T23);\n\tSET(b, c, d, a, 4, 20, ZSW_MD5_T24);\n\tSET(a, b, c, d, 9, 5, ZSW_MD5_T25);\n\tSET(d, a, b, c, 14, 9, ZSW_MD5_T26);\n\tSET(c, d, a, b, 3, 14, ZSW_MD5_T27);\n\tSET(b, c, d, a, 8, 20, ZSW_MD5_T28);\n\tSET(a, b, c, d, 13, 5, ZSW_MD5_T29);\n\tSET(d, a, b, c, 2, 9, ZSW_MD5_T30);\n\tSET(c, d, a, b, 7, 14, ZSW_MD5_T31);\n\tSET(b, c, d, a, 12, 20, ZSW_MD5_T32);\n#undef SET\n\n\t/* Round 3. */\n\t/* Let [abcd k s t] denote the operation\n          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */\n#define ZSW_MD5_H(x, y, z) ((x) ^ (y) ^ (z))\n#define SET(a, b, c, d, k, s, Ti)               \\\n\tt = a + ZSW_MD5_H(b, c, d) + X[k] + Ti; \\\n\ta = ZSW_MD5_ROTATE_LEFT(t, s) + b\n\t/* Do the following 16 operations. */\n\tSET(a, b, c, d, 5, 4, ZSW_MD5_T33);\n\tSET(d, a, b, c, 8, 11, ZSW_MD5_T34);\n\tSET(c, d, a, b, 11, 16, ZSW_MD5_T35);\n\tSET(b, c, d, a, 14, 23, ZSW_MD5_T36);\n\tSET(a, b, c, d, 1, 4, ZSW_MD5_T37);\n\tSET(d, a, b, c, 4, 11, ZSW_MD5_T38);\n\tSET(c, d, a, b, 7, 16, ZSW_MD5_T39);\n\tSET(b, c, d, a, 10, 23, ZSW_MD5_T40);\n\tSET(a, b, c, d, 13, 4, ZSW_MD5_T41);\n\tSET(d, a, b, c, 0, 11, ZSW_MD5_T42);\n\tSET(c, d, a, b, 3, 16, ZSW_MD5_T43);\n\tSET(b, c, d, a, 6, 23, ZSW_MD5_T44);\n\tSET(a, b, c, d, 9, 4, ZSW_MD5_T45);\n\tSET(d, a, b, c, 12, 11, ZSW_MD5_T46);\n\tSET(c, d, a, b, 15, 16, ZSW_MD5_T47);\n\tSET(b, c, d, a, 2, 23, ZSW_MD5_T48);\n#undef SET\n\n\t/* Round 4. */\n\t/* Let [abcd k s t] denote the operation\n          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */\n#define ZSW_MD5_I(x, y, z) ((y) ^ ((x) | ~(z)))\n#define SET(a, b, c, d, k, s, Ti)               \\\n\tt = a + ZSW_MD5_I(b, c, d) + X[k] + Ti; \\\n\ta = ZSW_MD5_ROTATE_LEFT(t, s) + b\n\t/* Do the following 16 operations. */\n\tSET(a, b, c, d, 0, 6, ZSW_MD5_T49);\n\tSET(d, a, b, c, 7, 10, ZSW_MD5_T50);\n\tSET(c, d, a, b, 14, 15, ZSW_MD5_T51);\n\tSET(b, c, d, a, 5, 21, ZSW_MD5_T52);\n\tSET(a, b, c, d, 12, 6, ZSW_MD5_T53);\n\tSET(d, a, b, c, 3, 10, ZSW_MD5_T54);\n\tSET(c, d, a, b, 10, 15, ZSW_MD5_T55);\n\tSET(b, c, d, a, 1, 21, ZSW_MD5_T56);\n\tSET(a, b, c, d, 8, 6, ZSW_MD5_T57);\n\tSET(d, a, b, c, 15, 10, ZSW_MD5_T58);\n\tSET(c, d, a, b, 6, 15, ZSW_MD5_T59);\n\tSET(b, c, d, a, 13, 21, ZSW_MD5_T60);\n\tSET(a, b, c, d, 4, 6, ZSW_MD5_T61);\n\tSET(d, a, b, c, 11, 10, ZSW_MD5_T62);\n\tSET(c, d, a, b, 2, 15, ZSW_MD5_T63);\n\tSET(b, c, d, a, 9, 21, ZSW_MD5_T64);\n#undef SET\n\n\t/* Then perform the following additions. (That is increment each\n        of the four registers by the value it had before this block\n        was started.) */\n\tpms->abcd[0] += a;\n\tpms->abcd[1] += b;\n\tpms->abcd[2] += c;\n\tpms->abcd[3] += d;\n}\n\nvoid md5_init(md5_state_t *pms)\n{\n\tpms->count[0] = pms->count[1] = 0;\n\tpms->abcd[0] = 0x67452301;\n\tpms->abcd[1] = /*0xefcdab89*/ ZSW_MD5_T_MASK ^ 0x10325476;\n\tpms->abcd[2] = /*0x98badcfe*/ ZSW_MD5_T_MASK ^ 0x67452301;\n\tpms->abcd[3] = 0x10325476;\n}\n\nvoid md5_append(md5_state_t *pms, md5_byte_t const *data, size_t nbytes)\n{\n\tmd5_byte_t const *p = data;\n\tsize_t left = nbytes;\n\tint offset = (pms->count[0] >> 3) & 63;\n\tmd5_word_t nbits = (md5_word_t)(nbytes << 3);\n\n\tif (nbytes <= 0)\n\t\treturn;\n\n\t/* Update the message length. */\n\tpms->count[1] += (md5_word_t)(nbytes >> 29);\n\tpms->count[0] += nbits;\n\tif (pms->count[0] < nbits)\n\t\tpms->count[1]++;\n\n\t/* Process an initial partial block. */\n\tif (offset) {\n\t\tint copy = (offset + nbytes > 64 ? 64 - offset\n\t\t\t\t\t\t : static_cast<int>(nbytes));\n\n\t\tstd::memcpy(pms->buf + offset, p, copy);\n\t\tif (offset + copy < 64)\n\t\t\treturn;\n\t\tp += copy;\n\t\tleft -= copy;\n\t\tmd5_process(pms, pms->buf);\n\t}\n\n\t/* Process full blocks. */\n\tfor (; left >= 64; p += 64, left -= 64)\n\t\tmd5_process(pms, p);\n\n\t/* Process a final partial block. */\n\tif (left)\n\t\tstd::memcpy(pms->buf, p, left);\n}\n\nvoid md5_finish(md5_state_t *pms, md5_byte_t digest[16])\n{\n\tstatic md5_byte_t const pad[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t\t   0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t\t   0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t\t   0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t\t   0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t\t\t   0,    0, 0, 0, 0, 0, 0, 0, 0};\n\tmd5_byte_t data[8];\n\tint i;\n\n\t/* Save the length before padding. */\n\tfor (i = 0; i < 8; ++i)\n\t\tdata[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));\n\t/* Pad to 56 bytes mod 64. */\n\tmd5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);\n\t/* Append the length. */\n\tmd5_append(pms, data, 8);\n\tfor (i = 0; i < 16; ++i)\n\t\tdigest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));\n}\n\n}\n\n#endif // WEBSOCKETPP_COMMON_MD5_HPP\n"
  },
  {
    "path": "rtsp-server/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.5)\nproject(rtsp-server)\n\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\ninclude_directories(\"3rdpart/md5\")\ninclude_directories(\"3rdpart/libb64/libb64/include\")\nadd_subdirectory(3rdpart/libb64)\n\nfile(GLOB rtsp-server_net_SOURCES\n\tnet/*.cpp)\n\nfile(GLOB rtsp-server_xop_SOURCES\n\txop/*.cpp)\n\nset(rtsp-server_SOURCES\n\t${rtsp-server_net_SOURCES}\n\t${rtsp-server_xop_SOURCES}\n\t)\n\nfile(GLOB rtsp-server_net_HEADERS\n\tnet/*.h)\n\nfile(GLOB rtsp-server_xop_HEADERS\n\txop/*.h)\n\nset(rtsp-server_HEADERS\n\t${rtsp-server_net_HEADERS}\n\t${rtsp-server_xop_HEADERS}\n\t)\n\nadd_library(rtsp-server STATIC\n\t${rtsp-server_SOURCES}\n\t${rtsp-server_HEADERS}\n\t)\n\ntarget_link_libraries(rtsp-server\n\tlibb64)\n"
  },
  {
    "path": "rtsp-server/net/Acceptor.cpp",
    "content": "//Scott Xu\n//2020-12-6 Add IPv6 support.\n#include \"Acceptor.h\"\n#include \"EventLoop.h\"\n#include \"SocketUtil.h\"\n#include \"Logger.h\"\n\nusing namespace xop;\n\nAcceptor::Acceptor(EventLoop *eventLoop)\n\t: event_loop_(eventLoop), tcp_socket_(new TcpSocket())\n{\n}\n\nAcceptor::~Acceptor() = default;\n\nint Acceptor::Listen(const std::string &ip, const uint16_t port)\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (tcp_socket_->GetSocket() > 0) {\n\t\ttcp_socket_->Close();\n\t}\n\tconst SOCKET sockfd =\n\t\ttcp_socket_->Create(SocketUtil::IsIpv6Address(ip));\n\tchannel_ptr_.reset(new Channel(sockfd));\n\tSocketUtil::SetReuseAddr(sockfd);\n\tSocketUtil::SetReusePort(sockfd);\n\tSocketUtil::SetNonBlock(sockfd);\n\n\tif (!tcp_socket_->Bind(ip, port)) {\n\t\treturn -1;\n\t}\n\n\tif (!tcp_socket_->Listen(1024)) {\n\t\treturn -1;\n\t}\n\n\tchannel_ptr_->SetReadCallback([this] { this->OnAccept(); });\n\tchannel_ptr_->EnableReading();\n\tevent_loop_->UpdateChannel(channel_ptr_);\n\treturn 0;\n}\n\nvoid Acceptor::Close()\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (tcp_socket_->GetSocket() > 0) {\n\t\tevent_loop_->RemoveChannel(channel_ptr_);\n\t\ttcp_socket_->Close();\n\t}\n}\n\nvoid Acceptor::OnAccept()\n{\n\tstd::lock_guard locker(mutex_);\n\tif (const auto sockfd = tcp_socket_->Accept(); sockfd > 0) {\n\t\tif (new_connection_callback_) {\n\t\t\tnew_connection_callback_(sockfd);\n\t\t} else {\n\t\t\tSocketUtil::Close(sockfd);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "rtsp-server/net/Acceptor.h",
    "content": "//Scott Xu\n//2020-12-6 Add IPv6 support.\n#ifndef XOP_ACCEPTOR_H\n#define XOP_ACCEPTOR_H\n\n#include <functional>\n#include <memory>\n#include <mutex>\n#include \"Channel.h\"\n#include \"TcpSocket.h\"\n\nnamespace xop {\n\ntypedef std::function<void(SOCKET)> NewConnectionCallback;\n\nclass EventLoop;\n\nclass Acceptor {\npublic:\n\texplicit Acceptor(EventLoop *eventLoop);\n\tvirtual ~Acceptor();\n\n\tvoid SetNewConnectionCallback(const NewConnectionCallback &cb)\n\t{\n\t\tnew_connection_callback_ = cb;\n\t}\n\n\tint Listen(const std::string &ip, uint16_t port);\n\tvoid Close();\n\nprivate:\n\tvoid OnAccept();\n\n\tEventLoop *event_loop_ = nullptr;\n\tstd::mutex mutex_;\n\tstd::unique_ptr<TcpSocket> tcp_socket_;\n\tChannelPtr channel_ptr_;\n\tNewConnectionCallback new_connection_callback_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/BufferReader.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#include \"BufferReader.h\"\n#include \"Socket.h\"\n\nusing namespace xop;\nuint32_t xop::ReadUint32BE(char *data)\n{\n\tconst auto p = reinterpret_cast<uint8_t *>(data);\n\tconst uint32_t value = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];\n\treturn value;\n}\n\nuint32_t xop::ReadUint32LE(char *data)\n{\n\tconst auto p = reinterpret_cast<uint8_t *>(data);\n\tconst uint32_t value = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];\n\treturn value;\n}\n\nuint32_t xop::ReadUint24BE(char *data)\n{\n\tconst auto p = reinterpret_cast<uint8_t *>(data);\n\tconst uint32_t value = (p[0] << 16) | (p[1] << 8) | p[2];\n\treturn value;\n}\n\nuint32_t xop::ReadUint24LE(char *data)\n{\n\tconst auto p = reinterpret_cast<uint8_t *>(data);\n\tconst uint32_t value = (p[2] << 16) | (p[1] << 8) | p[0];\n\treturn value;\n}\n\nuint16_t xop::ReadUint16BE(char *data)\n{\n\tconst auto p = reinterpret_cast<uint8_t *>(data);\n\tconst uint16_t value = (p[0] << 8) | p[1];\n\treturn value;\n}\n\nuint16_t xop::ReadUint16LE(char *data)\n{\n\tconst auto *p = reinterpret_cast<uint8_t *>(data);\n\tconst uint16_t value = (p[1] << 8) | p[0];\n\treturn value;\n}\n\nconstexpr char BufferReader::kCRLF[] = \"\\r\\n\";\n\nBufferReader::BufferReader(const uint32_t initialSize) : buffer_(initialSize)\n{\n\tbuffer_.resize(initialSize);\n}\n\nBufferReader::~BufferReader() = default;\n\nint BufferReader::Read(const SOCKET sockfd)\n{\n\tif (const size_t size = WritableBytes(); size < MAX_BYTES_PER_READ) {\n\t\tconst auto bufferReaderSize = buffer_.size();\n\t\tif (bufferReaderSize > MAX_BUFFER_SIZE) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tbuffer_.resize(bufferReaderSize + MAX_BYTES_PER_READ);\n\t}\n\n\tconst int bytes_read =\n\t\trecv(sockfd, beginWrite(), MAX_BYTES_PER_READ, 0);\n\tif (bytes_read > 0) {\n\t\twriter_index_ += bytes_read;\n\t}\n\n\treturn bytes_read;\n}\n\nsize_t BufferReader::ReadAll(std::string &data)\n{\n\tconst size_t size = ReadableBytes();\n\tif (size > 0) {\n\t\tdata.assign(Peek(), size);\n\t\twriter_index_ = 0;\n\t\treader_index_ = 0;\n\t}\n\n\treturn size;\n}\n\nsize_t BufferReader::ReadUntilCrlf(std::string &data)\n{\n\tconst char *crlf = FindLastCrlf();\n\tif (crlf == nullptr) {\n\t\treturn 0;\n\t}\n\n\tconst auto size = static_cast<size_t>(crlf - Peek() + 2);\n\tdata.assign(Peek(), size);\n\tRetrieve(size);\n\treturn size;\n}\n"
  },
  {
    "path": "rtsp-server/net/BufferReader.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_BUFFER_READER_H\n#define XOP_BUFFER_READER_H\n\n#include <cstdint>\n#include <vector>\n#include <string>\n#include <algorithm>\n#include \"Socket.h\"\n\nnamespace xop {\n\nuint32_t ReadUint32BE(char *data);\nuint32_t ReadUint32LE(char *data);\nuint32_t ReadUint24BE(char *data);\nuint32_t ReadUint24LE(char *data);\nuint16_t ReadUint16BE(char *data);\nuint16_t ReadUint16LE(char *data);\n\nclass BufferReader {\npublic:\n\tstatic constexpr uint32_t kInitialSize = 2048;\n\texplicit BufferReader(uint32_t initialSize = kInitialSize);\n\tvirtual ~BufferReader();\n\n\tsize_t ReadableBytes() const { return writer_index_ - reader_index_; }\n\n\tsize_t WritableBytes() const { return buffer_.size() - writer_index_; }\n\n\tchar *Peek() { return Begin() + reader_index_; }\n\n\tconst char *Peek() const { return Begin() + reader_index_; }\n\n\tconst char *FindFirstCrlf() const\n\t{\n\t\tconst char *crlf =\n\t\t\tstd::search(Peek(), BeginWrite(), kCRLF, kCRLF + 2);\n\t\treturn crlf == BeginWrite() ? nullptr : crlf;\n\t}\n\n\tconst char *FindLastCrlf() const\n\t{\n\t\tconst char *crlf =\n\t\t\tstd::find_end(Peek(), BeginWrite(), kCRLF, kCRLF + 2);\n\t\treturn crlf == BeginWrite() ? nullptr : crlf;\n\t}\n\n\tconst char *FindLastCrlfCrlf() const\n\t{\n\t\tchar crlfCrlf[] = \"\\r\\n\\r\\n\";\n\t\tconst char *crlf = std::find_end(Peek(), BeginWrite(), crlfCrlf,\n\t\t\t\t\t\t crlfCrlf + 4);\n\t\treturn crlf == BeginWrite() ? nullptr : crlf;\n\t}\n\n\tvoid RetrieveAll()\n\t{\n\t\twriter_index_ = 0;\n\t\treader_index_ = 0;\n\t}\n\n\tvoid Retrieve(const size_t len)\n\t{\n\t\tif (len <= ReadableBytes()) {\n\t\t\treader_index_ += len;\n\t\t\tif (reader_index_ == writer_index_) {\n\t\t\t\treader_index_ = 0;\n\t\t\t\twriter_index_ = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tRetrieveAll();\n\t\t}\n\t}\n\n\tvoid RetrieveUntil(const char *end) { Retrieve(end - Peek()); }\n\n\tint Read(SOCKET sockfd);\n\tsize_t ReadAll(std::string &data);\n\tsize_t ReadUntilCrlf(std::string &data);\n\n\tsize_t Size() const { return buffer_.size(); }\n\nprivate:\n\tchar *Begin() { return &*buffer_.begin(); }\n\n\tconst char *Begin() const { return &*buffer_.begin(); }\n\n\tchar *beginWrite() { return Begin() + writer_index_; }\n\n\tconst char *BeginWrite() const { return Begin() + writer_index_; }\n\n\tstd::vector<char> buffer_;\n\tsize_t reader_index_ = 0;\n\tsize_t writer_index_ = 0;\n\n\tstatic const char kCRLF[];\n\tstatic constexpr uint32_t MAX_BYTES_PER_READ = 4096;\n\tstatic constexpr uint32_t MAX_BUFFER_SIZE = 1024 * 100000;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/BufferWriter.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#include \"BufferWriter.h\"\n#include \"Socket.h\"\n#include \"SocketUtil.h\"\n\nusing namespace xop;\n\nvoid xop::WriteUint32BE(char *p, uint32_t value)\n{\n\tp[0] = value >> 24;\n\tp[1] = value >> 16;\n\tp[2] = value >> 8;\n\tp[3] = value & 0xff;\n}\n\nvoid xop::WriteUint32LE(char *p, uint32_t value)\n{\n\tp[0] = value & 0xff;\n\tp[1] = value >> 8;\n\tp[2] = value >> 16;\n\tp[3] = value >> 24;\n}\n\nvoid xop::WriteUint24BE(char *p, uint32_t value)\n{\n\tp[0] = value >> 16;\n\tp[1] = value >> 8;\n\tp[2] = value & 0xff;\n}\n\nvoid xop::WriteUint24LE(char *p, uint32_t value)\n{\n\tp[0] = value & 0xff;\n\tp[1] = value >> 8;\n\tp[2] = value >> 16;\n}\n\nvoid xop::WriteUint16BE(char *p, uint16_t value)\n{\n\tp[0] = value >> 8;\n\tp[1] = value & 0xff;\n}\n\nvoid xop::WriteUint16LE(char *p, uint16_t value)\n{\n\tp[0] = value & 0xff;\n\tp[1] = value >> 8;\n}\n\nBufferWriter::BufferWriter(const int capacity) : max_queue_length_(capacity) {}\n\nbool BufferWriter::Append(const std::shared_ptr<char> &data, const size_t size,\n\t\t\t  const uint32_t index)\n{\n\tif (size <= index) {\n\t\treturn false;\n\t}\n\n\tif (static_cast<int>(buffer_.size()) >= max_queue_length_) {\n\t\treturn false;\n\t}\n\n\tPacket pkt = {data, size, index};\n\tbuffer_.emplace(std::move(pkt));\n\treturn true;\n}\n\nbool BufferWriter::Append(const char *data, const size_t size,\n\t\t\t  const uint32_t index)\n{\n\tif (size <= index) {\n\t\treturn false;\n\t}\n\n\tif (static_cast<int>(buffer_.size()) >= max_queue_length_) {\n\t\treturn false;\n\t}\n\n\tPacket pkt;\n\tpkt.data.reset(new char[size + 512], std::default_delete<char[]>());\n\tmemcpy(pkt.data.get(), data, size);\n\tpkt.size = size;\n\tpkt.writeIndex = index;\n\tbuffer_.emplace(std::move(pkt));\n\treturn true;\n}\n\nint BufferWriter::Send(const SOCKET sockfd, const int timeout)\n{\n\tif (timeout > 0) {\n\t\tSocketUtil::SetBlock(sockfd, timeout);\n\t}\n\n\tint ret;\n\tint count = 1;\n\n\tdo {\n\t\tif (buffer_.empty()) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tcount -= 1;\n\t\tPacket &pkt = buffer_.front();\n\t\tret = send(sockfd, pkt.data.get() + pkt.writeIndex,\n\t\t\t   static_cast<uint32_t>(pkt.size) - pkt.writeIndex, 0);\n\t\tif (ret > 0) {\n\t\t\tpkt.writeIndex += ret;\n\t\t\tif (pkt.size == pkt.writeIndex) {\n\t\t\t\tcount += 1;\n\t\t\t\tbuffer_.pop();\n\t\t\t}\n\t\t} else if (ret < 0) {\n#if defined(WIN32) || defined(_WIN32)\n\t\t\tif (const int error = WSAGetLastError();\n\t\t\t    error == WSAEWOULDBLOCK ||\n\t\t\t    error == WSAEINPROGRESS || error == 0)\n#else\n\t\t\tif (errno == EINTR || errno == EAGAIN)\n#endif\n\t\t\t{\n\t\t\t\tret = 0;\n\t\t\t}\n\t\t}\n\t} while (count > 0);\n\n\tif (timeout > 0) {\n\t\tSocketUtil::SetNonBlock(sockfd);\n\t}\n\n\treturn ret;\n}\n"
  },
  {
    "path": "rtsp-server/net/BufferWriter.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_BUFFER_WRITER_H\n#define XOP_BUFFER_WRITER_H\n\n#include <cstdint>\n#include <memory>\n#include <queue>\n#include \"Socket.h\"\n\nnamespace xop {\n\nvoid WriteUint32BE(char *p, uint32_t value);\nvoid WriteUint32LE(char *p, uint32_t value);\nvoid WriteUint24BE(char *p, uint32_t value);\nvoid WriteUint24LE(char *p, uint32_t value);\nvoid WriteUint16BE(char *p, uint16_t value);\nvoid WriteUint16LE(char *p, uint16_t value);\n\nclass BufferWriter {\npublic:\n\texplicit BufferWriter(int capacity = kMaxQueueLength);\n\tvirtual ~BufferWriter() = default;\n\n\tbool Append(const std::shared_ptr<char> &data, size_t size,\n\t\t    uint32_t index = 0);\n\tbool Append(const char *data, size_t size, uint32_t index = 0);\n\tint Send(SOCKET sockfd, int timeout = 0);\n\n\tbool IsEmpty() const { return buffer_.empty(); }\n\n\tbool IsFull() const\n\t{\n\t\treturn static_cast<int>(buffer_.size()) >= max_queue_length_\n\t\t\t       ? true\n\t\t\t       : false;\n\t}\n\n\tsize_t Size() const { return buffer_.size(); }\n\nprivate:\n\ttypedef struct Packet {\n\t\tstd::shared_ptr<char> data;\n\t\tsize_t size{};\n\t\tuint32_t writeIndex{};\n\t} Packet;\n\n\tstd::queue<Packet> buffer_;\n\tint max_queue_length_ = 0;\n\n\tstatic constexpr int kMaxQueueLength = 10000;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/Channel.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_CHANNEL_H\n#define XOP_CHANNEL_H\n\n#include <functional>\n#include <memory>\n#include \"Socket.h\"\n\nnamespace xop {\n\nenum EventType: uint32_t {\n\tEVENT_NONE = 0,\n\tEVENT_IN = 1,\n\tEVENT_PRI = 2,\n\tEVENT_OUT = 4,\n\tEVENT_ERR = 8,\n\tEVENT_HUP = 16,\n\tEVENT_RDHUP = 8192\n};\n\nclass Channel {\npublic:\n\ttypedef std::function<void()> EventCallback;\n\n\tChannel() = delete;\n\n\texplicit Channel(const SOCKET sockfd) : sockfd_(sockfd) {}\n\n\tvirtual ~Channel() = default;\n\n\tvoid SetReadCallback(const EventCallback &cb) { read_callback_ = cb; }\n\n\tvoid SetWriteCallback(const EventCallback &cb) { write_callback_ = cb; }\n\n\tvoid SetCloseCallback(const EventCallback &cb) { close_callback_ = cb; }\n\n\tvoid SetErrorCallback(const EventCallback &cb) { error_callback_ = cb; }\n\n\tSOCKET GetSocket() const { return sockfd_; }\n\n\tuint32_t GetEvents() const { return events_; }\n\tvoid SetEvents(const int events) { events_ = events; }\n\n\tvoid EnableReading() { events_ |= EVENT_IN; }\n\n\tvoid EnableWriting() { events_ |= EVENT_OUT; }\n\n\tvoid DisableReading() { events_ &= ~EVENT_IN; }\n\n\tvoid DisableWriting() { events_ &= ~EVENT_OUT; }\n\n\tbool IsNoneEvent() const { return events_ == EVENT_NONE; }\n\tbool IsWriting() const { return (events_ & EVENT_OUT) != 0; }\n\tbool IsReading() const { return (events_ & EVENT_IN) != 0; }\n\n\tvoid HandleEvent(const uint32_t events) const\n\t{\n\t\tif (events & (EVENT_PRI | EVENT_IN)) {\n\t\t\tread_callback_();\n\t\t}\n\n\t\tif (events & EVENT_OUT) {\n\t\t\twrite_callback_();\n\t\t}\n\n\t\tif (events & EVENT_HUP) {\n\t\t\tclose_callback_();\n\t\t\treturn;\n\t\t}\n\n\t\tif (events & EVENT_ERR) {\n\t\t\terror_callback_();\n\t\t}\n\t}\n\nprivate:\n\tEventCallback read_callback_ = [] {};\n\tEventCallback write_callback_ = [] {};\n\tEventCallback close_callback_ = [] {};\n\tEventCallback error_callback_ = [] {};\n\n\tSOCKET sockfd_ = 0;\n\tuint32_t events_ = 0;\n};\n\ntypedef std::shared_ptr<Channel> ChannelPtr;\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/EpollTaskScheduler.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#include \"EpollTaskScheduler.h\"\n\n#if defined(__linux) || defined(__linux__)\n#include <sys/epoll.h>\n#include <cerrno>\n#endif\n#include \"Logger.h\"\n\nusing namespace xop;\n\nEpollTaskScheduler::EpollTaskScheduler(const int id) : TaskScheduler(id)\n{\n#if defined(__linux) || defined(__linux__)\n\tepollfd_ = epoll_create1(0);\n\tif (epollfd_ < 0) {\n\t\tLOG_ERROR(\"epoll_create1 errno: %d\", errno);\n\t}\n#endif\n\tthis->EpollTaskScheduler::UpdateChannel(wakeup_channel_);\n}\n\nEpollTaskScheduler::~EpollTaskScheduler()\n{\n#if defined(__linux) || defined(__linux__)\n\tif (epollfd_ >= 0) {\n\t\tclose(epollfd_);\n\t\tepollfd_ = -1;\n\t}\n#endif\n}\n\nvoid EpollTaskScheduler::UpdateChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n#if defined(__linux) || defined(__linux__)\n\tint fd = channel->GetSocket();\n\tif (channels_.find(fd) != channels_.end()) {\n\t\tif (channel->IsNoneEvent()) {\n\t\t\tUpdate(EPOLL_CTL_DEL, channel);\n\t\t\tchannels_.erase(fd);\n\t\t} else {\n\t\t\tUpdate(EPOLL_CTL_MOD, channel);\n\t\t}\n\t} else {\n\t\tif (!channel->IsNoneEvent()) {\n\t\t\tchannels_.emplace(fd, channel);\n\t\t\tUpdate(EPOLL_CTL_ADD, channel);\n\t\t}\n\t}\n#endif\n}\n\nvoid EpollTaskScheduler::Update(int operation, const ChannelPtr &channel)\n{\n#if defined(__linux) || defined(__linux__)\n\tstruct epoll_event event = {0};\n\n\tif (operation != EPOLL_CTL_DEL) {\n\t\tevent.data.ptr = channel.get();\n\t\tevent.events = channel->GetEvents();\n\t}\n\n\tif (::epoll_ctl(epollfd_, operation, channel->GetSocket(), &event) <\n\t    0) {\n\t\tLOG_ERROR(\"epoll_ctl errno: %d\", errno);\n\t}\n#endif\n}\n\nvoid EpollTaskScheduler::RemoveChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n#if defined(__linux) || defined(__linux__)\n\tint fd = channel->GetSocket();\n\n\tif (channels_.find(fd) != channels_.end()) {\n\t\tUpdate(EPOLL_CTL_DEL, channel);\n\t\tchannels_.erase(fd);\n\t}\n#endif\n}\n\nbool EpollTaskScheduler::HandleEvent(int timeout)\n{\n#if defined(__linux) || defined(__linux__)\n\tstruct epoll_event events[512] = {0};\n\tint num_events = -1;\n\n\tnum_events = epoll_wait(epollfd_, events, 512, timeout);\n\tif (num_events < 0) {\n\t\tif (errno != EINTR) {\n\t\t\tLOG_ERROR(\"epoll_wait errno: %d\", errno);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfor (int n = 0; n < num_events; n++) {\n\t\tif (events[n].data.ptr) {\n\t\t\tstatic_cast<Channel *>(events[n].data.ptr)\n\t\t\t\t->HandleEvent(events[n].events);\n\t\t}\n\t}\n\treturn true;\n#else\n\treturn false;\n#endif\n}\n"
  },
  {
    "path": "rtsp-server/net/EpollTaskScheduler.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_EPOLL_TASK_SCHEDULER_H\n#define XOP_EPOLL_TASK_SCHEDULER_H\n\n#include \"TaskScheduler.h\"\n#include <mutex>\n#include <unordered_map>\n\nnamespace xop {\nclass EpollTaskScheduler : public TaskScheduler {\npublic:\n\texplicit EpollTaskScheduler(int id = 0);\n\t~EpollTaskScheduler() override;\n\n\tvoid UpdateChannel(const ChannelPtr &channel) override;\n\tvoid RemoveChannel(const ChannelPtr &channel) override;\n\n\t// timeout: ms\n\tbool HandleEvent(int timeout) override;\n\nprivate:\n\tvoid Update(int operation, const ChannelPtr &channel);\n\n\tint epollfd_ = -1;\n\tstd::mutex mutex_;\n\tstd::unordered_map<int, ChannelPtr> channels_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/EventLoop.cpp",
    "content": "// PHZ\n// 2019-10-18\n// Scott Xu\n// 2021-1-20 Added MacOS support.\n\n#include \"EventLoop.h\"\n\n#if defined(WIN32) || defined(_WIN32)\n#include <windows.h>\n\n#include <utility>\n#include \"SelectTaskScheduler.h\"\n#pragma comment(lib, \"Ws2_32.lib\")\n#pragma comment(lib, \"Iphlpapi.lib\")\n#elif defined(__linux) || defined(__linux__)\n#include \"EpollTaskScheduler.h\"\n#elif defined(__APPLE__) || defined(__MACH__)\n#include \"KqueueTaskScheduler.h\"\n#else\n#include \"SelectTaskScheduler.h\"\n#endif\n\nusing namespace xop;\n\nEventLoop::EventLoop(const uint32_t num_threads)\n\t: num_threads_(num_threads > 0 ? num_threads : 1)\n{\n\tthis->Loop();\n}\n\nEventLoop::~EventLoop()\n{\n\tthis->Quit();\n}\n\nstd::shared_ptr<TaskScheduler> EventLoop::GetTaskScheduler()\n{\n\tstd::lock_guard locker(mutex_);\n\tif (task_schedulers_.size() == 1) {\n\t\treturn task_schedulers_.at(0);\n\t}\n\tauto task_scheduler = task_schedulers_.at(index_);\n\tindex_++;\n\tif (index_ >= task_schedulers_.size()) {\n\t\tindex_ = 1;\n\t}\n\treturn task_scheduler;\n\n\t//return nullptr;\n}\n\nvoid EventLoop::Loop()\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (!task_schedulers_.empty()) {\n\t\treturn;\n\t}\n\n\tfor (uint32_t n = 0; n < num_threads_; n++) {\n#if defined(WIN32) || defined(_WIN32)\n\t\tstd::shared_ptr<TaskScheduler> task_scheduler_ptr(\n\t\t\tnew SelectTaskScheduler(n));\n#elif defined(__linux) || defined(__linux__)\n\t\tstd::shared_ptr<TaskScheduler> task_scheduler_ptr(\n\t\t\tnew EpollTaskScheduler(n));\n#elif defined(__APPLE__) || defined(__MACH__)\n\t\tstd::shared_ptr<TaskScheduler> task_scheduler_ptr(\n\t\t\tnew KqueueTaskScheduler(n));\n#else\n\t\tstd::shared_ptr<TaskScheduler> task_scheduler_ptr(\n\t\t\tnew SelectTaskScheduler(n));\n#endif\n\t\ttask_schedulers_.push_back(task_scheduler_ptr);\n\t\tstd::shared_ptr<std::thread> thread(new std::thread(\n\t\t\t&TaskScheduler::Start, task_scheduler_ptr.get()));\n\t\tconst auto native_handle_type = thread->native_handle(); //TODO\n\t\tthreads_.push_back(thread);\n\t}\n\n\tconst int priority = TASK_SCHEDULER_PRIORITY_REALTIME;\n\n\tfor (const auto &iter : threads_) {\n#if defined(WIN32) || defined(_WIN32)\n\t\tswitch (priority) {\n\t\tcase TASK_SCHEDULER_PRIORITY_LOW:\n\t\t\tSetThreadPriority(iter->native_handle(),\n\t\t\t\t\t  THREAD_PRIORITY_BELOW_NORMAL);\n\t\t\tbreak;\n\t\tcase TASK_SCHEDULER_PRIORITY_NORMAL:\n\t\t\tSetThreadPriority(iter->native_handle(),\n\t\t\t\t\t  THREAD_PRIORITY_NORMAL);\n\t\t\tbreak;\n\t\tcase TASK_SCHEDULER_PRIORITYO_HIGH:\n\t\t\tSetThreadPriority(iter->native_handle(),\n\t\t\t\t\t  THREAD_PRIORITY_ABOVE_NORMAL);\n\t\t\tbreak;\n\t\tcase TASK_SCHEDULER_PRIORITY_HIGHEST:\n\t\t\tSetThreadPriority(iter->native_handle(),\n\t\t\t\t\t  THREAD_PRIORITY_HIGHEST);\n\t\t\tbreak;\n\t\tcase TASK_SCHEDULER_PRIORITY_REALTIME:\n\t\t\tSetThreadPriority(iter->native_handle(),\n\t\t\t\t\t  THREAD_PRIORITY_TIME_CRITICAL);\n\t\t\tbreak;\n\t\t}\n#else\n\n#endif\n\t}\n}\n\nvoid EventLoop::Quit()\n{\n\tstd::lock_guard locker(mutex_);\n\n\tfor (const auto &iter : task_schedulers_) {\n\t\titer->Stop();\n\t}\n\n\tfor (const auto iter : threads_) {\n\t\titer->join();\n\t}\n\n\ttask_schedulers_.clear();\n\tthreads_.clear();\n}\n\nvoid EventLoop::UpdateChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!task_schedulers_.empty()) {\n\t\ttask_schedulers_[0]->UpdateChannel(channel);\n\t}\n}\n\nvoid EventLoop::RemoveChannel(ChannelPtr &channel)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!task_schedulers_.empty()) {\n\t\ttask_schedulers_[0]->RemoveChannel(channel);\n\t}\n}\n\nTimerId EventLoop::AddTimer(TimerEvent timerEvent, const uint32_t msec)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!task_schedulers_.empty()) {\n\t\treturn task_schedulers_[0]->AddTimer(std::move(timerEvent),\n\t\t\t\t\t\t     msec);\n\t}\n\treturn 0;\n}\n\nvoid EventLoop::RemoveTimer(const TimerId timerId)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!task_schedulers_.empty()) {\n\t\ttask_schedulers_[0]->RemoveTimer(timerId);\n\t}\n}\n\nbool EventLoop::AddTriggerEvent(const TriggerEvent &callback)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!task_schedulers_.empty()) {\n\t\treturn task_schedulers_[0]->AddTriggerEvent(callback);\n\t}\n\treturn false;\n}\n"
  },
  {
    "path": "rtsp-server/net/EventLoop.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_EVENT_LOOP_H\n#define XOP_EVENT_LOOP_H\n\n#include <memory>\n#include <thread>\n#include <mutex>\n\n#include \"TaskScheduler.h\"\n#include \"Timer.h\"\n\n#define TASK_SCHEDULER_PRIORITY_LOW 0\n#define TASK_SCHEDULER_PRIORITY_NORMAL 1\n#define TASK_SCHEDULER_PRIORITYO_HIGH 2\n#define TASK_SCHEDULER_PRIORITY_HIGHEST 3\n#define TASK_SCHEDULER_PRIORITY_REALTIME 4\n\nnamespace xop {\n\nclass EventLoop {\npublic:\n\tEventLoop(const EventLoop &) = delete;\n\tEventLoop &operator=(const EventLoop &) = delete;\n\texplicit EventLoop(\n\t\tuint32_t num_threads = 1); //std::thread::hardware_concurrency()\n\tvirtual ~EventLoop();\n\n\tstd::shared_ptr<TaskScheduler> GetTaskScheduler();\n\n\tbool AddTriggerEvent(const TriggerEvent &callback);\n\tTimerId AddTimer(TimerEvent timerEvent, uint32_t msec);\n\tvoid RemoveTimer(TimerId timerId);\n\tvoid UpdateChannel(const ChannelPtr &channel);\n\tvoid RemoveChannel(ChannelPtr &channel);\n\n\tvoid Loop();\n\tvoid Quit();\n\nprivate:\n\tstd::mutex mutex_;\n\tuint32_t num_threads_ = 1;\n\tuint32_t index_ = 1;\n\tstd::vector<std::shared_ptr<TaskScheduler>> task_schedulers_;\n\tstd::vector<std::shared_ptr<std::thread>> threads_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/KqueueTaskScheduler.cpp",
    "content": "// Scott Xu\n// 2021-1-19\n\n#include \"KqueueTaskScheduler.h\"\n\n#if defined(__APPLE__) || defined(__MACH__)\n#include <sys/event.h>\n#include <errno.h>\n#endif\n\nusing namespace xop;\n\nKqueueTaskScheduler::KqueueTaskScheduler(int id) : TaskScheduler(id)\n{\n#if defined(__APPLE__) || defined(__MACH__)\n\tkqueuefd_ = kqueue();\n#endif\n\tthis->KqueueTaskScheduler::UpdateChannel(wakeup_channel_);\n}\n\nKqueueTaskScheduler::~KqueueTaskScheduler() {}\n\nvoid KqueueTaskScheduler::UpdateChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n#if defined(__APPLE__) || defined(__MACH__)\n\tint fd = channel->GetSocket();\n\tif (channels_.find(fd) != channels_.end()) {\n\t\tif (channel->IsNoneEvent()) {\n\t\t\tUpdate(EV_DELETE, channel);\n\t\t\tchannels_.erase(fd);\n\t\t} else {\n\t\t\tUpdate(EV_ADD | EV_ENABLE, channel);\n\t\t}\n\t} else {\n\t\tif (!channel->IsNoneEvent()) {\n\t\t\tchannels_.emplace(fd, channel);\n\t\t\tUpdate(EV_ADD | EV_ENABLE, channel);\n\t\t}\n\t}\n#endif\n}\n\nvoid KqueueTaskScheduler::Update(int operation, const ChannelPtr &channel)\n{\n#if defined(__APPLE__) || defined(__MACH__)\n\tstruct kevent events[2] = {0};\n\tint num_events = 0;\n\n\tif (channel->IsReading())\n\t\tEV_SET(&events[num_events++], channel->GetSocket(), EVFILT_READ,\n\t\t       operation, NULL, NULL, channel.get());\n\tif (channel->IsWriting())\n\t\tEV_SET(&events[num_events++], channel->GetSocket(),\n\t\t       EVFILT_WRITE, operation, NULL, NULL, channel.get());\n\n\tif (kevent(kqueuefd_, events, num_events, nullptr, 0, nullptr) < 0) {\n\t}\n#endif\n}\n\nvoid KqueueTaskScheduler::RemoveChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n#if defined(__APPLE__) || defined(__MACH__)\n\tint fd = channel->GetSocket();\n\n\tif (channels_.find(fd) != channels_.end()) {\n\t\tUpdate(EV_DELETE, channel);\n\t\tchannels_.erase(fd);\n\t}\n#endif\n}\n\nbool KqueueTaskScheduler::HandleEvent(int timeout)\n{\n#if defined(__APPLE__) || defined(__MACH__)\n\tstruct kevent events[512] = {0};\n\tint num_events = -1;\n\n\tif (timeout > 0) {\n\t\tstruct timespec _timeout = {0};\n\t\t_timeout.tv_sec = timeout / 1000;\n\t\t_timeout.tv_nsec = (timeout % 1000) * 1000 * 1000;\n\t\tnum_events =\n\t\t\tkevent(kqueuefd_, nullptr, 0, events, 512, &_timeout);\n\t} else\n\t\tnum_events =\n\t\t\tkevent(kqueuefd_, nullptr, 0, events, 512, nullptr);\n\tif (num_events < 0) {\n\t\tif (errno != EINTR) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfor (int n = 0; n < num_events; n++) {\n\t\tauto filter = events[n].filter;\n\t\tauto flags = events[n].flags;\n\t\tauto channel = (Channel *)events[n].udata;\n\n\t\tif (!channel)\n\t\t\tcontinue;\n\n\t\tint handleEventEvents = EVENT_NONE;\n\t\tif (filter == EVFILT_READ)\n\t\t\thandleEventEvents = EVENT_IN;\n\t\telse if (filter == EVFILT_WRITE)\n\t\t\thandleEventEvents = EVENT_OUT;\n\n\t\tif (flags & EV_ERROR)\n\t\t\thandleEventEvents = handleEventEvents | EVENT_ERR;\n\t\tif (flags & EV_EOF)\n\t\t\thandleEventEvents = handleEventEvents | EVENT_HUP;\n\n\t\tchannel->HandleEvent(handleEventEvents);\n\t}\n\treturn true;\n#else\n\treturn false;\n#endif\n}\n"
  },
  {
    "path": "rtsp-server/net/KqueueTaskScheduler.h",
    "content": "// Scott Xu\n// 2021-1-19\n\n#ifndef XOP_KQUEUE_TASK_SCHEDULER_H\n#define XOP_KQUEUE_TASK_SCHEDULER_H\n\n#include \"TaskScheduler.h\"\n#include <mutex>\n#include <unordered_map>\n\nnamespace xop {\nclass KqueueTaskScheduler : public TaskScheduler {\npublic:\n\texplicit KqueueTaskScheduler(int id = 0);\n\t~KqueueTaskScheduler() override;\n\n\tvoid UpdateChannel(const ChannelPtr &channel) override;\n\tvoid RemoveChannel(const ChannelPtr &channel) override;\n\n\t// timeout: ms\n\tbool HandleEvent(int timeout) override;\n\nprivate:\n\tvoid Update(int operation, const ChannelPtr &channel);\n\n\tint kqueuefd_ = -1;\n\tstd::mutex mutex_;\n\tstd::unordered_map<int, ChannelPtr> channels_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/Logger.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n\n#include \"Logger.h\"\n#include \"Timestamp.h\"\n#include <cstdarg>\n#include <cstring>\n#include <iostream>\n\nusing namespace xop;\n\nconst char *Priority_To_String[] = {\"DEBUG\", \"CONFIG\", \"INFO\", \"WARNING\",\n\t\t\t\t    \"ERROR\"};\n\nLogger::Logger() = default;\n\nLogger &Logger::Instance()\n{\n\tstatic Logger s_logger;\n\treturn s_logger;\n}\n\nLogger::~Logger() = default;\n\nvoid Logger::Init(char *pathname)\n{\n\tstd::unique_lock lock(mutex_);\n\n\tif (pathname != nullptr) {\n\t\tofs_.open(pathname, std::ios::out | std::ios::binary);\n\t\tif (ofs_.fail()) {\n\t\t\tstd::cerr << \"Failed to open logfile.\" << std::endl;\n\t\t}\n\t}\n}\n\nvoid Logger::Exit()\n{\n\tstd::unique_lock lock(mutex_);\n\n\tif (ofs_.is_open()) {\n\t\tofs_.close();\n\t}\n}\n\nvoid Logger::SetWriteCallback(const LogWriteCallbackFun writeCallback)\n{\n\t_writeCallback = writeCallback;\n}\n\nvoid Logger::Log(const Priority priority, const char *__file,\n\t\t const char *__func, const int __line, const char *fmt, ...)\n{\n\tstd::unique_lock lock(mutex_);\n\n\tchar buf[2048];\n\tauto buf_ptr = buf;\n\tauto buf_end = buf + sizeof(buf);\n\tbuf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, \"[%s][%s:%s:%d] \", Priority_To_String[priority], __file,\n\t\t__func, __line);\n\tva_list args;\n\tva_start(args, fmt);\n\tvsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args);\n\tva_end(args);\n\n\tthis->Write(std::string(buf));\n\t_writeCallback(priority, std::string(buf));\n}\n\nvoid Logger::Log2(const Priority priority, const char *fmt, ...)\n{\n\tstd::unique_lock lock(mutex_);\n\n\tchar buf[4096];\n\tauto buf_ptr = buf;\n\tauto buf_end = buf + sizeof(buf);\n\tbuf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, \"[%s] \", Priority_To_String[priority]);\n\tva_list args;\n\tva_start(args, fmt);\n\tvsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args);\n\tva_end(args);\n\n\tthis->Write(std::string(buf));\n\t_writeCallback(priority, std::string(buf));\n}\n\nvoid Logger::Write(const std::string &info)\n{\n\tif (ofs_.is_open()) {\n\t\tofs_ << \"[\" << Timestamp::Localtime() << \"]\" << info\n\t\t     << std::endl;\n\t}\n}\n"
  },
  {
    "path": "rtsp-server/net/Logger.h",
    "content": "// PHZ\n// 2020-5-15\n// Scott Xu\n// 2020-12-2\n// Add LogWriteCallbackFun.\n\n#ifndef XOP_LOGGER_H\n#define XOP_LOGGER_H\n\n#include <string>\n#include <mutex>\n#include <fstream>\n#include <iostream>\n\nnamespace xop {\n\nenum Priority {\n\tLOG_DEBUG,\n\tLOG_STATE,\n\tLOG_INFO,\n\tLOG_WARNING,\n\tLOG_ERROR,\n};\n\ntypedef void (*LogWriteCallbackFun)(Priority priority, std::string info);\n\nclass Logger {\npublic:\n\tLogger &operator=(const Logger &) = delete;\n\tLogger(const Logger &) = delete;\n\tstatic Logger &Instance();\n\tvirtual ~Logger();\n\n\tvoid Init(char *pathname = nullptr);\n\tvoid Exit();\n\tvoid SetWriteCallback(LogWriteCallbackFun writeCallback);\n\n\tvoid Log(Priority priority, const char *__file, const char *__func,\n\t\t int __line, const char *fmt, ...);\n\tvoid Log2(Priority priority, const char *fmt, ...);\n\nprivate:\n\tvoid Write(const std::string &buf);\n\tLogger();\n\n\tstd::mutex mutex_;\n\tstd::ofstream ofs_;\n\tLogWriteCallbackFun _writeCallback{};\n};\n\n}\n\n//#ifdef _DEBUG\n#define LOG_DEBUG(fmt, ...)                                            \\\n\txop::Logger::Instance().Log(LOG_DEBUG, __FILE__, __FUNCTION__, \\\n\t\t\t\t    __LINE__, fmt, ##__VA_ARGS__)\n//#else\n//#define LOG_DEBUG(fmt, ...)\n//#endif\n#define LOG_INFO(fmt, ...) \\\n\txop::Logger::Instance().Log2(LOG_INFO, fmt, ##__VA_ARGS__)\n#define LOG_ERROR(fmt, ...)                                            \\\n\txop::Logger::Instance().Log(LOG_ERROR, __FILE__, __FUNCTION__, \\\n\t\t\t\t    __LINE__, fmt, ##__VA_ARGS__)\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/MemoryManager.cpp",
    "content": "#include \"MemoryManager.h\"\n\nusing namespace xop;\n\nvoid *xop::Alloc(const uint32_t size)\n{\n\treturn MemoryManager::Instance().Alloc(size);\n}\n\nvoid xop::Free(void *ptr)\n{\n\treturn MemoryManager::Instance().Free(ptr);\n}\n\nMemoryPool::MemoryPool() = default;\n\nMemoryPool::~MemoryPool()\n{\n\tif (memory_) {\n\t\tfree(memory_);\n\t}\n}\n\nvoid MemoryPool::Init(const uint32_t size, const uint32_t n)\n{\n\tif (memory_) {\n\t\treturn;\n\t}\n\n\tblock_size_ = size;\n\tnum_blocks_ = n;\n\tmemory_ = static_cast<char *>(\n\t\tmalloc(num_blocks_ * (block_size_ + sizeof(MemoryBlock))));\n\thead_ = reinterpret_cast<MemoryBlock *>(memory_);\n\thead_->block_id = 1;\n\thead_->pool = this;\n\thead_->next = nullptr;\n\n\tMemoryBlock *current = head_;\n\tfor (uint32_t n = 1; n < num_blocks_; n++) {\n\t\tauto *next = reinterpret_cast<MemoryBlock *>(\n\t\t\tmemory_ + n * (block_size_ + sizeof(MemoryBlock)));\n\t\tnext->block_id = n + 1;\n\t\tnext->pool = this;\n\t\tnext->next = nullptr;\n\n\t\tcurrent->next = next;\n\t\tcurrent = next;\n\t}\n}\n\nvoid *MemoryPool::Alloc(const uint32_t size)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (head_ != nullptr) {\n\t\tMemoryBlock *block = head_;\n\t\thead_ = head_->next;\n\t\treturn reinterpret_cast<char *>(block) + sizeof(MemoryBlock);\n\t}\n\n\treturn nullptr;\n}\n\nvoid MemoryPool::Free(void *ptr)\n{\n\tif (const auto block = reinterpret_cast<MemoryBlock *>(\n\t\t    static_cast<char *>(ptr) - sizeof(MemoryBlock));\n\t    block->block_id != 0) {\n\t\tstd::lock_guard locker(mutex_);\n\t\tblock->next = head_;\n\t\thead_ = block;\n\t}\n}\n\nMemoryManager::MemoryManager()\n{\n\tmemory_pools_[0].Init(4096, 50);\n\tmemory_pools_[1].Init(40960, 10);\n\tmemory_pools_[2].Init(102400, 5);\n\t//memory_pools_[3].Init(204800, 2);\n}\n\nMemoryManager::~MemoryManager() = default;\n\nMemoryManager &MemoryManager::Instance()\n{\n\tstatic MemoryManager s_mgr;\n\treturn s_mgr;\n}\n\nvoid *MemoryManager::Alloc(const uint32_t size)\n{\n\tfor (auto &memory_pool : memory_pools_) {\n\t\tif (size <= memory_pool.BolckSize()) {\n\t\t\tif (void *ptr = memory_pool.Alloc(size);\n\t\t\t    ptr != nullptr) {\n\t\t\t\treturn ptr;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tconst auto block =\n\t\tstatic_cast<MemoryBlock *>(malloc(size + sizeof(MemoryBlock)));\n\tblock->block_id = 0;\n\tblock->pool = nullptr;\n\tblock->next = nullptr;\n\treturn reinterpret_cast<char *>(block) + sizeof(MemoryBlock);\n}\n\nvoid MemoryManager::Free(void *ptr)\n{\n\tconst auto block = reinterpret_cast<MemoryBlock *>(\n\t\tstatic_cast<char *>(ptr) - sizeof(MemoryBlock));\n\n\tif (MemoryPool *pool = block->pool;\n\t    pool != nullptr && block->block_id > 0) {\n\t\tpool->Free(ptr);\n\t} else {\n\t\tfree(block);\n\t}\n}\n"
  },
  {
    "path": "rtsp-server/net/MemoryManager.h",
    "content": "#ifndef XOP_MEMMORY_MANAGER_H\n#define XOP_MEMMORY_MANAGER_H\n\n#include <cstdint>\n#include <mutex>\n\nnamespace xop {\n\nvoid *Alloc(uint32_t size);\nvoid Free(void *ptr);\n\nclass MemoryPool;\n\nstruct MemoryBlock {\n\tuint32_t block_id = 0;\n\tMemoryPool *pool = nullptr;\n\tMemoryBlock *next = nullptr;\n};\n\nclass MemoryPool {\npublic:\n\tMemoryPool();\n\tvirtual ~MemoryPool();\n\n\tvoid Init(uint32_t size, uint32_t n);\n\tvoid *Alloc(uint32_t size);\n\tvoid Free(void *ptr);\n\n\tsize_t BolckSize() const { return block_size_; }\n\n\t//private:\n\tchar *memory_ = nullptr;\n\tuint32_t block_size_ = 0;\n\tuint32_t num_blocks_ = 0;\n\tMemoryBlock *head_ = nullptr;\n\tstd::mutex mutex_;\n};\n\nclass MemoryManager {\npublic:\n\tstatic MemoryManager &Instance();\n\t~MemoryManager();\n\n\tvoid *Alloc(uint32_t size);\n\tvoid Free(void *ptr);\n\nprivate:\n\tMemoryManager();\n\n\tstatic constexpr int kMaxMemoryPool = 3;\n\tMemoryPool memory_pools_[kMaxMemoryPool];\n};\n\n}\n#endif\n"
  },
  {
    "path": "rtsp-server/net/Pipe.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#include \"Pipe.h\"\n#include \"SocketUtil.h\"\n#include <random>\n\nusing namespace xop;\n\nPipe::Pipe()\n{\n\tmemset(pipe_fd_, 0, 2);\n}\n\nPipe::~Pipe()\n{\n\tClose();\n}\n\nbool Pipe::Create()\n{\n#if defined(__linux) || defined(__linux__)\n\tif (pipe2(pipe_fd_, O_NONBLOCK | O_CLOEXEC) < 0) {\n\t\treturn false;\n\t}\n#else\n\tconst TcpSocket rp(socket(AF_INET, SOCK_STREAM, 0));\n\tconst TcpSocket wp(socket(AF_INET, SOCK_STREAM, 0));\n\tstd::random_device rd;\n\n\tpipe_fd_[0] = rp.GetSocket();\n\tpipe_fd_[1] = wp.GetSocket();\n\tuint16_t port = 0;\n\tint again = 5;\n\n\twhile (again--) {\n\t\tport = static_cast<uint16_t>(rd());\n\t\tif (rp.Bind(\"127.0.0.1\", port)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (again == 0) {\n\t\treturn false;\n\t}\n\n\tif (!rp.Listen(1)) {\n\t\treturn false;\n\t}\n\n\tif (!wp.Connect(\"127.0.0.1\", port)) {\n\t\treturn false;\n\t}\n\n\tpipe_fd_[0] = rp.Accept(); //TODO\n\tif (pipe_fd_[0] < 0) {\n\t\treturn false;\n\t}\n\n\tSocketUtil::SetNonBlock(pipe_fd_[0]);\n\tSocketUtil::SetNonBlock(pipe_fd_[1]);\n#endif\n\treturn true;\n}\n\nint Pipe::Write(void *buf, const int len) const\n{\n#if defined(WIN32) || defined(_WIN32)\n\treturn ::send(pipe_fd_[1], static_cast<char *>(buf), len, 0);\n#else\n\treturn ::write(pipe_fd_[1], buf, len);\n#endif\n}\n\nint Pipe::Read(void *buf, const int len) const\n{\n#if defined(WIN32) || defined(_WIN32)\n\treturn recv(pipe_fd_[0], static_cast<char *>(buf), len, 0);\n#else\n\treturn ::read(pipe_fd_[0], buf, len);\n#endif\n}\n\nvoid Pipe::Close() const\n{\n#if defined(WIN32) || defined(_WIN32)\n\tclosesocket(pipe_fd_[0]);\n\tclosesocket(pipe_fd_[1]);\n#else\n\t::close(pipe_fd_[0]);\n\t::close(pipe_fd_[1]);\n#endif\n}\n"
  },
  {
    "path": "rtsp-server/net/Pipe.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_PIPE_H\n#define XOP_PIPE_H\n\n#include \"TcpSocket.h\"\n\nnamespace xop {\n\nclass Pipe {\npublic:\n\tPipe();\n\tvirtual ~Pipe();\n\tbool Create();\n\tint Write(void *buf, int len) const;\n\tint Read(void *buf, int len) const;\n\tvoid Close() const;\n\n\tSOCKET Read() const { return pipe_fd_[0]; }\n\tSOCKET Write() const { return pipe_fd_[1]; }\n\nprivate:\n\tSOCKET pipe_fd_[2]{};\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/RingBuffer.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_RING_BUFFER_H\n#define XOP_RING_BUFFER_H\n\n#include <vector>\n#include <atomic>\n\nnamespace xop {\n\ntemplate<typename T> class RingBuffer {\npublic:\n\texplicit RingBuffer(unsigned capacity = 60)\n\t\t: capacity_(capacity), num_datas_(0), buffer_(capacity)\n\t{\n\t}\n\n\tvirtual ~RingBuffer() {}\n\n\tbool Push(const T &data) { return PushData(std::forward<T>(data)); }\n\n\tbool Push(T &&data) { return PushData(data); }\n\n\tbool Pop(T &data)\n\t{\n\t\tif (num_datas_ > 0) {\n\t\t\tdata = std::move(buffer_[get_pos_]);\n\t\t\tAdd(get_pos_);\n\t\t\t--num_datas_;\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tbool IsFull() const { return num_datas_ == capacity_ ? true : false; }\n\n\tbool IsEmpty() const { return num_datas_ == 0 ? true : false; }\n\n\tint Size() const { return num_datas_; }\n\nprivate:\n\ttemplate<typename F> bool PushData(F &&data)\n\t{\n\t\tif (num_datas_ < capacity_) {\n\t\t\tbuffer_[put_pos_] = std::forward<F>(data);\n\t\t\tAdd(put_pos_);\n\t\t\t++num_datas_;\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tvoid Add(int &pos) const { pos = pos + 1 == capacity_ ? 0 : pos + 1; }\n\n\tint capacity_ = 0;\n\tint put_pos_ = 0;\n\tint get_pos_ = 0;\n\n\tstd::atomic_int num_datas_;\n\tstd::vector<T> buffer_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/SelectTaskScheduler.cpp",
    "content": "// PHZ\n// 2018-5-15\n\n#include \"SelectTaskScheduler.h\"\n#include \"Timer.h\"\n#include <cstring>\n#include <forward_list>\n\nusing namespace xop;\n\n#define SELECT_CTL_ADD 0\n#define SELECT_CTL_MOD 1\n#define SELECT_CTL_DEL 2\n\nSelectTaskScheduler::SelectTaskScheduler(const int id) : TaskScheduler(id)\n{\n\tFD_ZERO(&fd_read_backup_);\n\tFD_ZERO(&fd_write_backup_);\n\tFD_ZERO(&fd_exp_backup_);\n\n\tthis->SelectTaskScheduler::UpdateChannel(wakeup_channel_);\n}\n\nSelectTaskScheduler::~SelectTaskScheduler() = default;\n\nvoid SelectTaskScheduler::UpdateChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n\n\tif (SOCKET socket = channel->GetSocket();\n\t    channels_.find(socket) != channels_.end()) {\n\t\tif (channel->IsNoneEvent()) {\n\t\t\tis_fd_read_reset_ = true;\n\t\t\tis_fd_write_reset_ = true;\n\t\t\tis_fd_exp_reset_ = true;\n\t\t\tchannels_.erase(socket);\n\t\t} else {\n\t\t\t//is_fd_read_reset_ = true;\n\t\t\tis_fd_write_reset_ = true;\n\t\t}\n\t} else {\n\t\tif (!channel->IsNoneEvent()) {\n\t\t\tchannels_.emplace(socket, channel);\n\t\t\tis_fd_read_reset_ = true;\n\t\t\tis_fd_write_reset_ = true;\n\t\t\tis_fd_exp_reset_ = true;\n\t\t}\n\t}\n}\n\nvoid SelectTaskScheduler::RemoveChannel(const ChannelPtr &channel)\n{\n\tstd::lock_guard lock(mutex_);\n\n\tif (const SOCKET fd = channel->GetSocket();\n\t    channels_.find(fd) != channels_.end()) {\n\t\tis_fd_read_reset_ = true;\n\t\tis_fd_write_reset_ = true;\n\t\tis_fd_exp_reset_ = true;\n\t\tchannels_.erase(fd);\n\t}\n}\n\nbool SelectTaskScheduler::HandleEvent(int timeout)\n{\n\tif (channels_.empty()) {\n\t\tif (timeout <= 0) {\n\t\t\ttimeout = 10;\n\t\t}\n\n\t\tTimer::Sleep(timeout);\n\t\treturn true;\n\t}\n\n\tfd_set fd_read{};\n\tfd_set fd_write{};\n\tfd_set fd_exp{};\n\tFD_ZERO(&fd_read);\n\tFD_ZERO(&fd_write);\n\tFD_ZERO(&fd_exp);\n\tbool fd_read_reset = false;\n\tbool fd_write_reset = false;\n\tbool fd_exp_reset = false;\n\n\tif (is_fd_read_reset_ || is_fd_write_reset_ || is_fd_exp_reset_) {\n\t\tif (is_fd_exp_reset_) {\n\t\t\tmaxfd_ = 0;\n\t\t}\n\n\t\tstd::lock_guard lock(mutex_);\n\t\tfor (const auto & [fst, snd] : channels_) {\n\t\t\tconst uint32_t events = snd->GetEvents();\n\t\t\tconst SOCKET fd = snd->GetSocket();\n\n\t\t\tif (is_fd_read_reset_ && (events & EVENT_IN)) {\n\t\t\t\tFD_SET(fd, &fd_read);\n\t\t\t}\n\n\t\t\tif (is_fd_write_reset_ && (events & EVENT_OUT)) {\n\t\t\t\tFD_SET(fd, &fd_write);\n\t\t\t}\n\n\t\t\tif (is_fd_exp_reset_) {\n\t\t\t\tFD_SET(fd, &fd_exp);\n\t\t\t\tif (fd > maxfd_) {\n\t\t\t\t\tmaxfd_ = fd;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfd_read_reset = is_fd_read_reset_;\n\t\tfd_write_reset = is_fd_write_reset_;\n\t\tfd_exp_reset = is_fd_exp_reset_;\n\t\tis_fd_read_reset_ = false;\n\t\tis_fd_write_reset_ = false;\n\t\tis_fd_exp_reset_ = false;\n\t}\n\n\tif (fd_read_reset) {\n\t\tFD_ZERO(&fd_read_backup_);\n\t\tmemcpy(&fd_read_backup_, &fd_read, sizeof(fd_set));\n\t} else {\n\t\tmemcpy(&fd_read, &fd_read_backup_, sizeof(fd_set));\n\t}\n\n\tif (fd_write_reset) {\n\t\tFD_ZERO(&fd_write_backup_);\n\t\tmemcpy(&fd_write_backup_, &fd_write, sizeof(fd_set));\n\t} else {\n\t\tmemcpy(&fd_write, &fd_write_backup_, sizeof(fd_set));\n\t}\n\n\tif (fd_exp_reset) {\n\t\tFD_ZERO(&fd_exp_backup_);\n\t\tmemcpy(&fd_exp_backup_, &fd_exp, sizeof(fd_set));\n\t} else {\n\t\tmemcpy(&fd_exp, &fd_exp_backup_, sizeof(fd_set));\n\t}\n\n\tif (timeout <= 0) {\n\t\ttimeout = 10;\n\t}\n\n#if defined(WIN32) || defined(_WIN32)\n    const timeval tv = {timeout / 1000, timeout % 1000 * 1000};\n#else\n    timeval tv = {timeout / 1000, timeout % 1000 * 1000};\n#endif\n    const int ret = select(static_cast<int>(maxfd_) + 1, &fd_read,\n\t\t\t       &fd_write, &fd_exp, &tv);\n\n\tif (ret < 0) {\n#if defined(WIN32) || defined(_WIN32)\n#else\n\t\tif (errno == EINTR) {\n\t\t\treturn true;\n\t\t}\n#endif\n\t\treturn false;\n\t}\n\n\tstd::forward_list<std::pair<ChannelPtr, uint32_t>> event_list;\n\tif (ret > 0) {\n\t\tstd::lock_guard lock(mutex_);\n\t\tfor (const auto & [fst, snd] : channels_) {\n\t\t\tuint32_t events = 0;\n\t\t\tconst SOCKET socket = snd->GetSocket();\n\n\t\t\tif (FD_ISSET(socket, &fd_read)) {\n\t\t\t\tevents |= EVENT_IN;\n\t\t\t}\n\n\t\t\tif (FD_ISSET(socket, &fd_write)) {\n\t\t\t\tevents |= EVENT_OUT;\n\t\t\t}\n\n\t\t\tif (FD_ISSET(socket, &fd_exp)) {\n\t\t\t\tevents |= EVENT_HUP; // close\n\t\t\t}\n\n\t\t\tif (events != 0) {\n\t\t\t\tevent_list.emplace_front(snd, events);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const auto & [fst, snd] : event_list) {\n\t\tfst->HandleEvent(snd);\n\t}\n\n\treturn true;\n}\n"
  },
  {
    "path": "rtsp-server/net/SelectTaskScheduler.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_SELECT_TASK_SCHEDULER_H\n#define XOP_SELECT_TASK_SCHEDULER_H\n\n#include \"TaskScheduler.h\"\n#include \"Socket.h\"\n#include <mutex>\n#include <unordered_map>\n\n#if defined(WIN32) || defined(_WIN32)\n#else\n#include <sys/select.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n#endif\n\nnamespace xop {\n\nclass SelectTaskScheduler : public TaskScheduler {\npublic:\n\texplicit SelectTaskScheduler(int id = 0);\n\t~SelectTaskScheduler() override;\n\n\tvoid UpdateChannel(const ChannelPtr &channel) override;\n\tvoid RemoveChannel(const ChannelPtr &channel) override;\n\tbool HandleEvent(int timeout) override;\n\nprivate:\n\tfd_set fd_read_backup_{};\n\tfd_set fd_write_backup_{};\n\tfd_set fd_exp_backup_{};\n\tSOCKET maxfd_ = 0;\n\n\tbool is_fd_read_reset_ = false;\n\tbool is_fd_write_reset_ = false;\n\tbool is_fd_exp_reset_ = false;\n\n\tstd::mutex mutex_;\n\tstd::unordered_map<SOCKET, ChannelPtr> channels_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/Socket.h",
    "content": "// PHZ\n// 2018-5-15\n// Scott Xu\n// 2020-12-2 Add IPv6 Support.\n\n#ifndef XOP_SOCKET_H\n#define XOP_SOCKET_H\n\n#if defined(WIN32) || defined(_WIN32)\n#define FD_SETSIZE 1024\n#define WIN32_LEAN_AND_MEAN\n#define _WINSOCK_DEPRECATED_NO_WARNINGS\n#include <WinSock2.h>\n#include <Windows.h>\n#include <WS2tcpip.h>\n#include <iphlpapi.h>\n#define SHUT_RD 0\n#define SHUT_WR 1\n#define SHUT_RDWR 2\n#else\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/ioctl.h>\n#include <netinet/in.h>\n#include <netinet/ip.h>\n#include <arpa/inet.h>\n#include <net/ethernet.h>\n#include <net/route.h>\n#include <netdb.h>\n#include <net/if.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <sys/select.h>\n#if defined(__linux) || defined(__linux__)\n#include <netinet/ether.h>\n#include <netpacket/packet.h>\n#endif\n#define SOCKET int\n#define INVALID_SOCKET (-1)\n#define SOCKET_ERROR (-1)\n\n#define INET_ADDRSTRLEN 16  /* for IPv4 dotted-decimal */\n#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */\n#endif\n\n#include <cstdint>\n#include <cstring>\n\n#endif // _XOP_SOCKET_H\n"
  },
  {
    "path": "rtsp-server/net/SocketUtil.cpp",
    "content": "// PHZ\n// 2018-5-15\n// Scott Xu\n// 2020-12-2 Add IPv6 Support.\n\n#include \"SocketUtil.h\"\n#include \"Socket.h\"\n#include <iostream>\n\nusing namespace xop;\n\nbool SocketUtil::Bind(const SOCKET sockfd, const std::string &ip,\n\t\t      const uint16_t port, const bool ipv6)\n{\n\tsockaddr *psockaddr;\n\tsocklen_t addrlen;\n\tif (ipv6) {\n\t\tsockaddr_in6 addr = {0};\n\t\taddr.sin6_family = AF_INET6;\n\t\taddr.sin6_port = htons(port);\n\t\tinet_pton(AF_INET6, ip.c_str(), &addr.sin6_addr);\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t\taddrlen = sizeof(addr);\n\t} else {\n\t\tsockaddr_in addr = {0};\n\t\taddr.sin_family = AF_INET;\n\t\taddr.sin_port = htons(port);\n\t\tinet_pton(AF_INET, ip.c_str(), &addr.sin_addr);\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t\taddrlen = sizeof(addr);\n\t}\n\n\tif (bind(sockfd, psockaddr, addrlen) == SOCKET_ERROR) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid SocketUtil::SetNonBlock(const SOCKET fd)\n{\n#if defined(WIN32) || defined(_WIN32)\n\tunsigned long on = 1;\n\tioctlsocket(fd, FIONBIO, &on);\n#else\n\tint flags = fcntl(fd, F_GETFL, 0);\n\tfcntl(fd, F_SETFL, flags | O_NONBLOCK);\n#endif\n}\n\nvoid SocketUtil::SetBlock(const SOCKET fd, const int write_timeout)\n{\n#if defined(WIN32) || defined(_WIN32)\n\tunsigned long on = 0;\n\tioctlsocket(fd, FIONBIO, &on);\n#else\n\tint flags = fcntl(fd, F_GETFL, 0);\n\tfcntl(fd, F_SETFL, flags & (~O_NONBLOCK));\n#endif\n\tif (write_timeout > 0) {\n#ifdef SO_SNDTIMEO\n#if defined(WIN32) || defined(_WIN32)\n\t\tauto ms = static_cast<unsigned long>(write_timeout);\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,\n\t\t\t   reinterpret_cast<char *>(&ms),\n\t\t\t   sizeof(unsigned long));\n#else\n\t\tstruct timeval tv = {write_timeout / 1000,\n\t\t\t\t     (write_timeout % 1000) * 1000};\n\t\tsetsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof tv);\n#endif\n#endif\n\t}\n}\n\nvoid SocketUtil::SetReuseAddr(const SOCKET sockfd)\n{\n\tconstexpr int on = 1;\n\tsetsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,\n\t\t   reinterpret_cast<const char *>(&on), sizeof on);\n}\n\nvoid SocketUtil::SetReusePort(const SOCKET sockfd)\n{\n#ifdef SO_REUSEPORT\n\tint on = 1;\n\tsetsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char *)&on,\n\t\t   sizeof(on));\n#endif\n}\n\nvoid SocketUtil::SetNoDelay(const SOCKET sockfd)\n{\n#ifdef TCP_NODELAY\n\tint on = 1;\n\tint ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,\n\t\t\t     reinterpret_cast<char *>(&on), sizeof on);\n#endif\n}\n\nvoid SocketUtil::SetKeepAlive(const SOCKET sockfd)\n{\n\tint on = 1;\n\tsetsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,\n\t\t   reinterpret_cast<char *>(&on), sizeof on);\n}\n\nvoid SocketUtil::SetNoSigpipe(const SOCKET sockfd)\n{\n#ifdef SO_NOSIGPIPE\n\tint on = 1;\n\tsetsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (char *)&on, sizeof(on));\n#endif\n}\n\nvoid SocketUtil::SetSendBufSize(const SOCKET sockfd, int size)\n{\n\tsetsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,\n\t\t   reinterpret_cast<char *>(&size), sizeof size);\n}\n\nvoid SocketUtil::SetRecvBufSize(const SOCKET sockfd, int size)\n{\n\tsetsockopt(sockfd, SOL_SOCKET, SO_RCVBUF,\n\t\t   reinterpret_cast<char *>(&size), sizeof size);\n}\n\nstd::string SocketUtil::GetPeerIp(const SOCKET sockfd, const bool ipv6)\n{\n\tif (ipv6) {\n\t\tsockaddr_in6 addr = {0};\n\t\tchar str[INET6_ADDRSTRLEN] = \"::0\";\n\t\tif (GetPeerAddr6(sockfd, &addr) == 0)\n\t\t\tinet_ntop(AF_INET6, &addr.sin6_addr, str, sizeof str);\n\t\treturn str;\n\t}\n\tsockaddr_in addr = {0};\n\tchar str[INET_ADDRSTRLEN] = \"0.0.0.0\";\n\tif (GetPeerAddr(sockfd, &addr) == 0)\n\t\tinet_ntop(AF_INET, &addr.sin_addr, str, sizeof str);\n\treturn str;\n}\n\nstd::string SocketUtil::GetSocketIp(const SOCKET sockfd, const bool ipv6)\n{\n\tif (ipv6) {\n\t\tsockaddr_in6 addr = {0};\n\t\tchar str[INET6_ADDRSTRLEN] = \"::1\";\n\t\tif (GetSocketAddr6(sockfd, &addr) == 0)\n\t\t\tinet_ntop(AF_INET6, &addr.sin6_addr, str, sizeof str);\n\t\treturn str;\n\t}\n\tsockaddr_in addr = {0};\n\tchar str[INET_ADDRSTRLEN] = \"127.0.0.1\";\n\tif (GetSocketAddr(sockfd, &addr) == 0)\n\t\tinet_ntop(AF_INET, &addr.sin_addr, str, sizeof str);\n\treturn str;\n}\n\nuint16_t SocketUtil::GetPeerPort(const SOCKET sockfd, const bool ipv6)\n{\n\tif (ipv6) {\n\t\tsockaddr_in6 addr = {0};\n\t\tif (GetPeerAddr6(sockfd, &addr) == 0)\n\t\t\treturn ntohs(addr.sin6_port);\n\t}\n\tsockaddr_in addr = {0};\n\tif (GetPeerAddr(sockfd, &addr) == 0)\n\t\treturn ntohs(addr.sin_port);\n\n\treturn 0;\n}\n\nint SocketUtil::GetPeerAddr(const SOCKET sockfd, sockaddr_in *addr)\n{\n\tsocklen_t addrlen = sizeof(struct sockaddr_in);\n\treturn getpeername(sockfd, reinterpret_cast<sockaddr *>(addr),\n\t\t\t   &addrlen);\n}\n\nint SocketUtil::GetPeerAddr6(const SOCKET sockfd, sockaddr_in6 *addr)\n{\n\tsocklen_t addrlen = sizeof(struct sockaddr_in6);\n\treturn getpeername(sockfd, reinterpret_cast<sockaddr *>(addr),\n\t\t\t   &addrlen);\n}\n\nint SocketUtil::GetSocketAddr(const SOCKET sockfd, sockaddr_in *addr)\n{\n\tsocklen_t addrlen = sizeof(struct sockaddr_in);\n\treturn getsockname(sockfd, reinterpret_cast<sockaddr *>(addr),\n\t\t\t   &addrlen);\n}\n\nint SocketUtil::GetSocketAddr6(const SOCKET sockfd, sockaddr_in6 *addr)\n{\n\tsocklen_t addrlen = sizeof(struct sockaddr_in6);\n\treturn getsockname(sockfd, reinterpret_cast<sockaddr *>(addr),\n\t\t\t   &addrlen);\n}\n\nvoid SocketUtil::Close(const SOCKET sockfd)\n{\n#if defined(WIN32) || defined(_WIN32)\n\t::closesocket(sockfd);\n#else\n\t::close(sockfd);\n#endif\n}\n\nbool SocketUtil::Connect(const SOCKET sockfd, const std::string &ip,\n\t\t\t const uint16_t port, const int timeout,\n\t\t\t const bool ipv6)\n{\n\tbool is_connected = true;\n\tif (timeout > 0) {\n\t\tSetNonBlock(sockfd);\n\t}\n\n\tsockaddr *psockaddr;\n\tsocklen_t addrlen;\n\tif (ipv6) {\n\t\tsockaddr_in6 addr = {0};\n\t\taddr.sin6_family = AF_INET6;\n\t\taddr.sin6_port = htons(port);\n\t\tinet_pton(AF_INET6, ip.c_str(), &addr.sin6_addr);\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t\taddrlen = sizeof(addr);\n\t} else {\n\t\tsockaddr_in addr = {0};\n\t\taddr.sin_family = AF_INET;\n\t\taddr.sin_port = htons(port);\n\t\tinet_pton(AF_INET, ip.c_str(), &addr.sin_addr);\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t\taddrlen = sizeof(addr);\n\t}\n\n\tif (connect(sockfd, psockaddr, addrlen) == SOCKET_ERROR) {\n\t\tif (timeout > 0) {\n\t\t\tis_connected = false;\n\t\t\tfd_set fd_write{};\n\t\t\tFD_ZERO(&fd_write);\n\t\t\tFD_SET(sockfd, &fd_write);\n#if defined(WIN32) || defined(_WIN32)\n            const timeval tv = {timeout / 1000,\n\t\t\t\t\t    timeout % 1000 * 1000};\n#else\n            timeval tv = {timeout / 1000,\n\t\t\t\t\t    timeout % 1000 * 1000};\n#endif\n            select(static_cast<int>(sockfd) + 1, nullptr, &fd_write,\n\t\t\t       nullptr, &tv);\n\n\t\t\tif (FD_ISSET(sockfd, &fd_write)) {\n\t\t\t\tis_connected = true;\n\t\t\t}\n\t\t\tSetBlock(sockfd);\n\t\t} else {\n\t\t\tis_connected = false;\n\t\t}\n\t}\n\n\treturn is_connected;\n}\n\nbool SocketUtil::IsIpv6Address(const std::string &ip)\n{\n\tin6_addr addr6{};\n\treturn inet_pton(AF_INET6, ip.c_str(), &addr6) > 0;\n}\n\nbool SocketUtil::IsIpv6Socket(const SOCKET sockfd)\n{\n\tsockaddr_in6 addr = {0};\n\tsocklen_t addrlen = sizeof addr;\n\tgetsockname(sockfd, reinterpret_cast<sockaddr *>(&addr), &addrlen);\n\tif (addr.sin6_family == AF_INET6)\n\t\treturn true;\n\treturn false;\n}\n"
  },
  {
    "path": "rtsp-server/net/SocketUtil.h",
    "content": "// PHZ\n// 2018-5-15\n// Scott Xu\n// 2020-12-2 Add IPv6 Support.\n\n#ifndef XOP_SOCKET_UTIL_H\n#define XOP_SOCKET_UTIL_H\n\n#include \"Socket.h\"\n#include <string>\n\nnamespace xop {\n\nclass SocketUtil {\npublic:\n\tstatic bool Bind(SOCKET sockfd, const std::string &ip, uint16_t port,\n\t\t\t bool ipv6 = false);\n\tstatic void SetNonBlock(SOCKET fd);\n\tstatic void SetBlock(SOCKET fd, int write_timeout = 0);\n\tstatic void SetReuseAddr(SOCKET fd);\n\tstatic void SetReusePort(SOCKET sockfd);\n\tstatic void SetNoDelay(SOCKET sockfd);\n\tstatic void SetKeepAlive(SOCKET sockfd);\n\tstatic void SetNoSigpipe(SOCKET sockfd);\n\tstatic void SetSendBufSize(SOCKET sockfd, int size);\n\tstatic void SetRecvBufSize(SOCKET sockfd, int size);\n\tstatic std::string GetPeerIp(SOCKET sockfd, bool ipv6 = false);\n\tstatic std::string GetSocketIp(SOCKET sockfd, bool ipv6 = false);\n\tstatic uint16_t GetPeerPort(SOCKET sockfd, bool ipv6 = false);\n\tstatic int GetPeerAddr(SOCKET sockfd, sockaddr_in *addr);\n\tstatic int GetPeerAddr6(SOCKET sockfd, sockaddr_in6 *addr);\n\tstatic int GetSocketAddr(SOCKET sockfd, sockaddr_in *addr);\n\tstatic int GetSocketAddr6(SOCKET sockfd, sockaddr_in6 *addr);\n\tstatic void Close(SOCKET sockfd);\n\tstatic bool Connect(SOCKET sockfd, const std::string &ip, uint16_t port,\n\t\t\t    int timeout = 0, bool ipv6 = false);\n\tstatic bool IsIpv6Address(const std::string &ip);\n\tstatic bool IsIpv6Socket(SOCKET sockfd);\n};\n\n}\n\n#endif // _SOCKET_UTIL_H\n"
  },
  {
    "path": "rtsp-server/net/TaskScheduler.cpp",
    "content": "#include \"TaskScheduler.h\"\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <signal.h>\n#endif\n\nusing namespace xop;\n\nTaskScheduler::TaskScheduler(const int id)\n\t: id_(id),\n\t  is_shutdown_(false),\n\t  wakeup_pipe_(new Pipe()),\n\t  trigger_events_(new xop::RingBuffer<TriggerEvent>(kMaxTriggetEvents))\n{\n\tstatic std::once_flag flag;\n\tstd::call_once(flag, [] {\n#if defined(WIN32) || defined(_WIN32)\n\t\tWSADATA wsa_data;\n\t\tif (WSAStartup(MAKEWORD(2, 2), &wsa_data)) {\n\t\t\tWSACleanup();\n\t\t}\n#endif\n\t});\n\n\tif (wakeup_pipe_->Create()) {\n\t\twakeup_channel_.reset(new Channel(wakeup_pipe_->Read()));\n\t\twakeup_channel_->EnableReading();\n\t\twakeup_channel_->SetReadCallback([this]() { this->Wake(); });\n\t}\n}\n\nTaskScheduler::~TaskScheduler() = default;\n\nvoid TaskScheduler::Start()\n{\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n\tsignal(SIGPIPE, SIG_IGN);\n\tsignal(SIGQUIT, SIG_IGN);\n\tsignal(SIGUSR1, SIG_IGN);\n\tsignal(SIGTERM, SIG_IGN);\n\tsignal(SIGKILL, SIG_IGN);\n#endif\n\tis_shutdown_ = false;\n\twhile (!is_shutdown_) {\n\t\tthis->HandleTriggerEvent();\n\t\tthis->timer_queue_.HandleTimerEvent();\n\t\tconst int64_t timeout = this->timer_queue_.GetTimeRemaining();\n\t\tthis->HandleEvent(static_cast<int>(timeout));\n\t}\n}\n\nvoid TaskScheduler::Stop()\n{\n\tis_shutdown_ = true;\n\tchar event = kTriggetEvent;\n\twakeup_pipe_->Write(&event, 1);\n}\n\nTimerId TaskScheduler::AddTimer(const TimerEvent &timerEvent,\n\t\t\t\tconst uint32_t msec)\n{\n\tconst TimerId id = timer_queue_.AddTimer(timerEvent, msec);\n\treturn id;\n}\n\nvoid TaskScheduler::RemoveTimer(const TimerId timerId)\n{\n\ttimer_queue_.RemoveTimer(timerId);\n}\n\nbool TaskScheduler::AddTriggerEvent(TriggerEvent callback)\n{\n\tif (trigger_events_->Size() < kMaxTriggetEvents) {\n\t\tstd::lock_guard lock(mutex_);\n\t\tchar event = kTriggetEvent;\n\t\ttrigger_events_->Push(std::move(callback));\n\t\twakeup_pipe_->Write(&event, 1);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid TaskScheduler::Wake() const\n{\n\tchar event[10] = {0};\n\twhile (wakeup_pipe_->Read(event, 10) > 0)\n\t\t;\n}\n\nvoid TaskScheduler::HandleTriggerEvent() const\n{\n\tdo {\n\t\tif (TriggerEvent callback; trigger_events_->Pop(callback)) {\n\t\t\tcallback();\n\t\t}\n\t} while (trigger_events_->Size() > 0);\n}\n"
  },
  {
    "path": "rtsp-server/net/TaskScheduler.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_TASK_SCHEDULER_H\n#define XOP_TASK_SCHEDULER_H\n\n#include \"Channel.h\"\n#include \"Pipe.h\"\n#include \"Timer.h\"\n#include \"RingBuffer.h\"\n\nnamespace xop {\n\ntypedef std::function<void()> TriggerEvent;\n\nclass TaskScheduler {\npublic:\n\texplicit TaskScheduler(int id = 1);\n\tvirtual ~TaskScheduler();\n\n\tvoid Start();\n\tvoid Stop();\n\tTimerId AddTimer(const TimerEvent &timerEvent, uint32_t msec);\n\tvoid RemoveTimer(TimerId timerId);\n\tbool AddTriggerEvent(TriggerEvent callback);\n\n\tvirtual void UpdateChannel(const ChannelPtr &channel) = 0;\n\tvirtual void RemoveChannel(const ChannelPtr &channel) = 0;\n\tvirtual bool HandleEvent(int timeout) = 0;\n\n\tint GetId() const { return id_; }\n\nprotected:\n\tvoid Wake() const;\n\tvoid HandleTriggerEvent() const;\n\n\tint id_ = 0;\n\tstd::atomic_bool is_shutdown_;\n\tstd::unique_ptr<Pipe> wakeup_pipe_;\n\tstd::shared_ptr<Channel> wakeup_channel_;\n\tstd::unique_ptr<RingBuffer<TriggerEvent>> trigger_events_;\n\n\tstd::mutex mutex_;\n\tTimerQueue timer_queue_;\n\n\tstatic constexpr char kTriggetEvent = 1;\n\tstatic constexpr char kTimerEvent = 2;\n\tstatic constexpr int kMaxTriggetEvents = 50000;\n};\n\n}\n#endif\n"
  },
  {
    "path": "rtsp-server/net/TcpConnection.cpp",
    "content": "//Scott Xu\n//2020-12-6 Add IPv6 support.\n#include \"TcpConnection.h\"\n#include \"SocketUtil.h\"\n\nusing namespace xop;\n\nTcpConnection::TcpConnection(\n\tconst SOCKET sockfd, std::shared_ptr<TaskScheduler> task_scheduler)\n\t: read_buffer_(new BufferReader),\n\t  write_buffer_(new BufferWriter(500)),\n\t  is_closed_(false),\n\t  task_scheduler_(std::move(task_scheduler)),\n\t  channel_(new Channel(sockfd)),\n\t  ipv6_(SocketUtil::IsIpv6Socket(sockfd))\n{\n\tchannel_->SetReadCallback([this] { this->HandleRead(); });\n\tchannel_->SetWriteCallback([this] { this->HandleWrite(); });\n\tchannel_->SetCloseCallback([this] { this->HandleClose(); });\n\tchannel_->SetErrorCallback([this] { this->HandleError(); });\n\n\tSocketUtil::SetNonBlock(sockfd);\n\tSocketUtil::SetSendBufSize(sockfd, 100 * 1024);\n\tSocketUtil::SetKeepAlive(sockfd);\n\n\tchannel_->EnableReading();\n\ttask_scheduler_->UpdateChannel(channel_);\n}\n\nTcpConnection::~TcpConnection()\n{\n\tif (const SOCKET fd = channel_->GetSocket(); fd > 0) {\n\t\tSocketUtil::Close(fd);\n\t}\n}\n\nvoid TcpConnection::Send(const std::shared_ptr<char> &data, const size_t size)\n{\n\tif (!is_closed_) {\n\t\tmutex_.lock();\n\t\twrite_buffer_->Append(data, size);\n\t\tmutex_.unlock();\n\n\t\tthis->HandleWrite();\n\t}\n}\n\nvoid TcpConnection::Send(const char *data, const size_t size)\n{\n\tif (!is_closed_) {\n\t\tmutex_.lock();\n\t\twrite_buffer_->Append(data, size);\n\t\tmutex_.unlock();\n\n\t\tthis->HandleWrite();\n\t}\n}\n\nvoid TcpConnection::Disconnect()\n{\n\tstd::lock_guard lock(mutex_);\n\ttask_scheduler_->AddTriggerEvent([this] { this->Close(); });\n}\n\nvoid TcpConnection::HandleRead()\n{\n\t{\n\t\tstd::lock_guard lock(mutex_);\n\n\t\tif (is_closed_) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (const int ret = read_buffer_->Read(channel_->GetSocket());\n\t\t    ret <= 0) {\n\t\t\tthis->Close();\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (read_cb_) {\n\t\tif (const bool ret =\n\t\t\t    read_cb_(weak_from_this(), *read_buffer_);\n\t\t    !ret) {\n\t\t\tstd::lock_guard lock(mutex_);\n\t\t\tthis->Close();\n\t\t}\n\t}\n}\n\nvoid TcpConnection::HandleWrite()\n{\n\tif (is_closed_) {\n\t\treturn;\n\t}\n\n\t//std::lock_guard<std::mutex> lock(mutex_);\n\tif (!mutex_.try_lock()) {\n\t\treturn;\n\t}\n\n\tbool empty;\n\t//do\n\t//{\n\tif (const int ret = write_buffer_->Send(channel_->GetSocket());\n\t    ret < 0) {\n\t\tthis->Close();\n\t\tmutex_.unlock();\n\t\treturn;\n\t}\n\tempty = write_buffer_->IsEmpty();\n\t//} while (false);\n\n\tif (empty) {\n\t\tif (channel_->IsWriting()) {\n\t\t\tchannel_->DisableWriting();\n\t\t\ttask_scheduler_->UpdateChannel(channel_);\n\t\t}\n\t} else if (!channel_->IsWriting()) {\n\t\tchannel_->EnableWriting();\n\t\ttask_scheduler_->UpdateChannel(channel_);\n\t}\n\n\tmutex_.unlock();\n}\n\nvoid TcpConnection::Close()\n{\n\tif (!is_closed_) {\n\t\tis_closed_ = true;\n\t\ttask_scheduler_->RemoveChannel(channel_);\n\n\t\tif (close_cb_) {\n\t\t\tclose_cb_(weak_from_this());\n\t\t}\n\n\t\tif (disconnect_cb_) {\n\t\t\tdisconnect_cb_(weak_from_this());\n\t\t}\n\t}\n}\n\nvoid TcpConnection::HandleClose()\n{\n\tstd::lock_guard lock(mutex_);\n\tthis->Close();\n}\n\nvoid TcpConnection::HandleError()\n{\n\tstd::lock_guard lock(mutex_);\n\tthis->Close();\n}\n"
  },
  {
    "path": "rtsp-server/net/TcpConnection.h",
    "content": "//Scott Xu\n//2020-12-6 Add IPv6 support.\n#ifndef XOP_TCP_CONNECTION_H\n#define XOP_TCP_CONNECTION_H\n\n#include <atomic>\n#include <mutex>\n#include \"TaskScheduler.h\"\n#include \"BufferReader.h\"\n#include \"BufferWriter.h\"\n#include \"Channel.h\"\n#include \"SocketUtil.h\"\n\nnamespace xop {\n\nclass TcpConnection : public std::enable_shared_from_this<TcpConnection> {\npublic:\n\tusing Ptr = std::shared_ptr<TcpConnection>;\n\tusing Weak = std::weak_ptr<TcpConnection>;\n\tusing DisconnectCallback = std::function<void(Weak conn)>;\n\tusing CloseCallback = std::function<void(Weak conn)>;\n\tusing ReadCallback =\n\t\tstd::function<bool(Weak conn, BufferReader &buffer)>;\n\n\tTcpConnection(SOCKET sockfd,\n\t\t      std::shared_ptr<TaskScheduler> task_scheduler);\n\tvirtual ~TcpConnection();\n\n\tstd::shared_ptr<TaskScheduler> GetTaskScheduler() const\n\t{\n\t\treturn task_scheduler_;\n\t}\n\n\tvoid SetReadCallback(const ReadCallback &cb) { read_cb_ = cb; }\n\n\tvoid SetCloseCallback(const CloseCallback &cb) { close_cb_ = cb; }\n\n\tvoid Send(const std::shared_ptr<char> &data, size_t size);\n\tvoid Send(const char *data, size_t size);\n\n\tvoid Disconnect();\n\n\tbool IsClosed() const { return is_closed_; }\n\n\tbool IsIpv6() const { return ipv6_; }\n\n\tSOCKET GetSocket() const { return channel_->GetSocket(); }\n\n\tuint16_t GetPort() const\n\t{\n\t\treturn SocketUtil::GetPeerPort(channel_->GetSocket(), ipv6_);\n\t}\n\n\tstd::string GetIp() const\n\t{\n\t\treturn SocketUtil::GetPeerIp(channel_->GetSocket(), ipv6_);\n\t}\n\nprotected:\n\tfriend class TcpServer;\n\n\tvirtual void HandleRead();\n\tvirtual void HandleWrite();\n\tvirtual void HandleClose();\n\tvirtual void HandleError();\n\n\tvoid SetDisconnectCallback(const DisconnectCallback &cb)\n\t{\n\t\tdisconnect_cb_ = cb;\n\t}\n\n\tstd::unique_ptr<BufferReader> read_buffer_;\n\tstd::unique_ptr<BufferWriter> write_buffer_;\n\tstd::atomic_bool is_closed_;\n\nprivate:\n\tvoid Close();\n\n\tstd::shared_ptr<TaskScheduler> task_scheduler_;\n\tstd::shared_ptr<Channel> channel_;\n\tstd::mutex mutex_;\n\tDisconnectCallback disconnect_cb_;\n\tCloseCallback close_cb_;\n\tReadCallback read_cb_;\n\tbool ipv6_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/TcpServer.cpp",
    "content": "// Scott Xu\n// 2020-12-04 Add multiple socket support.\n#include \"TcpServer.h\"\n#include \"Acceptor.h\"\n#include \"EventLoop.h\"\n#include <utility>\n\nusing namespace xop;\nusing namespace std;\n\nTcpServer::TcpServer(EventLoop *event_loop) : event_loop_(event_loop)\n//, port_(0)\n//, acceptor_(new Acceptor(event_loop_))\n//, is_started_(false)\n{\n}\n\nTcpServer::~TcpServer()\n{\n\tTcpServer::Stop();\n}\n\nbool TcpServer::Start(const std::string &ip, const uint16_t port)\n{\n\t/*Stop();\n\n\tif (!is_started_) {\n\t\tif (acceptor_->Listen(ip, port) < 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tport_ = port;\n\t\tip_ = ip;\n\t\tis_started_ = true;\n\t\treturn true;\n\t}*/\n\t//return false;\n\n\tauto acceptor = std::make_unique<Acceptor>(event_loop_);\n\tacceptor->SetNewConnectionCallback([this](SOCKET sockfd) {\n\t\tif (const auto conn = this->OnConnect(sockfd)) {\n\t\t\tthis->AddConnection(sockfd, conn);\n\t\t\tconn->SetDisconnectCallback([this](const TcpConnection::\n\t\t\t\t\t\t\t\t   Weak &conn) {\n\t\t\t\tconst auto scheduler =\n\t\t\t\t\tconn.lock()->GetTaskScheduler();\n\t\t\t\tif (SOCKET sockfd = conn.lock()->GetSocket();\n\t\t\t\t    !scheduler->AddTriggerEvent([this, sockfd] {\n\t\t\t\t\t    this->RemoveConnection(sockfd);\n\t\t\t\t    })) {\n\t\t\t\t\tscheduler->AddTimer(\n\t\t\t\t\t\t[this, sockfd]() {\n\t\t\t\t\t\t\tthis->RemoveConnection(\n\t\t\t\t\t\t\t\tsockfd);\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t},\n\t\t\t\t\t\t100);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t});\n\tif (acceptor->Listen(ip, port) < 0)\n\t\treturn false;\n\tacceptors_.push_back(std::move(acceptor));\n\treturn true;\n}\n\nvoid TcpServer::Stop()\n{\n\t/*if (is_started_) {\n\t\t\n\t\tmutex_.lock();\n\t\tfor (auto iter : connections_) {\n\t\t\titer.second->Disconnect();\n\t\t}\n\t\tmutex_.unlock();\n\n\t\tacceptor_->Close();\n\t\tis_started_ = false;\n\n\t\twhile (1) {\n\t\t\tTimer::Sleep(1);\n\t\t\tif (connections_.empty()) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}*/\n\tif (acceptors_.empty())\n\t\treturn;\n\n\tmutex_.lock();\n\tfor (const auto iter : connections_)\n\t\titer.second->Disconnect();\n\tmutex_.unlock();\n\n\tfor (auto it = acceptors_.begin(); it != acceptors_.end(); ++it)\n\t\t(*it)->Close();\n\n\twhile (!connections_.empty())\n\t\tTimer::Sleep(10);\n\n\tacceptors_.clear();\n\n\treturn;\n}\n\nTcpConnection::Ptr TcpServer::OnConnect(const SOCKET sockfd)\n{\n\treturn std::make_shared<TcpConnection>(sockfd,\n\t\t\t\t\t       event_loop_->GetTaskScheduler());\n}\n\nvoid TcpServer::AddConnection(const SOCKET sockfd, TcpConnection::Ptr tcpConn)\n{\n\tstd::lock_guard locker(mutex_);\n\tconnections_.emplace(sockfd, tcpConn);\n}\n\nvoid TcpServer::RemoveConnection(const SOCKET sockfd)\n{\n\tstd::lock_guard locker(mutex_);\n\tconnections_.erase(sockfd);\n}\n"
  },
  {
    "path": "rtsp-server/net/TcpServer.h",
    "content": "// PHZ\n// 2018-11-10\n// Scott Xu\n// 2020-12-04 Add multiple socket support.\n\n#ifndef XOP_TCPSERVER_H\n#define XOP_TCPSERVER_H\n\n#include <memory>\n#include <string>\n#include <mutex>\n#include <unordered_map>\n#include \"EventLoop.h\"\n#include \"TcpConnection.h\"\n\nnamespace xop {\n\nclass Acceptor;\nclass EventLoop;\n\nclass TcpServer {\npublic:\n\texplicit TcpServer(EventLoop *event_loop);\n\tvirtual ~TcpServer();\n\n\tvirtual bool Start(const std::string &ip, uint16_t port);\n\tvirtual void Stop();\n\n\t/*std::string GetIPAddress() const\n\t{ return ip_; }\n\n\tuint16_t GetPort() const \n\t{ return port_; }*/\n\nprotected:\n\tvirtual TcpConnection::Ptr OnConnect(SOCKET sockfd);\n\tvirtual void AddConnection(SOCKET sockfd, TcpConnection::Ptr tcpConn);\n\tvirtual void RemoveConnection(SOCKET sockfd);\n\n\tEventLoop *event_loop_;\n\t//uint16_t port_;\n\t//std::string ip_;\n\tstd::vector<std::unique_ptr<Acceptor>> acceptors_;\n\t//bool is_started_;\n\tstd::mutex mutex_;\n\tstd::unordered_map<SOCKET, std::shared_ptr<TcpConnection>> connections_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/TcpSocket.cpp",
    "content": "// PHZ\n// 2018-5-15\n// Scott Xu\n// 2020-12-2 Add IPv6 Support.\n\n#include \"TcpSocket.h\"\n#include \"Socket.h\"\n#include \"SocketUtil.h\"\n#include \"Logger.h\"\n\nusing namespace xop;\n\nTcpSocket::TcpSocket(const SOCKET sockfd, const bool ipv6)\n\t: sockfd_(sockfd), ipv6_(ipv6)\n{\n}\n\nTcpSocket::~TcpSocket() = default;\n\nSOCKET TcpSocket::Create(const bool ipv6)\n{\n\tipv6_ = ipv6;\n\tsockfd_ = ::socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_STREAM, 0); //TODO\n\treturn sockfd_;\n}\n\nbool TcpSocket::Bind(const std::string &ip, const uint16_t port) const\n{\n\tif (!SocketUtil::Bind(sockfd_, ip, port, ipv6_)) {\n\t\tLOG_ERROR(\" <socket=%d> bind <%s:%u> failed.\\n\", sockfd_,\n\t\t\t  ip.c_str(), port);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool TcpSocket::Listen(const int backlog) const\n{\n\tif (::listen(sockfd_, backlog) == SOCKET_ERROR) {\n\t\tLOG_ERROR(\"<socket=%d> listen failed.\\n\", sockfd_);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nSOCKET TcpSocket::Accept() const\n{\n\tsockaddr *psockaddr;\n\tsocklen_t addrlen;\n\tif (ipv6_) {\n\t\tsockaddr_in6 addr = {0};\n\t\taddrlen = sizeof addr;\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t} else {\n\t\tsockaddr_in addr = {0};\n\t\taddrlen = sizeof addr;\n\t\tpsockaddr = reinterpret_cast<sockaddr *>(&addr);\n\t}\n\n\tconst SOCKET socket_fd = accept(sockfd_, psockaddr, &addrlen);\n\n\treturn socket_fd;\n}\n\nbool TcpSocket::Connect(const std::string &ip, const uint16_t port,\n\t\t\tconst int timeout) const\n{\n\tif (!SocketUtil::Connect(sockfd_, ip, port, timeout, ipv6_)) {\n\t\tLOG_ERROR(\"<socket=%d> connect failed.\\n\", sockfd_);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid TcpSocket::Close()\n{\n#if defined(WIN32) || defined(_WIN32)\n\tclosesocket(sockfd_);\n#else\n\t::close(sockfd_);\n#endif\n\tsockfd_ = 0;\n}\n\nvoid TcpSocket::ShutdownWrite()\n{\n\tshutdown(sockfd_, SHUT_WR);\n\tsockfd_ = 0;\n}\n"
  },
  {
    "path": "rtsp-server/net/TcpSocket.h",
    "content": "// PHZ\n// 2018-5-15\n// Scott Xu\n// 2020-12-2 Add IPv6 Support.\n\n#ifndef XOP_TCP_SOCKET_H\n#define XOP_TCP_SOCKET_H\n\n#include <cstdint>\n#include <string>\n#include \"Socket.h\"\n\nnamespace xop {\n\nclass TcpSocket {\npublic:\n\tTcpSocket(SOCKET sockfd = -1, bool ipv6 = false);\n\tvirtual ~TcpSocket();\n\n\tSOCKET Create(bool ipv6 = false);\n\tbool Bind(const std::string &ip, uint16_t port) const;\n\tbool Listen(int backlog) const;\n\tSOCKET Accept() const;\n\tbool Connect(const std::string &ip, uint16_t port,\n\t\t     int timeout = 0) const;\n\tvoid Close();\n\tvoid ShutdownWrite();\n\n\tSOCKET GetSocket() const { return sockfd_; }\n\tbool IsIpv6Socket() const { return ipv6_; }\n\nprivate:\n\tSOCKET sockfd_ = 0;\n\tbool ipv6_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/ThreadSafeQueue.h.bak",
    "content": "#ifndef THREAD_SAFE_QUEUE_H\n#define THREAD_SAFE_QUEUE_H\n\n#include <mutex>\n#include <queue>\n#include <condition_variable>\n\nnamespace xop\n{\n    \ntemplate<typename T>\nclass ThreadSafeQueue\n{\npublic:\n    ThreadSafeQueue()\n    {\n        \n    }\n    \n    ThreadSafeQueue(ThreadSafeQueue const& other)\n    {\n        std::lock_guard<std::mutex> lock(other._mutex);\n        _dataQueue = other._dataQueue;\n    }\n\n    ~ThreadSafeQueue()\n    {\n       \n    }\n            \n    void push(T value)\n    {\n        std::lock_guard<std::mutex> lock(_mutex);\n        _dataQueue.push(value);\n        _dataCond.notify_one();\n    }\n        \n    bool waitAndPop(T& value)\n    {\n        std::unique_lock<std::mutex> lock(_mutex);\n        _dataCond.wait(lock);\n\t\tif (_dataQueue.empty())\n\t\t{\n\t\t\treturn false;\n\t\t}\n        value = _dataQueue.front();\n        _dataQueue.pop();\n\t\treturn true;\n    }\n\n    std::shared_ptr<T> waitAndPop()\n    {\n        std::unique_lock<std::mutex> lock(_mutex);\n        _dataCond.wait(lock);\n\t\tif (_dataQueue.empty())\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n        std::shared_ptr<T> res(std::make_shared<T>(_dataQueue.front()));\n        _dataQueue.pop();\n        return res;\n    }\n\n    bool tryPop(T& value)\n    {\n        std::lock_guard<std::mutex> lock(_mutex);\n        if(_dataQueue.empty())\n            return false;\n        \n        value = _dataQueue.front();\n        _dataQueue.pop();\n        \n        return true;\n    }\n\n    std::shared_ptr<T> tryPop()\n    {\n        std::lock_guard<std::mutex> lock(_mutex);\n        if(_dataQueue.empty())\n            return std::shared_ptr<T>();\n        std::shared_ptr<T> res(std::make_shared<T>(_dataQueue.front()));\n        _dataQueue.pop();\n        return res;\n    }\n\n    size_t size() const\n    {\n        std::lock_guard<std::mutex> lock(_mutex);\n        return _dataQueue.size();\n    }\n\n    bool empty() const\n    {\n        std::lock_guard<std::mutex> lock(_mutex);\n        return _dataQueue.empty();\n    }\n\n    void clear()\n    {\t\t\n        std::lock_guard<std::mutex> lock(_mutex);\n        std::queue<T> empty;\n        _dataQueue.swap(empty);\n    } \n    \n\tvoid wake()\n\t{\n\t\t_dataCond.notify_one();\n\t}\n\nprivate:\t\n    mutable std::mutex _mutex;\n    std::queue<T> _dataQueue;\n    std::condition_variable _dataCond;    \n};\n\n}\n\n#endif \n\n"
  },
  {
    "path": "rtsp-server/net/Timer.cpp",
    "content": "#include \"Timer.h\"\n\nusing namespace xop;\nusing namespace std;\nusing namespace std::chrono;\n\nTimerId TimerQueue::AddTimer(const TimerEvent &event, uint32_t ms)\n{\n\tstd::lock_guard locker(mutex_);\n\tconst int64_t timeout = GetTimeNow();\n\tTimerId timer_id = ++last_timer_id_;\n\n\tauto timer = make_shared<Timer>(event, ms);\n\ttimer->SetNextTimeout(timeout);\n\ttimers_.emplace(timer_id, timer);\n\tevents_.emplace(std::pair(timeout + ms, timer_id), std::move(timer));\n\treturn timer_id;\n}\n\nvoid TimerQueue::RemoveTimer(TimerId timerId)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (const auto iter = timers_.find(timerId); iter != timers_.end()) {\n\t\tint64_t timeout = iter->second->getNextTimeout();\n\t\tevents_.erase(std::pair(timeout, timerId));\n\t\ttimers_.erase(timerId);\n\t}\n}\n\nint64_t TimerQueue::GetTimeNow() const\n{\n\tconst auto time_point = steady_clock::now();\n\treturn duration_cast<milliseconds>(time_point.time_since_epoch())\n\t\t.count();\n}\n\nint64_t TimerQueue::GetTimeRemaining()\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (timers_.empty()) {\n\t\treturn -1;\n\t}\n\n\tint64_t msec = events_.begin()->first.first - GetTimeNow();\n\tif (msec < 0) {\n\t\tmsec = 0;\n\t}\n\n\treturn msec;\n}\n\nvoid TimerQueue::HandleTimerEvent()\n{\n\tif (!timers_.empty()) {\n\t\tstd::lock_guard locker(mutex_);\n\t\tconst int64_t timePoint = GetTimeNow();\n\t\twhile (!timers_.empty() &&\n\t\t       events_.begin()->first.first <= timePoint) {\n\t\t\tTimerId timerId = events_.begin()->first.second;\n\t\t\tif (const bool flag =\n\t\t\t\t    events_.begin()->second->event_callback_();\n\t\t\t    flag) {\n\t\t\t\tevents_.begin()->second->SetNextTimeout(\n\t\t\t\t\ttimePoint);\n\t\t\t\tauto timerPtr = events_.begin()->second;\n\t\t\t\tevents_.erase(events_.begin());\n\t\t\t\tevents_.emplace(\n\t\t\t\t\tstd::pair(timerPtr->getNextTimeout(),\n\t\t\t\t\t\t  timerId),\n\t\t\t\t\ttimerPtr);\n\t\t\t} else {\n\t\t\t\tevents_.erase(events_.begin());\n\t\t\t\ttimers_.erase(timerId);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "rtsp-server/net/Timer.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef _XOP_TIMER_H\n#define _XOP_TIMER_H\n\n#include <map>\n#include <unordered_map>\n#include <chrono>\n#include <functional>\n#include <cstdint>\n#include <chrono>\n#include <memory>\n#include <mutex>\n#include <thread>\n\nnamespace xop {\n\ntypedef std::function<bool()> TimerEvent;\ntypedef uint32_t TimerId;\n\nclass Timer {\npublic:\n\tTimer(TimerEvent event, const uint32_t msec)\n\t\t: event_callback_(std::move(event)), interval_(msec)\n\t{\n\t\tif (interval_ == 0) {\n\t\t\tinterval_ = 1;\n\t\t}\n\t}\n\n\tstatic void Sleep(const uint32_t msec)\n\t{\n\t\tstd::this_thread::sleep_for(std::chrono::milliseconds(msec));\n\t}\n\n\tvoid SetEventCallback(const TimerEvent &event)\n\t{\n\t\tevent_callback_ = event;\n\t}\n\n\tvoid Start(const int64_t microseconds, const bool repeat = false)\n\t{\n\t\tis_repeat_ = repeat;\n\t\tauto time_begin = std::chrono::high_resolution_clock::now();\n\t\tint64_t elapsed = 0;\n\n\t\tdo {\n\t\t\tstd::this_thread::sleep_for(std::chrono::microseconds(\n\t\t\t\tmicroseconds - elapsed));\n\t\t\ttime_begin = std::chrono::high_resolution_clock::now();\n\t\t\tif (event_callback_) {\n\t\t\t\tevent_callback_();\n\t\t\t}\n\t\t\telapsed =\n\t\t\t\tstd::chrono::duration_cast<\n\t\t\t\t\tstd::chrono::microseconds>(\n\t\t\t\t\tstd::chrono::high_resolution_clock::now() -\n\t\t\t\t\ttime_begin)\n\t\t\t\t\t.count();\n\t\t\tif (elapsed < 0) {\n\t\t\t\telapsed = 0;\n\t\t\t}\n\n\t\t} while (is_repeat_);\n\t}\n\n\tvoid Stop() { is_repeat_ = false; }\n\nprivate:\n\tfriend class TimerQueue;\n\n\tvoid SetNextTimeout(const int64_t time_point)\n\t{\n\t\tnext_timeout_ = time_point + interval_;\n\t}\n\n\tint64_t getNextTimeout() const { return next_timeout_; }\n\n\tbool is_repeat_ = false;\n\tTimerEvent event_callback_ = [] { return false; };\n\tuint32_t interval_ = 0;\n\tint64_t next_timeout_ = 0;\n};\n\nclass TimerQueue {\npublic:\n\tTimerId AddTimer(const TimerEvent &event, uint32_t msec);\n\tvoid RemoveTimer(TimerId timerId);\n\n\tint64_t GetTimeRemaining();\n\tvoid HandleTimerEvent();\n\nprivate:\n\tint64_t GetTimeNow() const;\n\n\tstd::mutex mutex_;\n\tstd::unordered_map<TimerId, std::shared_ptr<Timer>> timers_;\n\tstd::map<std::pair<int64_t, TimerId>, std::shared_ptr<Timer>> events_;\n\tuint32_t last_timer_id_ = 0;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/net/Timestamp.cpp",
    "content": "#include \"Timestamp.h\"\n#include <iomanip>\n#include <sstream>\n\nusing namespace xop;\nusing namespace std;\nusing namespace std::chrono;\n\nstd::string Timestamp::Localtime()\n{\n\tstd::ostringstream stream;\n\tconst auto now = system_clock::now();\n\tconst time_t tt = system_clock::to_time_t(now);\n\n#if defined(WIN32) || defined(_WIN32)\n\ttm tm{};\n\tlocaltime_s(&tm, &tt);\n\tstream << std::put_time(&tm, \"%F %T\");\n#else\n\tchar buffer[200] = {0};\n\tstd::string timeString;\n\tstd::strftime(buffer, 200, \"%F %T\", std::localtime(&tt));\n\tstream << buffer;\n#endif\n\treturn stream.str();\n}\n"
  },
  {
    "path": "rtsp-server/net/Timestamp.h",
    "content": "// PHZ\n// 2018-5-15\n\n#ifndef XOP_TIMESTAMP_H\n#define XOP_TIMESTAMP_H\n\n#include <chrono>\n#include <string>\n#include <functional>\n#include <cstdint>\n#include <chrono>\n\nnamespace xop {\n\nclass Timestamp {\npublic:\n\tTimestamp()\n\t\t: begin_time_point_(std::chrono::high_resolution_clock::now())\n\t{\n\t}\n\n\tvoid reset()\n\t{\n\t\tbegin_time_point_ = std::chrono::high_resolution_clock::now();\n\t}\n\n\tint64_t Elapsed() const\n\t{\n\t\treturn std::chrono::duration_cast<std::chrono::milliseconds>(\n\t\t\t       std::chrono::high_resolution_clock::now() -\n\t\t\t       begin_time_point_)\n\t\t\t.count();\n\t}\n\n\tstatic std::string Localtime();\n\nprivate:\n\tstd::chrono::time_point<std::chrono::high_resolution_clock>\n\t\tbegin_time_point_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/AACSource.cpp",
    "content": "// PHZ\n// 2018-5-16\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n#include \"AACSource.h\"\n//#include <stdlib.h>\n#include <cstdio>\n#include <cstring>\n#include <chrono>\n#include <array>\n#include <vector>\n//#include <map>\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <sys/time.h>\n#endif\n\nusing namespace xop;\nusing namespace std;\n\nAACSource::AACSource(const uint32_t samplerate, const uint8_t channels,\n\t\t     const bool has_adts)\n\t: samplerate_(samplerate), channels_(channels), has_adts_(has_adts)\n{\n\tpayload_ = 97;\n\tmedia_type_ = MediaType::AAC;\n\tclock_rate_ = samplerate;\n}\n\nAACSource *AACSource::CreateNew(const uint32_t samplerate,\n\t\t\t\tconst uint8_t channels, const bool has_adts)\n{\n\treturn new AACSource(samplerate, channels, has_adts);\n}\n\nAACSource::~AACSource() = default;\n\nstring AACSource::GetMediaDescription(const uint16_t port)\n{\n\tchar buf[100];\n\tsnprintf(buf, sizeof(buf), \"m=audio %hu RTP/AVP 97\", port); // \\r\\nb=AS:64\n\n\treturn buf;\n}\n\nstatic array<uint32_t, 16> samplingFrequencyTable = {\n\t96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,\n\t16000, 12000, 11025, 8000,  7350,  0,     0,     0 /*reserved */\n};\n\nstring AACSource::GetAttribute() // RFC 3640\n{\n\tuint8_t samplingFrequencyIndex = 0;\n\tfor (const auto samplingFrequency : samplingFrequencyTable) {\n\t\tif (samplingFrequency == samplerate_)\n\t\t\tbreak;\n\t\tsamplingFrequencyIndex++;\n\t}\n\tconstexpr uint8_t profile = 1;\n\n\tif (samplingFrequencyIndex == samplingFrequencyTable.size())\n\t\treturn \"\"; // error\n\n\tconst auto *rtpmap_fmt = \"a=rtpmap:97 MPEG4-GENERIC/%u/%u\\r\\n\";\n\tconst auto *fmtp_fmt = \"a=fmtp:97 profile-level-id=1;\"\n\t\t\t       \"mode=AAC-hbr;\"\n\t\t\t       \"sizelength=13;indexlength=3;indexdeltalength=3;\"\n\t\t\t       \"config=%02X%02X\";\n\tconst size_t buf_size =\n\t\tsnprintf(nullptr, 0, rtpmap_fmt, samplerate_, channels_) +\n\t\tstrlen(fmtp_fmt);\n\tauto buf = vector<char>(buf_size);\n\tconst size_t rtpmap_format_size =\n\t\tsnprintf(buf.data(), buf_size, rtpmap_fmt, samplerate_, channels_);\n\n\tconst array audioSpecificConfig = {\n\t\tstatic_cast<uint8_t>((profile + 1) << 3 |\n\t\t\t\t     samplingFrequencyIndex >> 1),\n\t\tstatic_cast<uint8_t>(samplingFrequencyIndex << 7 |\n\t\t\t\t     channels_ << 3)};\n\tsnprintf(buf.data() + rtpmap_format_size, buf_size - rtpmap_format_size, fmtp_fmt,\n\t\taudioSpecificConfig[0], audioSpecificConfig[1]);\n\n\treturn buf.data();\n}\n\nbool AACSource::HandleFrame(const MediaChannelId channel_id,\n\t\t\t    const AVFrame frame)\n{\n\tif (frame.size > (MAX_RTP_PAYLOAD_SIZE - AU_SIZE)) {\n\t\treturn false;\n\t}\n\n\tsize_t adts_size = 0;\n\tif (has_adts_) {\n\t\tadts_size = ADTS_SIZE;\n\t}\n\n\tconst uint8_t *frame_buf = frame.buffer.get() + adts_size;\n\tsize_t frame_size = frame.size - adts_size;\n\n\tconst char AU[AU_SIZE] = {0x00, 0x10,\n\t\t\t\t  static_cast<char>((frame_size & 0x1fe0) >> 5),\n\t\t\t\t  static_cast<char>((frame_size & 0x1f) << 3)};\n\n\tRtpPacket rtp_pkt;\n\trtp_pkt.type = FrameType::AUDIO_FRAME;\n\trtp_pkt.timestamp = frame.timestamp;\n\trtp_pkt.size = static_cast<uint16_t>(frame_size) + RTP_TCP_HEAD_SIZE +\n\t\t       RTP_HEADER_SIZE + AU_SIZE;\n\trtp_pkt.last = 1;\n\tuint8_t *rtp_pkt_data =\n\t\trtp_pkt.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;\n\n\t*(rtp_pkt_data++) = AU[0];\n\t*(rtp_pkt_data++) = AU[1];\n\t*(rtp_pkt_data++) = AU[2];\n\t*(rtp_pkt_data++) = AU[3];\n\n\tmemcpy(rtp_pkt_data, frame_buf, frame_size);\n\n\tif (send_frame_callback_) {\n\t\treturn send_frame_callback_(channel_id, rtp_pkt); //TODO\n\t}\n\n\treturn true;\n}\n\nuint32_t AACSource::GetTimestamp(const uint32_t sampleRate)\n{\n\t//auto time_point = chrono::time_point_cast<chrono::milliseconds>(chrono::high_resolution_clock::now());\n\t//return (uint32_t)(time_point.time_since_epoch().count() * sampleRate / 1000);\n\n\tconst auto time_point = chrono::time_point_cast<chrono::microseconds>(\n\t\tchrono::steady_clock::now());\n\treturn static_cast<uint32_t>(\n\t\t(time_point.time_since_epoch().count() + 500) / 1000 *\n\t\tsampleRate / 1000);\n}\n"
  },
  {
    "path": "rtsp-server/xop/AACSource.h",
    "content": "// PHZ\n// 2018-5-16\n\n#ifndef XOP_AAC_SOURCE_H\n#define XOP_AAC_SOURCE_H\n\n#include \"MediaSource.h\"\n#include \"rtp.h\"\n\nnamespace xop {\n\nclass AACSource : public MediaSource {\npublic:\n\tstatic AACSource *CreateNew(uint32_t samplerate = 44100,\n\t\t\t\t    uint8_t channels = 2, bool has_adts = true);\n\t~AACSource() override;\n\n\tuint32_t GetSamplerate() const { return samplerate_; }\n\n\tuint32_t GetChannels() const { return channels_; }\n\n\tstd::string GetMediaDescription(uint16_t port = 0) override;\n\n\tstd::string GetAttribute() override;\n\n\tbool HandleFrame(MediaChannelId channel_id, AVFrame frame) override;\n\n\tstatic uint32_t GetTimestamp(uint32_t samplerate = 44100);\n\nprivate:\n\tAACSource(uint32_t samplerate, uint8_t channels, bool has_adts);\n\n\tuint32_t samplerate_ = 44100;\n\tuint8_t channels_ = 2;\n\tbool has_adts_ = true;\n\n\tstatic constexpr size_t ADTS_SIZE = 7;\n\tstatic constexpr size_t AU_SIZE = 4;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/Base64Encode.cpp",
    "content": "\n#include \"Base64Encode.h\"\n\n#include <vector>\n\nextern \"C\" {\n#include \"b64/cencode.h\"\n}\n\nstd::string xop::Base64Encode(const void *input, const size_t size)\n{\n\tstd::vector<char> buffer(size / 3 * 4 + (size % 3 > 0 ? 4 : 0) + 1);\n\tbase64_encodestate b64encoder;\n\tbase64_init_encodestate(&b64encoder);\n\n\tconst auto length = base64_encode_block(\n\t\tstatic_cast<const char *>(input), static_cast<int>(size),\n\t\tbuffer.data(), &b64encoder);\n\tbase64_encode_blockend(buffer.data() + length, &b64encoder);\n\n\treturn std::string(buffer.cbegin(), buffer.cend() - 1); //TODO\n}\n"
  },
  {
    "path": "rtsp-server/xop/Base64Encode.h",
    "content": "#ifndef _XOP_BASE64ENCODE_H\n#define _XOP_BASE64ENCODE_H\n#include <string>\n\nnamespace xop {\nstd::string Base64Encode(const void *input, const size_t size);\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/BaseMd5.cpp",
    "content": "#include \"BaseMd5.h\"\n#include \"md5.hpp\"\n\nusing namespace xop;\n\nBaseMd5::BaseMd5() : Md5() {}\n\nBaseMd5::~BaseMd5() = default;\n\nvoid BaseMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize,\n\t\t\t unsigned char *outHash)\n{\n\tmd5::md5_state_t state;\n\n\tmd5_init(&state);\n\tmd5_append(&state, data, dataSize);\n\tmd5_finish(&state, outHash);\n}\n"
  },
  {
    "path": "rtsp-server/xop/BaseMd5.h",
    "content": "#ifndef _XOP_BASEMD5_H\n#define _XOP_BASEMD5_H\n\n#include \"Md5.h\"\n\nnamespace xop {\nclass BaseMd5 : public Md5 {\npublic:\n\tBaseMd5();\n\t~BaseMd5() override;\n\n\tvoid GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t\tunsigned char *outHash) override;\n};\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/CngMd5.cpp",
    "content": "#include \"CngMd5.h\"\n#include \"net/Logger.h\"\n\n#if defined(WIN32) || defined(_WIN32)\n#pragma comment(lib, \"Bcrypt.lib\")\n#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)\n#define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)\n#endif\n\nusing namespace xop;\n\nCngMd5::CngMd5() : Md5()\n{\n\n#if defined(WIN32) || defined(_WIN32)\n\tDWORD cbData = 0;\n\tNTSTATUS status;\n\tif (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(\n\t\t\t\t&hAlgorithm_, BCRYPT_MD5_ALGORITHM, nullptr, 0))) {\n\t\tLOG_ERROR(\n\t\t\t\"**** Error 0x%x returned by BCryptOpenAlgorithmProvider\",\n\t\t\tstatus);\n\t\treturn;\n\t}\n\tif (!NT_SUCCESS(status = BCryptGetProperty(\n\t\t\t\thAlgorithm_, BCRYPT_OBJECT_LENGTH,\n\t\t\t\t(PBYTE)&cbHashObject_, sizeof(DWORD), &cbData,\n\t\t\t\t0))) {\n\t\tLOG_ERROR(\"**** Error 0x%x returned by BCryptGetProperty\",\n\t\t\t  status);\n\t\tCngMd5::~CngMd5();\n\t\treturn;\n\t}\n\tif (!NT_SUCCESS(status = BCryptGetProperty(\n\t\t\t\thAlgorithm_, BCRYPT_HASH_LENGTH,\n\t\t\t\t(PBYTE)&cbHash_, sizeof(DWORD), &cbData, 0))) {\n\t\tLOG_ERROR(\"**** Error 0x%x returned by BCryptGetProperty\",\n\t\t\t  status);\n\t\tCngMd5::~CngMd5();\n\t\treturn;\n\t}\n\tif (cbHash_ > MD5_HASH_LENGTH) {\n\t\tLOG_ERROR(\"**** The generated hash value is too long\");\n\t\tCngMd5::~CngMd5();\n\t}\n#endif\n}\n\nCngMd5::~CngMd5()\n{\n#if defined(WIN32) || defined(_WIN32)\n\tif (hAlgorithm_) {\n\t\tBCryptCloseAlgorithmProvider(hAlgorithm_, 0);\n\t\thAlgorithm_ = nullptr;\n\t}\n#endif\n}\n\nvoid CngMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize,\n\t\t\tunsigned char *outHash)\n{\n#if defined(WIN32) || defined(_WIN32)\n\tif (hAlgorithm_ == nullptr) return;\n\n\tconst auto pbHashObject = static_cast<PBYTE>(\n\t\tHeapAlloc(GetProcessHeap(), 0, cbHashObject_));\n\t//create a hash\n\tBCRYPT_HASH_HANDLE hHash = nullptr;\n\n\tauto cleanup = [&] () {\n\t\tif (hHash)\n\t\t\tBCryptDestroyHash(hHash);\n\t\tif (pbHashObject)\n\t\t\tHeapFree(GetProcessHeap(), 0, pbHashObject);\n\t};\n\n\tif (nullptr == pbHashObject) {\n\t\tLOG_ERROR(\"**** memory allocation failed\");\n\t\tcleanup();\n\t\treturn;\n\t}\n\tNTSTATUS status;\n\tif (!NT_SUCCESS(status = BCryptCreateHash(hAlgorithm_, &hHash,\n\t\t\t\t\t\t  pbHashObject, cbHashObject_,\n\t\t\t\t\t\t  nullptr, 0, 0))) {\n\t\tLOG_ERROR(\"**** Error 0x%x returned by BCryptCreateHash\",\n\t\t\t  status);\n\t\tcleanup();\n\t\treturn;\n\t}\n\n\tif (!NT_SUCCESS(status = BCryptHashData(hHash, const_cast<PBYTE>(data),\n\t\t\t\t\t\tstatic_cast<ULONG>(dataSize), 0))) {\n\t\tLOG_ERROR(\"**** Error 0x%x returned by BCryptHashData\", status);\n\t\tcleanup();\n\t\treturn;\n\t}\n\n\t//close the hash\n\tif (!NT_SUCCESS(status =\n\t\t\t\tBCryptFinishHash(hHash, outHash, cbHash_, 0))) {\n\t\tLOG_ERROR(\"**** Error 0x%x returned by BCryptFinishHash\",\n\t\t\t  status);\n\t}\n\tcleanup();\n#endif\n}\n"
  },
  {
    "path": "rtsp-server/xop/CngMd5.h",
    "content": "#ifndef _XOP_CNGMD5_H\n#define _XOP_CNGMD5_H\n\n#include \"Md5.h\"\n\n#if defined(WIN32) || defined(_WIN32)\n#include <Windows.h>\n#include <bcrypt.h>\n#endif\n\nnamespace xop {\nclass CngMd5 : public Md5 {\npublic:\n\tCngMd5();\n\t~CngMd5() override;\n\n\tvoid GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t\tunsigned char *outHash) override;\n\nprivate:\n#if defined(WIN32) || defined(_WIN32)\n\tBCRYPT_ALG_HANDLE hAlgorithm_ = nullptr;\n\tDWORD cbHash_ = 0, cbHashObject_ = 0;\n#endif\n};\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/DigestAuthentication.cpp",
    "content": "#include \"DigestAuthentication.h\"\n\n#include <chrono>\n#include <string>\n#include <random>\n#if defined(WIN32) || defined(_WIN32)\n#include \"CngMd5.h\"\n#elif defined(__linux) || defined(__linux__)\n#include \"BaseMd5.h\"\n#elif defined(__APPLE__) || defined(__MACH__)\n#include \"MacMd5.h\"\n#else\n#include \"BaseMd5.h\"\n#endif\n\nusing namespace xop;\n\nDigestAuthentication::DigestAuthentication(std::string realm,\n\t\t\t\t\t   std::string username,\n\t\t\t\t\t   std::string password)\n\t: realm_(std::move(realm)),\n\t  username_(std::move(username)),\n\t  password_(std::move(password))\n{\n#if defined(WIN32) || defined(_WIN32)\n\tmd5_ = new CngMd5();\n#elif defined(__linux) || defined(__linux__)\n\tmd5_ = new BaseMd5();\n#elif defined(__APPLE__) || defined(__MACH__)\n\tmd5_ = new MacMd5();\n#else\n\tmd5_ = new BaseMd5();\n#endif\n}\n\nDigestAuthentication::~DigestAuthentication()\n{\n\tdelete md5_;\n}\n\nstd::string DigestAuthentication::GetNonce() const\n{\n\tstd::random_device rd;\n\n\tconst auto timePoint =\n\t\tstd::chrono::time_point_cast<std::chrono::milliseconds>(\n\t\t\tstd::chrono::steady_clock::now());\n\tconst uint32_t timestamp =\n\t\tstatic_cast<uint32_t>(timePoint.time_since_epoch().count());\n\n\treturn md5_->GetMd5HashString(std::to_string(timestamp + rd()));\n}\n\nstd::string DigestAuthentication::GetResponse(const std::string &nonce,\n\t\t\t\t\t      const std::string &cmd,\n\t\t\t\t\t      const std::string &url) const\n{\n\t//md5(md5(<username>:<realm> : <password>) :<nonce> : md5(<cmd>:<url>))\n\tconst auto hex1 = md5_->GetMd5HashString(username_ + \":\" + realm_ +\n\t\t\t\t\t\t \":\" + password_);\n\tconst auto hex2 = md5_->GetMd5HashString(cmd + \":\" + url);\n\tauto response = md5_->GetMd5HashString(hex1 + \":\" + nonce + \":\" + hex2);\n\treturn response;\n}\n"
  },
  {
    "path": "rtsp-server/xop/DigestAuthentication.h",
    "content": "//PHZ\n//2019-10-6\n\n#ifndef RTSP_DIGEST_AUTHENTICATION_H\n#define RTSP_DIGEST_AUTHENTICATION_H\n\n#include <string>\n#include \"Md5.h\"\n\nnamespace xop {\n\nclass DigestAuthentication {\npublic:\n\tDigestAuthentication(std::string realm, std::string\n\t                     username,\n\t                     std::string password);\n\tvirtual ~DigestAuthentication();\n\n\tstd::string GetRealm() const { return realm_; }\n\n\tstd::string GetUsername() const { return username_; }\n\n\tstd::string GetPassword() const { return password_; }\n\n\tstd::string GetNonce() const;\n\tstd::string GetResponse(const std::string &nonce, const std::string &cmd,\n\t                        const std::string &url) const;\n\nprivate:\n\tstd::string realm_;\n\tstd::string username_;\n\tstd::string password_;\n\tMd5 *md5_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/G711ASource.cpp",
    "content": "// PHZ\n// 2018-5-16\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n#include \"G711ASource.h\"\n#include <cstdio>\n#include <cstring>\n#include <chrono>\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <sys/time.h>\n#endif\n\nusing namespace xop;\nusing namespace std;\n\nG711ASource::G711ASource()\n{\n\tpayload_ = 8;\n\tmedia_type_ = MediaType::PCMA;\n\tclock_rate_ = 8000;\n}\n\nG711ASource *G711ASource::CreateNew()\n{\n\treturn new G711ASource();\n}\n\nG711ASource::~G711ASource() = default;\n\nstring G711ASource::GetMediaDescription(const uint16_t port)\n{\n\tchar buf[100];\n\tsnprintf(buf, sizeof(buf), \"m=audio %hu RTP/AVP 8\", port);\n\n\treturn buf;\n}\n\nstring G711ASource::GetAttribute()\n{\n\treturn \"a=rtpmap:8 PCMA/8000/1\";\n}\n\nbool G711ASource::HandleFrame(const MediaChannelId channel_id,\n\t\t\t      const AVFrame frame)\n{\n\tif (frame.size > MAX_RTP_PAYLOAD_SIZE) {\n\t\treturn false;\n\t}\n\n\tconst uint8_t *frame_buf = frame.buffer.get();\n\tconst size_t frame_size = frame.size;\n\n\tRtpPacket rtp_pkt;\n\trtp_pkt.type = FrameType::AUDIO_FRAME;\n\trtp_pkt.timestamp = frame.timestamp;\n\trtp_pkt.size = static_cast<uint16_t>(frame_size) + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;\n\trtp_pkt.last = 1;\n\n\tmemcpy(rtp_pkt.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE,\n\t       frame_buf, frame_size);\n\n\tif (send_frame_callback_) {\n\t\treturn send_frame_callback_(channel_id, rtp_pkt); //TODO\n\t}\n\n\treturn true;\n}\n\nuint32_t G711ASource::GetTimestamp()\n{\n\tconst auto time_point = chrono::time_point_cast<chrono::microseconds>(\n\t\tchrono::steady_clock::now());\n\treturn static_cast<uint32_t>(\n\t\t(time_point.time_since_epoch().count() + 500) / 1000 * 8);\n}\n"
  },
  {
    "path": "rtsp-server/xop/G711ASource.h",
    "content": "// PHZ\n// 2018-5-16\n\n#ifndef XOP_G711A_SOURCE_H\n#define XOP_G711A_SOURCE_H\n\n#include \"MediaSource.h\"\n#include \"rtp.h\"\n\nnamespace xop {\n\nclass G711ASource : public MediaSource {\npublic:\n\tstatic G711ASource *CreateNew();\n\t~G711ASource() override;\n\n\tuint32_t GetSampleRate() const { return samplerate_; }\n\n\tuint32_t GetChannels() const { return channels_; }\n\n\tstd::string GetMediaDescription(uint16_t port = 0) override;\n\n\tstd::string GetAttribute() override;\n\n\tbool HandleFrame(MediaChannelId channel_id, AVFrame frame) override;\n\n\tstatic uint32_t GetTimestamp();\n\nprivate:\n\tG711ASource();\n\n\tuint32_t samplerate_ = 8000;\n\tuint32_t channels_ = 1;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/H264NalUnit.cpp",
    "content": "#include \"H264NalUnit.h\"\n\nusing namespace xop;\n\nH264NalUnit::H264NalUnit(const uint8_t *data, size_t dataSize)\n\t: NalUnit(data, dataSize)\n{\n}\n\nuint8_t H264NalUnit::GetType()\n{\n\tuint8_t *data;\n\tif (GetHeader(&data) == H264_NALU_HEADER_SIZE) {\n\t\treturn data[0] & 0x1f;\n\t}\n\treturn 0;\n}\n\nuint8_t H264NalUnit::GetRefIdc()\n{\n\tuint8_t *data;\n\tif (GetHeader(&data) == H264_NALU_HEADER_SIZE) {\n\t\treturn (data[0] & 0x60) >> 5;\n\t}\n\treturn 0;\n}\n\nH264NalType H264NalUnit::GetH264Type()\n{\n\treturn static_cast<H264NalType>(GetType());\n}\n\nsize_t H264NalUnit::GetHeader(uint8_t **data)\n{\n\tif (GetData(data) >= H264_NALU_HEADER_SIZE)\n\t\treturn H264_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nsize_t H264NalUnit::CopyHeader(uint8_t *start, size_t size)\n{\n\tif (size > H264_NALU_HEADER_SIZE) {\n\t\tsize = H264_NALU_HEADER_SIZE;\n\t}\n\treturn CopyData(start, size);\n}\n\nsize_t H264NalUnit::GetHeaderSize()\n{\n\tconst auto size = GetSize();\n\tif (size > H264_NALU_HEADER_SIZE)\n\t\treturn H264_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nsize_t H264NalUnit::GetBody(uint8_t **data)\n{\n\tconst auto size = GetData(data);\n\tif (size > H264_NALU_HEADER_SIZE) {\n\t\t*data += H264_NALU_HEADER_SIZE;\n\t\treturn size - H264_NALU_HEADER_SIZE;\n\t}\n\treturn 0;\n}\n\nsize_t H264NalUnit::CopyBody(uint8_t *start, size_t size, size_t skip)\n{\n\tskip += H264_NALU_HEADER_SIZE;\n\treturn CopyData(start, size, skip);\n}\n\nsize_t H264NalUnit::GetBodySize()\n{\n\tconst auto size = GetSize();\n\tif (size > H264_NALU_HEADER_SIZE)\n\t\treturn size - H264_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nbool H264NalUnit::IsIdrFrame()\n{\n\tconst auto type = GetH264Type();\n\treturn type == H264NalType::H264_NAL_SLICE_IDR;\n}\n\nbool H264NalUnit::IsFrame()\n{\n\tconst auto type = GetH264Type();\n\treturn type >= H264NalType::H264_NAL_SLICE && type <= H264NalType::H264_NAL_SLICE_IDR;\n}\n\nNalUnit * H264NalUnit::GetNalUnit(const uint8_t *data, size_t dataSize)\n{\n\treturn new H264NalUnit(data, dataSize);\n}\n"
  },
  {
    "path": "rtsp-server/xop/H264NalUnit.h",
    "content": "#ifndef XOP_H264_NALUNIT_H\n#define XOP_H264_NALUNIT_H\n\n#include <cstddef>\n#include \"NalUnit.h\"\n\n#define H264_NALU_HEADER_SIZE 1\n\nnamespace xop {\n\nenum class H264NalType: uint8_t {\n\tH264_NAL_UNKNOWN = 0,\n\tH264_NAL_SLICE = 1,\n\tH264_NAL_SLICE_DPA = 2,\n\tH264_NAL_SLICE_DPB = 3,\n\tH264_NAL_SLICE_DPC = 4,\n\tH264_NAL_SLICE_IDR = 5,\n\tH264_NAL_SEI = 6,\n\tH264_NAL_SPS = 7,\n\tH264_NAL_PPS = 8,\n\tH264_NAL_AUD = 9,\n\tH264_NAL_EOSEQ = 10,\n\tH264_NAL_EOSTREAM = 11,\n\tH264_NAL_FILLER = 12,\n\tH264_NAL_RSV13 = 13,\n\tH264_NAL_RSV14 = 14,\n\tH264_NAL_RSV15 = 15,\n\tH264_NAL_RSV16 = 16,\n\tH264_NAL_RSV17 = 17,\n\tH264_NAL_RSV18 = 18,\n\tH264_NAL_RSV19 = 19,\n\tH264_NAL_RSV20 = 20,\n\tH264_NAL_RSV21 = 21,\n\tH264_NAL_RSV22 = 22,\n\tH264_NAL_RSV23 = 23,\n\tH264_NAL_UNSPEC24 = 24,\n\tH264_NAL_UNSPEC25 = 25,\n\tH264_NAL_UNSPEC26 = 26,\n\tH264_NAL_UNSPEC27 = 27,\n\tH264_NAL_UNSPEC28 = 28,\n\tH264_NAL_UNSPEC29 = 29,\n\tH264_NAL_UNSPEC30 = 30,\n\tH264_NAL_UNSPEC31 = 31\n};\n\nclass H264NalUnit : public NalUnit {\npublic:\n\tuint8_t GetType() override;\n\tuint8_t GetRefIdc();\n\tH264NalType GetH264Type();\n\tsize_t GetHeader(uint8_t **data) override;\n\tsize_t CopyHeader(uint8_t *start, size_t size) override;\n\tsize_t GetHeaderSize() override;\n\tsize_t GetBody(uint8_t **data) override;\n\tsize_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) override;\n\tsize_t GetBodySize() override;\n\tbool IsIdrFrame() override;\n\tbool IsFrame() override;\n\tstatic NalUnit *GetNalUnit(const uint8_t *data, size_t dataSize);\n\nprivate:\n\tH264NalUnit(const uint8_t *data, size_t dataSize);\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/H264Source.cpp",
    "content": "// PHZ\n// 2018-5-16\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n\n#include \"H264Source.h\"\n#include <memory>\n#include <utility>\n#include <vector>\n#include <cstdio>\n#include <cstring>\n#include <chrono>\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <sys/time.h>\n#endif\n\n#include \"Base64Encode.h\"\n#include \"Nal.h\"\n#include \"H264NalUnit.h\"\n\nusing namespace xop;\nusing namespace std;\n\nH264Source::H264Source(vector<uint8_t> sps, vector<uint8_t> pps,\n\t\t       const uint32_t framerate)\n\t: framerate_(framerate), sps_(std::move(sps)), pps_(std::move(pps))\n{\n\tpayload_ = 96;\n\tmedia_type_ = MediaType::H264;\n\tclock_rate_ = 90000;\n}\n\nH264Source *H264Source::CreateNew(vector<uint8_t> extraData,\n\t\t\t\t  const uint32_t framerate)\n{\n\tNal<H264NalUnit> nal(extraData);\n\tvector<uint8_t> sps, pps;\n\tconst auto sps_nal_unit = nal.GetNalUnitByType(\n\t\t\t   static_cast<uint8_t>(H264NalType::H264_NAL_SPS)),\n\t\t   pps_nal_unit = nal.GetNalUnitByType(\n\t\t\t   static_cast<uint8_t>(H264NalType::H264_NAL_PPS));\n\tif (sps_nal_unit != nullptr)\n\t\tsps = sps_nal_unit->GetData();\n\tif (pps_nal_unit != nullptr)\n\t\tpps = pps_nal_unit->GetData();\n\n\treturn new H264Source(sps, pps, framerate);\n}\n\nH264Source *H264Source::CreateNew(vector<uint8_t> sps, vector<uint8_t> pps,\n\t\t\t\t  const uint32_t framerate)\n{\n\treturn new H264Source(std::move(sps), std::move(pps), framerate);\n}\n\nH264Source::~H264Source() = default;\n\nstring H264Source::GetMediaDescription(const uint16_t port)\n{\n\tchar buf[100];\n\tsnprintf(buf, sizeof(buf), \"m=video %hu RTP/AVP 96\", port); // \\r\\nb=AS:2000\n\treturn buf;\n}\n\nstring H264Source::GetAttribute()\n{\n\tauto sdp = string(\"a=rtpmap:96 H264/90000\\r\\n\");\n\n\tif (!sps_.empty() && !pps_.empty()) {\n\t\tconst auto fmtp = \"a=fmtp:96 packetization-mode=1;\"\n\t\t\t\t  \"profile-level-id=%06X;\"\n\t\t\t\t  \"sprop-parameter-sets=%s,%s\";\n\n\t\tconst auto pps_base64 = Base64Encode(pps_.data(), pps_.size());\n\t\tconst auto sps_base64 = Base64Encode(sps_.data(), sps_.size());\n\n\t\tconst uint32_t profile_level_id =\n\t\t\t(sps_.at(1) << 16) | (sps_.at(2) << 8) | sps_.at(3);\n\n\t\tconst size_t buf_size = 1 + strlen(fmtp) + 6 +\n\t\t\t\t\tsps_base64.length() +\n\t\t\t\t\tpps_base64.length();\n\t\tauto buf = vector<char>(buf_size);\n\n\t\tsnprintf(buf.data(), buf_size, fmtp, profile_level_id, sps_base64.c_str(),\n\t\t\tpps_base64.c_str());\n\n\t\tsdp.append(buf.data());\n\t}\n\n\treturn sdp;\n}\n\nbool H264Source::HandleFrame(const MediaChannelId channelId,\n\t\t\t     const AVFrame frame)\n{\n\tRtpPacket rtp_packet;\n\t//rtpPacket.timestamp = frame.timestamp == 0 ? GetTimestamp() : frame.timestamp;\n\trtp_packet.timestamp = frame.timestamp;\n\tconst auto rtp_packet_data =\n\t\trtp_packet.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;\n\n\tNal<H264NalUnit> nal(frame.buffer.get(), frame.size);\n\n\tif (nal.GetCount() == 0)\n\t\treturn false;\n\n\tsize_t nal_index = 0;\n\twhile (nal_index < nal.GetCount()) {\n\t\tsize_t end_index = nal_index;\n\t\tsize_t size_count = H264_NALU_HEADER_SIZE;\n\t\twhile (size_count < MAX_RTP_PAYLOAD_SIZE &&\n\t\t       end_index < nal.GetCount()) {\n\t\t\tsize_count += nal[end_index++]->GetSize() + 2;\n\t\t}\n\t\tend_index--;\n\t\tif (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index)\n\t\t\tsize_count -= nal[end_index--]->GetSize() + 2;\n\t\tif (end_index > nal_index) {\n\t\t//Single-Time Aggregation Packet (STAP-A)\n\t\t/*  0                   1                   2                   3\n                 *  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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |                          RTP Header                           |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |STAP-A NAL HDR |         NALU 1 Size           |   NALU 1 HDR  |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |                         NALU 1 Data                           |\n                 * :                                                               :\n                 * |               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |               |         NALU 2 Size           |   NALU 2 HDR  |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |                         NALU 2 Data                           |\n                 * :                                                               :\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 */\n\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t\t\t  static_cast<uint16_t>(size_count);\n\t\t\trtp_packet.last = 1;\n\t\t\tsize_t skip = H264_NALU_HEADER_SIZE;\n\t\t\tuint8_t maximum_ref_idc = 0;\n\t\t\tauto all_frame_type = FrameType::NONE;\n\t\t\tfor (; nal_index <= end_index; nal_index++) {\n\t\t\t\tconst auto nal_unit = nal[nal_index];\n\t\t\t\tconst auto ref_idc = nal_unit->GetRefIdc();\n\t\t\t\tconst auto frame_type =\n\t\t\t\t\tGetRtpFrameType(nal_unit);\n\t\t\t\tif (maximum_ref_idc < ref_idc)\n\t\t\t\t\tmaximum_ref_idc = ref_idc;\n\t\t\t\tif (frame_type == FrameType::VIDEO_FRAME_IDR)\n\t\t\t\t\tall_frame_type =\n\t\t\t\t\t\tFrameType::VIDEO_FRAME_IDR;\n\t\t\t\telse if (all_frame_type == FrameType::NONE)\n\t\t\t\t\tall_frame_type = frame_type;\n\n\t\t\t\tconst auto size = static_cast<uint16_t>(\n\t\t\t\t\tnal_unit->GetSize());\n\t\t\t\trtp_packet_data[skip++] = size >> 8;\n\t\t\t\trtp_packet_data[skip++] = size & 0xff;\n\t\t\t\tskip += nal_unit->CopyData(\n\t\t\t\t\trtp_packet_data + skip,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH264_NALU_HEADER_SIZE - 2);\n\t\t\t}\n\t\t\t//STAP-A NAL HDR\n\t\t\trtp_packet_data[0] = static_cast<uint8_t>(\n\t\t\t\t(maximum_ref_idc & 0x03) << 5 | 24);\n\t\t\trtp_packet.type = all_frame_type;\n\t\t\tif (!send_frame_callback_(channelId, rtp_packet))\n\t\t\t\treturn false;\n\t\t} else {\n\t\t//Single NAL Unit Packets\n\t\t/*  0                   1                   2                   3\n                 *  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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |F|NRI|  type   |                                               |\n                 * +-+-+-+-+-+-+-+-+                                               |\n                 * |                                                               |\n                 * |               Bytes 2..n of a Single NAL unit                 |\n                 * |                                                               |\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 */\n\t\t\tconst auto nal_unit = nal[nal_index++];\n\t\t\tif (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) {\n\t\t\t\tconst auto size = nal_unit->CopyData(\n\t\t\t\t\trtp_packet_data, MAX_RTP_PAYLOAD_SIZE);\n\t\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE +\n\t\t\t\t\t\t  RTP_HEADER_SIZE +\n\t\t\t\t\t\t  static_cast<uint16_t>(size);\n\t\t\t\trtp_packet.last = 1;\n\t\t\t\trtp_packet.type = GetRtpFrameType(nal_unit);\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\t\t\t} else {\n\t\t//Fragmentation Units (FU-A)\n\t\t/*  0                   1                   2                   3\n                 *  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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * | FU indicator  |   FU header   |                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |\n                 * |                                                               |\n                 * |                         FU payload                            |\n                 * |                                                               |\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t\t */\n\t\t\t\t//PayloadHeader\n\t\t\t\tnal_unit->CopyHeader(rtp_packet_data,\n\t\t\t\t\t\t     H264_NALU_HEADER_SIZE);\n\t\t\t\trtp_packet_data[0] &= 0xe0;\n\t\t\t\trtp_packet_data[0] |= 28;\n\t\t\t\t//FU Header\n\t\t\t\trtp_packet_data[1] = nal_unit->GetType() & 0x1f;\n\n\t\t\t\tsize_t skip = 0;\n\t\t\t\tconst size_t size = nal_unit->GetBodySize();\n\n\t\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE +\n\t\t\t\t\t\t  RTP_HEADER_SIZE +\n\t\t\t\t\t\t  MAX_RTP_PAYLOAD_SIZE;\n\t\t\t\trtp_packet.last = 0;\n\t\t\t\trtp_packet.type = GetRtpFrameType(nal_unit);\n\n\t\t\t\t//First\n\t\t\t\trtp_packet_data[1] |= 0x80;\n\t\t\t\tskip += nal_unit->CopyBody(\n\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\tH264_NALU_HEADER_SIZE + 1,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH264_NALU_HEADER_SIZE - 1,\n\t\t\t\t\tskip);\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\n\t\t\t\t//Middle\n\t\t\t\trtp_packet_data[1] &= 0x1f;\n\t\t\t\twhile (size - skip >\n\t\t\t\t       MAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t       H264_NALU_HEADER_SIZE - 1) {\n\t\t\t\t\tskip += nal_unit->CopyBody(\n\t\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\t\tH264_NALU_HEADER_SIZE +\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\t\tH264_NALU_HEADER_SIZE -\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\tskip);\n\t\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//Last\n\t\t\t\trtp_packet_data[1] |= 0x40;\n\t\t\t\trtp_packet.last = 1;\n\t\t\t\tconst auto last_size = nal_unit->CopyBody(\n\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\tH264_NALU_HEADER_SIZE + 1,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH264_NALU_HEADER_SIZE - 1,\n\t\t\t\t\tskip);\n\t\t\t\trtp_packet.size =\n\t\t\t\t\tRTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t\t\tstatic_cast<uint16_t>(last_size) +\n\t\t\t\t\tH264_NALU_HEADER_SIZE + 1;\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\n\nuint32_t H264Source::GetTimestamp()\n{\n\t/* #if defined(__linux) || defined(__linux__)\n\tstruct timeval tv = {0};\n\tgettimeofday(&tv, NULL);\n\tuint32_t ts = ((tv.tv_sec*1000)+((tv.tv_usec+500)/1000))*90; // 90: _clockRate/1000;\n\treturn ts;\n#else  */\n\tconst auto time_point = chrono::time_point_cast<chrono::microseconds>(\n\t\tchrono::steady_clock::now());\n\treturn static_cast<uint32_t>(\n\t\t(time_point.time_since_epoch().count() + 500) / 1000 * 90);\n\t//#endif\n}\n\nFrameType H264Source::GetRtpFrameType(std::shared_ptr<NalUnit> nalUnit)\n{\n\tif (nalUnit->IsIdrFrame())\n\t\treturn FrameType::VIDEO_FRAME_IDR;\n\tif (nalUnit->IsFrame())\n\t\treturn FrameType::VIDEO_FRAME_NOTIDR;\n\treturn FrameType::NONE;\n}\n"
  },
  {
    "path": "rtsp-server/xop/H264Source.h",
    "content": "// PHZ\n// 2018-5-16\n\n#ifndef XOP_H264_SOURCE_H\n#define XOP_H264_SOURCE_H\n\n#include \"MediaSource.h\"\n#include \"rtp.h\"\n#include \"NalUnit.h\"\n\nnamespace xop {\n\nclass H264Source : public MediaSource {\npublic:\n\tstatic H264Source *CreateNew(std::vector<uint8_t> extraData,\n\t\t\t\t     uint32_t framerate = 25);\n\n\tstatic H264Source *CreateNew(std::vector<uint8_t> sps,\n\t\t\t\t     std::vector<uint8_t> pps,\n\t\t\t\t     uint32_t framerate = 25);\n\t~H264Source() override;\n\n\tvoid SetFramerate(const uint32_t framerate) { framerate_ = framerate; }\n\n\tuint32_t GetFramerate() const { return framerate_; }\n\n\tstd::string GetMediaDescription(uint16_t port) override;\n\n\tstd::string GetAttribute() override;\n\n\tbool HandleFrame(MediaChannelId channel_id, AVFrame frame) override;\n\n\tstatic uint32_t GetTimestamp();\n\nprivate:\n\tH264Source(std::vector<uint8_t> sps, std::vector<uint8_t> pps,\n\t\t   uint32_t framerate);\n\n\tstatic FrameType GetRtpFrameType(std::shared_ptr<NalUnit> nalUnit);\n\n\tuint32_t framerate_ = 25;\n\n\t//uint32_t profileLevelId_;\n\n\tstd::vector<uint8_t> sps_;\n\n\tstd::vector<uint8_t> pps_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/H265NalUnit.cpp",
    "content": "#include \"H265NalUnit.h\"\n\nusing namespace xop;\n\nH265NalUnit::H265NalUnit(const uint8_t *data, size_t dataSize) : NalUnit(data, dataSize)\n{\n}\n\nuint8_t H265NalUnit::GetType()\n{\n\tuint8_t *data;\n\tif (GetHeader(&data) == H265_NALU_HEADER_SIZE) {\n\t\treturn (data[0] & 0x7e) >> 1;\n\t}\n\treturn 0;\n}\n\nuint8_t H265NalUnit::GetLayerId()\n{\n\tuint8_t *data;\n\tif (GetHeader(&data) == H265_NALU_HEADER_SIZE) {\n\t\treturn (data[0] << 5 & 0x20) | (data[1] >> 3 & 0x1f);\n\t}\n\treturn 0;\n}\n\nuint8_t H265NalUnit::GetTemporalId()\n{\n\tuint8_t *data;\n\tif (GetHeader(&data) == H265_NALU_HEADER_SIZE) {\n\t\treturn data[1] & 0x07;\n\t}\n\treturn 0;\n}\n\nH265NalType H265NalUnit::GetH265Type()\n{\n\treturn static_cast<H265NalType>(GetType());\n}\n\nsize_t H265NalUnit::GetHeader(uint8_t **data)\n{\n\tif (GetData(data) >= H265_NALU_HEADER_SIZE)\n\t\treturn H265_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nsize_t H265NalUnit::CopyHeader(uint8_t *start, size_t size)\n{\n\tif (size > H265_NALU_HEADER_SIZE) {\n\t\tsize = H265_NALU_HEADER_SIZE;\n\t}\n\treturn CopyData(start, size);\n}\n\nsize_t H265NalUnit::GetHeaderSize()\n{\n\tconst auto size = GetSize();\n\tif (size > H265_NALU_HEADER_SIZE)\n\t\treturn H265_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nsize_t H265NalUnit::GetBody(uint8_t **data)\n{\n\tconst auto size = GetData(data);\n\tif (size > H265_NALU_HEADER_SIZE) {\n\t\t*data += H265_NALU_HEADER_SIZE;\n\t\treturn size - H265_NALU_HEADER_SIZE;\n\t}\n\treturn 0;\n}\n\nsize_t H265NalUnit::CopyBody(uint8_t *start, size_t size, size_t skip)\n{\n\tskip += H265_NALU_HEADER_SIZE;\n\treturn CopyData(start, size, skip);\n}\n\nsize_t H265NalUnit::GetBodySize()\n{\n\tconst auto size = GetSize();\n\tif (size > H265_NALU_HEADER_SIZE)\n\t\treturn size - H265_NALU_HEADER_SIZE;\n\treturn 0;\n}\n\nbool H265NalUnit::IsIdrFrame()\n{\n\tconst auto type = GetH265Type();\n\treturn type == H265NalType::H265_NAL_IDR_N_LP || type == H265NalType::H265_NAL_IDR_W_RADL;\n}\n\nbool H265NalUnit::IsFrame()\n{\n\tconst auto type = GetH265Type();\n\treturn type <= H265NalType::H265_NAL_CRA_NUT;\n}\n\nNalUnit* H265NalUnit::GetNalUnit(const uint8_t *data, size_t dataSize)\n{\n\treturn new H265NalUnit(data, dataSize);\n}\n\n"
  },
  {
    "path": "rtsp-server/xop/H265NalUnit.h",
    "content": "#ifndef XOP_H265_NALUNIT_H\n#define XOP_H265_NALUNIT_H\n\n#include <cstddef>\n#include \"NalUnit.h\"\n\n#define H265_NALU_HEADER_SIZE 2\n\nnamespace xop {\n\nenum class H265NalType: uint8_t {\n\tH265_NAL_TRAIL_N = 0,\n\tH265_NAL_TRAIL_R = 1,\n\tH265_NAL_TSA_N = 2,\n\tH265_NAL_TSA_R = 3,\n\tH265_NAL_STSA_N = 4,\n\tH265_NAL_STSA_R = 5,\n\tH265_NAL_RADL_N = 6,\n\tH265_NAL_RADL_R = 7,\n\tH265_NAL_RASL_N = 8,\n\tH265_NAL_RASL_R = 9,\n\tH265_NAL_VCL_N10 = 10,\n\tH265_NAL_VCL_R11 = 11,\n\tH265_NAL_VCL_N12 = 12,\n\tH265_NAL_VCL_R13 = 13,\n\tH265_NAL_VCL_N14 = 14,\n\tH265_NAL_VCL_R15 = 15,\n\tH265_NAL_BLA_W_LP = 16,\n\tH265_NAL_BLA_W_RADL = 17,\n\tH265_NAL_BLA_N_LP = 18,\n\tH265_NAL_IDR_W_RADL = 19,\n\tH265_NAL_IDR_N_LP = 20,\n\tH265_NAL_CRA_NUT = 21,\n\tH265_NAL_RSV_IRAP_VCL22 = 22,\n\tH265_NAL_RSV_IRAP_VCL23 = 23,\n\tH265_NAL_RSV_VCL24 = 24,\n\tH265_NAL_RSV_VCL25 = 25,\n\tH265_NAL_RSV_VCL26 = 26,\n\tH265_NAL_RSV_VCL27 = 27,\n\tH265_NAL_RSV_VCL28 = 28,\n\tH265_NAL_RSV_VCL29 = 29,\n\tH265_NAL_RSV_VCL30 = 30,\n\tH265_NAL_RSV_VCL31 = 31,\n\tH265_NAL_VPS = 32,\n\tH265_NAL_SPS = 33,\n\tH265_NAL_PPS = 34,\n\tH265_NAL_AUD = 35,\n\tH265_NAL_EOS_NUT = 36,\n\tH265_NAL_EOB_NUT = 37,\n\tH265_NAL_FD_NUT = 38,\n\tH265_NAL_SEI_PREFIX = 39,\n\tH265_NAL_SEI_SUFFIX = 40,\n\tH265_NAL_RSV_NVCL41 = 41,\n\tH265_NAL_RSV_NVCL42 = 42,\n\tH265_NAL_RSV_NVCL43 = 43,\n\tH265_NAL_RSV_NVCL44 = 44,\n\tH265_NAL_RSV_NVCL45 = 45,\n\tH265_NAL_RSV_NVCL46 = 46,\n\tH265_NAL_RSV_NVCL47 = 47,\n\tH265_NAL_UNSPEC48 = 48,\n\tH265_NAL_UNSPEC49 = 49,\n\tH265_NAL_UNSPEC50 = 50,\n\tH265_NAL_UNSPEC51 = 51,\n\tH265_NAL_UNSPEC52 = 52,\n\tH265_NAL_UNSPEC53 = 53,\n\tH265_NAL_UNSPEC54 = 54,\n\tH265_NAL_UNSPEC55 = 55,\n\tH265_NAL_UNSPEC56 = 56,\n\tH265_NAL_UNSPEC57 = 57,\n\tH265_NAL_UNSPEC58 = 58,\n\tH265_NAL_UNSPEC59 = 59,\n\tH265_NAL_UNSPEC60 = 60,\n\tH265_NAL_UNSPEC61 = 61,\n\tH265_NAL_UNSPEC62 = 62,\n\tH265_NAL_UNSPEC63 = 63,\n};\n\nclass H265NalUnit : public NalUnit {\npublic:\n\tuint8_t GetType() override;\n\tuint8_t GetLayerId();\n\tuint8_t GetTemporalId();\n\tH265NalType GetH265Type();\n\tsize_t GetHeader(uint8_t **data) override;\n\tsize_t CopyHeader(uint8_t *start, size_t size) override;\n\tsize_t GetHeaderSize() override;\n\tsize_t GetBody(uint8_t **data) override;\n\tsize_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) override;\n\tsize_t GetBodySize() override;\n\tbool IsIdrFrame() override;\n\tbool IsFrame() override;\n\tstatic NalUnit *GetNalUnit(const uint8_t *data, size_t dataSize);\n\nprivate:\n\tH265NalUnit(const uint8_t *data, size_t dataSize);\n\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/H265Source.cpp",
    "content": "// PHZ\n// 2018-6-7\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n\n#include \"H265Source.h\"\n#include <cstdio>\n#include <cstring>\n#include <chrono>\n#include <utility>\n\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <sys/time.h>\n#endif\n\n#include \"Base64Encode.h\"\n#include \"Nal.h\"\n#include \"H265NalUnit.h\"\n\nusing namespace xop;\nusing namespace std;\n\nH265Source::H265Source(vector<uint8_t> vps, vector<uint8_t> sps,\n\t\t       vector<uint8_t> pps, vector<uint8_t> sei,\n\t\t       const uint32_t framerate)\n\t: framerate_(framerate),\n\t  vps_(std::move(vps)),\n\t  sps_(std::move(sps)),\n\t  pps_(std::move(pps)),\n\t  sei_(std::move(sei))\n{\n\tpayload_ = 96;\n\tmedia_type_ = MediaType::H265;\n\tclock_rate_ = 90000;\n}\n\nH265Source *H265Source::CreateNew(vector<uint8_t> extraData,\n\t\t\t\t  vector<uint8_t> sei, const uint32_t framerate)\n{\n\tNal<H265NalUnit> nal(extraData);\n\tvector<uint8_t> vps, sps, pps;\n\tconst auto vps_nal_unit = nal.GetNalUnitByType(\n\t\t\t   static_cast<uint8_t>(H265NalType::H265_NAL_VPS)),\n\t\t   sps_nal_unit = nal.GetNalUnitByType(\n\t\t\t   static_cast<uint8_t>(H265NalType::H265_NAL_SPS)),\n\t\t   pps_nal_unit = nal.GetNalUnitByType(\n\t\t\t   static_cast<uint8_t>(H265NalType::H265_NAL_PPS));\n\tif (vps_nal_unit != nullptr)\n\t\tvps = vps_nal_unit->GetData();\n\tif (sps_nal_unit != nullptr)\n\t\tsps = sps_nal_unit->GetData();\n\tif (pps_nal_unit != nullptr)\n\t\tpps = pps_nal_unit->GetData();\n\n\treturn new H265Source(vps, sps, pps, std::move(sei), framerate);\n}\n\nH265Source::~H265Source() = default;\n\nH265Source *H265Source::CreateNew(vector<uint8_t> vps, vector<uint8_t> sps,\n\t\t\t\t  vector<uint8_t> pps, vector<uint8_t> sei,\n\t\t\t\t  const uint32_t framerate)\n{\n\treturn new H265Source(std::move(vps), std::move(sps), std::move(pps), std::move(sei),\n\t\t\t      framerate);\n}\n\nstring H265Source::GetMediaDescription(const uint16_t port)\n{\n\tchar buf[100];\n\tsnprintf(buf, sizeof(buf), \"m=video %hu RTP/AVP 96\", port);\n\n\treturn buf;\n}\n\nstring H265Source::GetAttribute()\n{\n\tauto sdp = string(\"a=rtpmap:96 H265/90000\\r\\n\");\n\n\tif (!vps_.empty() && !sps_.empty() && !pps_.empty()) {\n\t\tconst auto fmtp =\n\t\t\t\"a=fmtp:96 profile-space=%u;tier-flag=%u;\"\n\t\t\t\"profile-id=%u;level-id=%u;interop-constraints=%012llX;\"\n\t\t\t\"sprop-vps=%s;sprop-pps=%s;sprop-sps=%s;%s\";\n\n\t\tstring vps_base64, pps_base64, sps_base64, sei;\n\t\tvps_base64 = Base64Encode(vps_.data(), vps_.size());\n\t\tpps_base64 = Base64Encode(pps_.data(), pps_.size());\n\t\tsps_base64 = Base64Encode(sps_.data(), sps_.size());\n\t\tif (!sei_.empty()) {\n\t\t\tsei = \"sprop-sei=\";\n\t\t\tsei.append(Base64Encode(sei_.data(), sei_.size()));\n\t\t\tsei.append(\";\");\n\t\t} else\n\t\t\tsei = \"\";\n\n\t\tconst uint8_t profile_space = sps_.at(3) >> 6;\n\t\tconst uint8_t tier_flag = (sps_.at(3) & 0x20) >> 5;\n\t\tconst uint8_t profile_id = sps_.at(3) & 0x1f;\n\t\tconst uint8_t level_id = sps_.at(17);\n\t\tconst uint64_t interop_constraints =\n\t\t\tstatic_cast<uint64_t>(sps_.at(9)) << 40 |\n\t\t\tstatic_cast<uint64_t>(sps_.at(10)) << 32 |\n\t\t\tstatic_cast<uint64_t>(sps_.at(11)) << 24 |\n\t\t\tstatic_cast<uint64_t>(sps_.at(12)) << 16 |\n\t\t\tstatic_cast<uint64_t>(sps_.at(13)) << 8 |\n\t\t\tstatic_cast<uint64_t>(sps_.at(14));\n\n\t\tconst size_t buf_size = 1 + strlen(fmtp) + vps_base64.length() +\n\t\t\t\t\tpps_base64.length() +\n\t\t\t\t\tsps_base64.length() + sei.length();\n\t\tauto buf = vector<char>(buf_size);\n\n\t\tsnprintf(buf.data(), buf_size, fmtp, profile_space, tier_flag, profile_id,\n\t\t\tlevel_id, interop_constraints, vps_base64.c_str(),\n\t\t\tpps_base64.c_str(), sps_base64.c_str(), sei.c_str());\n\t\tbuf[strlen(buf.data()) - 1] = '\\0';\n\n\t\tsdp.append(buf.data());\n\t}\n\n\treturn sdp;\n}\n\nbool H265Source::HandleFrame(const MediaChannelId channelId,\n\t\t\t     const AVFrame frame)\n{\n\tRtpPacket rtp_packet;\n\t//rtp_packet.timestamp = frame.timestamp == 0 ? GetTimestamp() : frame.timestamp;\n\trtp_packet.timestamp = frame.timestamp;\n\tconst auto rtp_packet_data =\n\t\trtp_packet.data.get() + RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE;\n\n\tNal<H265NalUnit> nal(frame.buffer.get(), frame.size);\n\n\tif (nal.GetCount() == 0)\n\t\treturn false;\n\n\tsize_t nal_index = 0;\n\twhile (nal_index < nal.GetCount()) {\n\t\tsize_t end_index = nal_index;\n\t\tsize_t size_count = H265_NALU_HEADER_SIZE;\n\t\twhile (size_count < MAX_RTP_PAYLOAD_SIZE &&\n\t\t       end_index < nal.GetCount()) {\n\t\t\tsize_count += nal[end_index++]->GetSize() + 2;\n\t\t}\n\t\tend_index--;\n\t\tif (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index)\n\t\t\tsize_count -= nal[end_index--]->GetSize() + 2;\n\t\tif (end_index > nal_index) {\n\t\t//Aggregation Packets\n\t\t/*  0                   1                   2                   3\n                 *  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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |                          RTP Header                           |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |   PayloadHdr (Type=48)        |         NALU 1 Size           |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |          NALU 1 HDR           |                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+         NALU 1 Data           |\n                 * |                   . . .                                       |\n                 * |                                                               |\n                 * +               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |  . . .        | NALU 2 Size                   | NALU 2 HDR    |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * | NALU 2 HDR    |                                               |\n                 * +-+-+-+-+-+-+-+-+              NALU 2 Data                      |\n                 * |                   . . .                                       |\n                 * |                                                               |\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 */\n\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t\t\t  static_cast<uint16_t>(size_count);\n\t\t\trtp_packet.last = 1;\n\t\t\tsize_t skip = H265_NALU_HEADER_SIZE;\n\t\t\tuint8_t lowest_layer_id = 0x3f;\n\t\t\tuint8_t lowest_temporal_id = 0x07;\n\t\t\tauto all_frame_type = FrameType::NONE;\n\t\t\tfor (; nal_index <= end_index; nal_index++) {\n\t\t\t\tconst auto nal_unit = nal[nal_index];\n\t\t\t\tconst auto layer_id = nal_unit->GetLayerId();\n\t\t\t\tconst auto temporal_id =\n\t\t\t\t\tnal_unit->GetTemporalId();\n\t\t\t\tconst auto frame_type =\n\t\t\t\t\tGetRtpFrameType(nal_unit);\n\t\t\t\tif (lowest_layer_id > layer_id)\n\t\t\t\t\tlowest_layer_id = layer_id;\n\t\t\t\tif (lowest_temporal_id > temporal_id)\n\t\t\t\t\tlowest_temporal_id = temporal_id;\n\t\t\t\tif (frame_type == FrameType::VIDEO_FRAME_IDR)\n\t\t\t\t\tall_frame_type =\n\t\t\t\t\t\tFrameType::VIDEO_FRAME_IDR;\n\t\t\t\telse if (all_frame_type == FrameType::NONE)\n\t\t\t\t\tall_frame_type = frame_type;\n\n\t\t\t\tconst auto size = static_cast<uint16_t>(\n\t\t\t\t\tnal_unit->GetSize());\n\t\t\t\trtp_packet_data[skip++] = size >> 8;\n\t\t\t\trtp_packet_data[skip++] = size & 0xff;\n\t\t\t\tskip += nal_unit->CopyData(\n\t\t\t\t\trtp_packet_data + skip,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH265_NALU_HEADER_SIZE - 2);\n\t\t\t}\n\t\t\t//PayloadHeader\n\t\t\trtp_packet_data[0] = (lowest_layer_id & 0x3f) >> 5 |\n\t\t\t\t\t     48 << 1;\n\t\t\trtp_packet_data[1] = static_cast<uint8_t>(\n\t\t\t\t(lowest_layer_id & 0x3f) << 3 |\n\t\t\t\t(lowest_temporal_id & 0x07));\n\t\t\trtp_packet.type = all_frame_type;\n\t\t\tif (!send_frame_callback_(channelId, rtp_packet))\n\t\t\t\treturn false;\n\t\t} else {\n\t\t//Single NAL Unit Packets\n\t\t/*  0                   1                   2                   3\n                 *   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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |           PayloadHdr          |                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |\n                 * |                                                               |\n                 * |                                                               |\n                 * |                  NAL unit payload data                        |\n                 * |                                                               |\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 */\n\t\t\tconst auto nal_unit = nal[nal_index++];\n\t\t\tif (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) {\n\t\t\t\tconst auto size = nal_unit->CopyData(\n\t\t\t\t\trtp_packet_data, MAX_RTP_PAYLOAD_SIZE);\n\t\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE +\n\t\t\t\t\t\t  RTP_HEADER_SIZE +\n\t\t\t\t\t\t  static_cast<uint16_t>(size);\n\t\t\t\trtp_packet.last = 1;\n\t\t\t\trtp_packet.type = GetRtpFrameType(nal_unit);\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\t\t\t} else {\n\t\t//Fragmentation Units\n\t\t/*  0                   1                   2                   3\n                 *  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\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n                 * |    PayloadHdr (Type=49)       |   FU header   |               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+               |\n                 * |                                                               |\n                 * |                                                               |\n                 * |                         FU payload                            |\n                 * |                                                               |\n                 * |                                                               |\n                 * |                                                               |\n                 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t\t */\n\t\t\t\t//PayloadHeader\n\t\t\t\tnal_unit->CopyHeader(rtp_packet_data,\n\t\t\t\t\t\t     H265_NALU_HEADER_SIZE);\n\t\t\t\trtp_packet_data[0] &= 0x81;\n\t\t\t\trtp_packet_data[0] |= 49 << 1;\n\t\t\t\t//FU Header\n\t\t\t\trtp_packet_data[2] = nal_unit->GetType() & 0x3f;\n\n\t\t\t\tsize_t skip = 0;\n\t\t\t\tconst size_t size = nal_unit->GetBodySize();\n\n\t\t\t\trtp_packet.size = RTP_TCP_HEAD_SIZE +\n\t\t\t\t\t\t  RTP_HEADER_SIZE +\n\t\t\t\t\t\t  MAX_RTP_PAYLOAD_SIZE;\n\t\t\t\trtp_packet.last = 0;\n\t\t\t\trtp_packet.type = GetRtpFrameType(nal_unit);\n\n\t\t\t\t//First\n\t\t\t\trtp_packet_data[2] |= 0x80;\n\t\t\t\tskip += nal_unit->CopyBody(\n\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\tH265_NALU_HEADER_SIZE + 1,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH265_NALU_HEADER_SIZE - 1,\n\t\t\t\t\tskip);\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\n\t\t\t\t//Middle\n\t\t\t\trtp_packet_data[2] &= 0x3f;\n\t\t\t\twhile (size - skip > MAX_RTP_PAYLOAD_SIZE - 3) {\n\t\t\t\t\tskip += nal_unit->CopyBody(\n\t\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\t\tH265_NALU_HEADER_SIZE +\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\t\tH265_NALU_HEADER_SIZE -\n\t\t\t\t\t\t\t1,\n\t\t\t\t\t\tskip);\n\t\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t//Last\n\t\t\t\trtp_packet_data[2] |= 0x40;\n\t\t\t\trtp_packet.last = 1;\n\t\t\t\tconst auto last_size = nal_unit->CopyBody(\n\t\t\t\t\trtp_packet_data +\n\t\t\t\t\t\tH265_NALU_HEADER_SIZE + 1,\n\t\t\t\t\tMAX_RTP_PAYLOAD_SIZE -\n\t\t\t\t\t\tH265_NALU_HEADER_SIZE - 1,\n\t\t\t\t\tskip);\n\t\t\t\trtp_packet.size =\n\t\t\t\t\tRTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t\t\tstatic_cast<uint16_t>(last_size) +\n\t\t\t\t\tH265_NALU_HEADER_SIZE + 1;\n\t\t\t\tif (!send_frame_callback_(channelId,\n\t\t\t\t\t\t\t  rtp_packet))\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\treturn true;\n}\n\nuint32_t H265Source::GetTimestamp()\n{\n\t/* #if defined(__linux) || defined(__linux__) \n\tstruct timeval tv = {0};\n\tgettimeofday(&tv, NULL);\n\tuint32_t ts = ((tv.tv_sec*1000)+((tv.tv_usec+500)/1000))*90; // 90: _clockRate/1000;\n\treturn ts;\n#else */\n\t//auto time_point = chrono::time_point_cast<chrono::milliseconds>(chrono::system_clock::now());\n\tconst auto time_point = chrono::time_point_cast<chrono::microseconds>(\n\t\tchrono::steady_clock::now());\n\treturn static_cast<uint32_t>(\n\t\t(time_point.time_since_epoch().count() + 500) / 1000 * 90);\n\t//#endif\n}\n\nFrameType H265Source::GetRtpFrameType(std::shared_ptr<NalUnit> nalUnit)\n{\n\tif (nalUnit->IsIdrFrame())\n\t\treturn FrameType::VIDEO_FRAME_IDR;\n\tif (nalUnit->IsFrame())\n\t\treturn FrameType::VIDEO_FRAME_NOTIDR;\n\treturn FrameType::NONE;\n}\n"
  },
  {
    "path": "rtsp-server/xop/H265Source.h",
    "content": "// PHZ\n// 2018-5-16\n\n#ifndef XOP_H265_SOURCE_H\n#define XOP_H265_SOURCE_H\n\n#include \"MediaSource.h\"\n#include \"rtp.h\"\n#include \"NalUnit.h\"\n\nnamespace xop {\n\nclass H265Source : public MediaSource {\npublic:\n\tstatic H265Source *\n\tCreateNew(std::vector<uint8_t> extraData,\n\t\t  std::vector<uint8_t> sei = std::vector<uint8_t>(),\n\t\t  uint32_t framerate = 25);\n\n\tstatic H265Source *\n\tCreateNew(std::vector<uint8_t> vps, std::vector<uint8_t> sps,\n\t\t  std::vector<uint8_t> pps,\n\t\t  std::vector<uint8_t> sei = std::vector<uint8_t>(),\n\t\t  uint32_t framerate = 25);\n\n\t~H265Source() override;\n\n\tvoid SetFramerate(const uint32_t framerate) { framerate_ = framerate; }\n\n\tuint32_t GetFramerate() const { return framerate_; }\n\n\tstd::string GetMediaDescription(uint16_t port = 0) override;\n\n\tstd::string GetAttribute() override;\n\n\tbool HandleFrame(MediaChannelId channelId, AVFrame frame) override;\n\n\tstatic uint32_t GetTimestamp();\n\nprivate:\n\tH265Source(std::vector<uint8_t> vps, std::vector<uint8_t> sps,\n\t\t   std::vector<uint8_t> pps, std::vector<uint8_t> sei,\n\t\t   uint32_t framerate);\n\n\tstatic FrameType GetRtpFrameType(std::shared_ptr<NalUnit> nalUnit);\n\n\tuint32_t framerate_ = 25;\n\n\tstd::vector<uint8_t> vps_;\n\n\tstd::vector<uint8_t> sps_;\n\n\tstd::vector<uint8_t> pps_;\n\n\tstd::vector<uint8_t> sei_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/MacMd5.cpp",
    "content": "#include \"MacMd5.h\"\n\n#if defined(__APPLE__) || defined(__MACH__)\n#include <CommonCrypto/CommonDigest.h>\n#endif\n\nusing namespace xop;\n\nMacMd5::MacMd5() : Md5() {}\n\nMacMd5::~MacMd5() = default;\n\nvoid MacMd5::GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t\tunsigned char *outHash)\n{\n#if defined(__APPLE__) || defined(__MACH__)\n\tCC_MD5(data, (CC_LONG)dataSize, outHash);\n#endif\n}\n"
  },
  {
    "path": "rtsp-server/xop/MacMd5.h",
    "content": "#ifndef _XOP_MACMD5_H\n#define _XOP_MACMD5_H\n\n#include \"Md5.h\"\n\nnamespace xop {\nclass MacMd5 : public Md5 {\npublic:\n\tMacMd5();\n\t~MacMd5() override;\n\n\tvoid GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t\tunsigned char *outHash) override;\n};\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/Md5.cpp",
    "content": "#include \"Md5.h\"\n\nusing namespace xop;\n\nMd5::Md5() = default;\n\nMd5::~Md5() = default;\n\nvoid Md5::GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t     unsigned char *outHash)\n{\n}\n\nvoid Md5::GetMd5Hash(const std::string &str, unsigned char *outHash)\n{\n\tGetMd5Hash(reinterpret_cast<const unsigned char *>(str.c_str()),\n\t\t   str.length(), outHash);\n}\n\nstd::string Md5::GetMd5HashString(const unsigned char *data,\n\t\t\t\t  const size_t dataSize)\n{\n\tunsigned char hash[MD5_HASH_LENGTH];\n\tGetMd5Hash(data, dataSize, hash);\n\tchar hashStr[2 * MD5_HASH_LENGTH + 1]{};\n\tfor (size_t i = 0; i < MD5_HASH_LENGTH; i++) {\n\t\thashStr[2 * i] = hex_value_[((hash[i] >> 4) & 0xF)];\n\t\thashStr[2 * i + 1] = hex_value_[(hash[i]) & 0x0F];\n\t}\n\thashStr[2 * MD5_HASH_LENGTH] = '\\0';\n\treturn hashStr;\n}\n\nstd::string Md5::GetMd5HashString(const std::string &str)\n{\n\treturn GetMd5HashString(\n\t\treinterpret_cast<const unsigned char *>(str.c_str()),\n\t\tstr.length());\n}\n"
  },
  {
    "path": "rtsp-server/xop/Md5.h",
    "content": "#ifndef _XOP_MD5_H\n#define _XOP_MD5_H\n\n#include <string>\n\n#define MD5_HASH_LENGTH 16\n\nnamespace xop {\nclass Md5 {\npublic:\n\tMd5();\n\tvirtual ~Md5();\n\n\tvirtual void GetMd5Hash(const unsigned char *data, size_t dataSize,\n\t\t\t\tunsigned char *outHash);\n\tvoid GetMd5Hash(const std::string &str, unsigned char *outHash);\n\tstd::string GetMd5HashString(const unsigned char *data,\n\t\t\t\t     size_t dataSize);\n\tstd::string GetMd5HashString(const std::string &str);\n\nprivate:\n\tconst char hex_value_[16] = {'0', '1', '2', '3', '4', '5', '6', '7',\n\t\t\t\t     '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\n};\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/MediaSession.cpp",
    "content": "﻿// PHZ\n// 2018-9-30\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#include \"MediaSession.h\"\n#include \"RtpConnection.h\"\n#include \"RtspConnection.h\"\n#include <cstring>\n#include <ctime>\n#include <map>\n#include <forward_list>\n#include <utility>\n#include \"net/Logger.h\"\n\nusing namespace xop;\nusing namespace std;\n\nstd::atomic_uint MediaSession::last_session_id_(1);\n\nMediaSession::MediaSession(std::string url_suffix,\n\t\t\t   const uint8_t max_channel_count)\n\t: max_channel_count_(max_channel_count),\n\t  session_id_(++last_session_id_),\n\t  suffix_(std::move(url_suffix)),\n\t  media_sources_(max_channel_count),\n\t  buffer_(max_channel_count),\n\t  multicast_port_(max_channel_count, 0),\n\t  has_new_client_(false)\n{\n}\n\nMediaSession *MediaSession::CreateNew(std::string url_suffix,\n\t\t\t\t      const uint32_t max_channel_count)\n{\n\treturn new MediaSession(std::move(url_suffix), max_channel_count);\n}\n\nMediaSession::~MediaSession()\n{\n\tif (!multicast_ip6_.empty()) {\n\t\tMulticastAddr::instance().Release(multicast_ip6_);\n\t}\n\tif (!multicast_ip_.empty()) {\n\t\tMulticastAddr::instance().Release(multicast_ip_);\n\t}\n}\n\nvoid MediaSession::AddNotifyConnectedCallback(\n\tconst NotifyConnectedCallback &callback)\n{\n\tnotify_connected_callbacks_.push_back(callback);\n}\n\nvoid MediaSession::AddNotifyDisconnectedCallback(\n\tconst NotifyDisconnectedCallback &callback)\n{\n\tnotify_disconnected_callbacks_.push_back(callback);\n}\n\nbool MediaSession::AddSource(MediaChannelId channel_id, MediaSource *source)\n{\n\tif (static_cast<uint8_t>(channel_id) >= max_channel_count_)\n\t\treturn false;\n\n\tsource->SetSendFrameCallback([this](const MediaChannelId channel_id,\n\t\t\t\t\t    const RtpPacket &pkt) {\n\t\tstd::lock_guard lock(map_mutex_);\n\n\t\tstd::forward_list<std::shared_ptr<RtpConnection>> clients;\n\t\t//std::map<int, RtpPacket> packets;\n\n\t\tRtpPacket tmp_pkt;\n\t\tmemcpy(tmp_pkt.data.get(), pkt.data.get(), pkt.size);\n\t\ttmp_pkt.size = pkt.size;\n\t\ttmp_pkt.last = pkt.last;\n\t\ttmp_pkt.timestamp = pkt.timestamp;\n\t\ttmp_pkt.type = pkt.type;\n\n\t\tfor (auto iter = clients_.begin(); iter != clients_.end();\n\t\t     ++iter) {\n\t\t\tif (auto conn = iter->second.lock(); conn == nullptr) {\n\t\t\t\tclients_.erase(iter);\n\t\t\t} else {\n\t\t\t\tif (conn->IsMulticast()) continue;\n\t\t\t\tconn->SendRtpPacket(channel_id, tmp_pkt);\n\t\t\t}\n\t\t}\n\n\t\t/*for (auto iter = clients_.begin(); iter != clients_.end();) {\n\t\t\tif (auto conn = iter->second.lock(); conn == nullptr) {\n\t\t\t\tclients_.erase(iter++);\n\t\t\t} else {\n\t\t\t\tif (int id = conn->GetId(); id >= 0) {\n\t\t\t\t\tif (packets.find(id) == packets.end()) {\n\t\t\t\t\t\tRtpPacket tmp_pkt;\n\t\t\t\t\t\tmemcpy(tmp_pkt.data.get(),\n\t\t\t\t\t\t       pkt.data.get(),\n\t\t\t\t\t\t       pkt.size);\n\t\t\t\t\t\ttmp_pkt.size = pkt.size;\n\t\t\t\t\t\ttmp_pkt.last = pkt.last;\n\t\t\t\t\t\ttmp_pkt.timestamp =\n\t\t\t\t\t\t\tpkt.timestamp;\n\t\t\t\t\t\ttmp_pkt.type = pkt.type;\n\t\t\t\t\t\tpackets.emplace(id, tmp_pkt);\n\t\t\t\t\t}\n\t\t\t\t\tclients.emplace_front(conn);\n\t\t\t\t}\n\t\t\t\t++iter;\n\t\t\t}\n\t\t}\n\n\t\tint count = 0;\n\t\tfor (const auto &iter : clients) {\n\t\t\tif (int id = iter->GetId(); id >= 0) {\n\t\t\t\tif (auto iter2 = packets.find(id);\n\t\t\t\t    iter2 != packets.end()) {\n\t\t\t\t\tcount++;\n\t\t\t\t\tif (const int ret = iter->SendRtpPacket(\n\t\t\t\t\t\t    channel_id, iter2->second);\n\t\t\t\t\t    is_multicast_ && ret == 0) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}*/\n\n\t\tif (is_multicast_) {\n\t\t\tmulticast_v6_client_->SendRtpPacket(\n\t\t\t\tchannel_id, tmp_pkt);\n\t\t\tmulticast_client_->SendRtpPacket(\n\t\t\t\tchannel_id, tmp_pkt);\n\t\t}\n\t\treturn true;\n\t});\n\n\tmedia_sources_[static_cast<uint8_t>(channel_id)].reset(source);\n\treturn true;\n}\n\nbool MediaSession::RemoveSource(MediaChannelId channel_id)\n{\n\tmedia_sources_[static_cast<uint8_t>(channel_id)] = nullptr;\n\treturn true;\n}\n\nbool MediaSession::StartMulticast()\n{\n\tif (task_scheduler_.lock() == nullptr)\n\t\treturn false;\n\n\tif (is_multicast_)\n\t\treturn true;\n\n\tmulticast_ip6_ = MulticastAddr::instance().GetAddr6();\n\tmulticast_ip_ = MulticastAddr::instance().GetAddr();\n\tif (multicast_ip6_.empty() || multicast_ip_.empty())\n\t\treturn false;\n\n\tstd::random_device rd;\n\tfor (uint32_t chn = 0; chn < max_channel_count_; chn++)\n\t\tmulticast_port_[chn] = htons(rd() & 0xfffe);\n\n\tmulticast_v6_client_ =\n\t\tmake_shared<RtpConnection>(GetMaxChannelCount(), task_scheduler_, true);\n\tmulticast_client_ = make_shared<RtpConnection>(GetMaxChannelCount(),\n\t\t\t\t\t\t       task_scheduler_, false);\n\n\tfor (uint32_t chn = 0; chn < max_channel_count_; chn++) {\n\t\tif (!multicast_v6_client_->SetupRtpOverMulticast(\n\t\t\t    static_cast<MediaChannelId>(chn), multicast_ip6_,\n\t\t\t    multicast_port_[chn]))\n\t\t\treturn false;\n\t\tif (!multicast_client_->SetupRtpOverMulticast(\n\t\t\t    static_cast<MediaChannelId>(chn), multicast_ip_,\n\t\t\t    multicast_port_[chn]))\n\t\t\treturn false;\n\t}\n\n\tis_multicast_ = true;\n\treturn true;\n}\n\nstd::string MediaSession::GetSdpMessage(const std::string ip,\n\t\t\t\t\tconst std::string &session_name,\n\t\t\t\t\tconst bool ipv6) const\n{\n\tif (media_sources_.empty())\n\t\treturn \"\";\n\n\t//std::string ip = NetInterface::GetLocalIPAddress(ipv6);\n\tchar buf[2048] = {0};\n\n\tsnprintf(buf, sizeof(buf),\n\t\t \"v=0\\r\\n\"\n\t\t \"o=- 9%ld 1 IN IP%d %s\\r\\n\"\n\t\t \"t=0 0\\r\\n\"\n\t\t \"a=control:*\\r\\n\",\n\t\t static_cast<long>(std::time(nullptr)), ipv6 ? 6 : 4,\n\t\t ip.c_str());\n\n\tif (!session_name.empty()) {\n\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t \"s=%s\\r\\n\", session_name.c_str());\n\t}\n\n\tif (is_multicast_) {\n\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t \"a=type:broadcast\\r\\n\"\n\t\t\t \"a=rtcp-unicast:reflection\\r\\n\");\n\t}\n\n\tfor (uint32_t chn = 0; chn < max_channel_count_; chn++) {\n\t\tif (media_sources_[chn]) {\n\t\t\tif (is_multicast_) {\n\t\t\t\tsnprintf(buf + strlen(buf),\n\t\t\t\t\t sizeof(buf) - strlen(buf), \"%s\\r\\n\",\n\t\t\t\t\t media_sources_[chn]\n\t\t\t\t\t\t ->GetMediaDescription(\n\t\t\t\t\t\t\t multicast_port_[chn])\n\t\t\t\t\t\t .c_str());\n\n\t\t\t\tsnprintf(buf + strlen(buf),\n\t\t\t\t\t sizeof(buf) - strlen(buf),\n\t\t\t\t\t \"c=IN IP%d %s/%d\\r\\n\", ipv6 ? 6 : 4,\n\t\t\t\t\t ipv6 ? multicast_ip6_.c_str()\n\t\t\t\t\t      : multicast_ip_.c_str(),\n\t\t\t\t\t ipv6 ? 255 : 8);\n\t\t\t} else {\n\t\t\t\tsnprintf(buf + strlen(buf),\n\t\t\t\t\t sizeof(buf) - strlen(buf), \"%s\\r\\n\",\n\t\t\t\t\t media_sources_[chn]\n\t\t\t\t\t\t ->GetMediaDescription(0)\n\t\t\t\t\t\t .c_str());\n\t\t\t}\n\n\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t \"%s\\r\\n\",\n\t\t\t\t media_sources_[chn]->GetAttribute().c_str());\n\n\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t \"a=control:track%d\\r\\n\", chn);\n\t\t}\n\t}\n\n\treturn buf;\n}\n\nMediaSource *MediaSession::GetMediaSource(MediaChannelId channel_id) const\n{\n\tif (static_cast<uint8_t>(channel_id) < max_channel_count_) {\n\t\treturn media_sources_[static_cast<uint8_t>(channel_id)].get();\n\t}\n\n\treturn nullptr;\n}\n\nbool MediaSession::HandleFrame(MediaChannelId channelId, AVFrame frame)\n{\n\tstd::lock_guard lock(mutex_);\n\n\tif (static_cast<uint8_t>(channelId) < max_channel_count_) {\n\t\tmedia_sources_[static_cast<uint8_t>(channelId)]->HandleFrame(\n\t\t\tchannelId, std::move(frame));\n\t} else {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool MediaSession::AddClient(const SOCKET rtspfd,\n\t\t\t     std::shared_ptr<RtpConnection> rtp_conn, string ip,\n\t\t\t     uint16_t port)\n{\n\tstd::lock_guard lock(map_mutex_);\n\n\tif (const auto iter = clients_.find(rtspfd); iter == clients_.end()) {\n\t\tstd::weak_ptr rtp_conn_weak_ptr = rtp_conn;\n\t\tclients_.emplace(rtspfd, rtp_conn_weak_ptr);\n\t\tfor (auto &callback : notify_connected_callbacks_)\n\t\t\tcallback(session_id_, ip,\n\t\t\t\t port); /* 回调通知当前客户端数量 */\n\n\t\thas_new_client_ = true;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nvoid MediaSession::RemoveClient(const SOCKET rtspfd, std::string ip,\n\t\t\t\tuint16_t port)\n{\n\tstd::lock_guard lock(map_mutex_);\n\n\tif (const auto iter = clients_.find(rtspfd); iter != clients_.end()) {\n\t\tif (const auto conn = iter->second.lock()) {\n\t\t\tfor (auto &callback : notify_disconnected_callbacks_)\n\t\t\t\tcallback(session_id_, ip,\n\t\t\t\t\t port); /* 回调通知当前客户端数量 */\n\t\t}\n\t\tclients_.erase(iter);\n\t}\n}\n"
  },
  {
    "path": "rtsp-server/xop/MediaSession.h",
    "content": "// PHZ\n// 2018-6-8\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#ifndef XOP_MEDIA_SESSION_H\n#define XOP_MEDIA_SESSION_H\n\n#include <memory>\n#include <string>\n#include <vector>\n#include <mutex>\n#include <atomic>\n#include <random>\n#include <cstdint>\n#include <map>\n#include <unordered_set>\n#include \"media.h\"\n#include \"MediaSource.h\"\n#include \"net/Socket.h\"\n#include \"net/RingBuffer.h\"\n\nnamespace xop {\n\nclass RtpConnection;\nclass TaskScheduler;\nclass EventLoop;\n\nclass MediaSession {\npublic:\n\tusing Ptr = std::shared_ptr<MediaSession>;\n\tusing NotifyConnectedCallback =\n\t\tstd::function<void(MediaSessionId sessionId,\n\t\t\t\t   std::string peer_ip, uint16_t peer_port)>;\n\tusing NotifyDisconnectedCallback =\n\t\tstd::function<void(MediaSessionId sessionId,\n\t\t\t\t   std::string peer_ip, uint16_t peer_port)>;\n\n\tstatic MediaSession *CreateNew(std::string url_suffix = \"live\",\n\t\t\t\t       uint32_t max_channel_count = 2);\n\tvirtual ~MediaSession();\n\n\tbool AddSource(MediaChannelId channel_id, MediaSource *source);\n\tbool RemoveSource(MediaChannelId channel_id);\n\n\tbool StartMulticast();\n\n\tvoid\n\tAddNotifyConnectedCallback(const NotifyConnectedCallback &callback);\n\n\tvoid AddNotifyDisconnectedCallback(\n\t\tconst NotifyDisconnectedCallback &callback);\n\n\tstd::string GetRtspUrlSuffix() const { return suffix_; }\n\n\tvoid SetRtspUrlSuffix(const std::string &suffix) { suffix_ = suffix; }\n\n\tstd::string GetSdpMessage(std::string ip,\n\t\t\t\t  const std::string &session_name,\n\t\t\t\t  bool ipv6 = false) const;\n\n\tMediaSource *GetMediaSource(MediaChannelId channel_id) const;\n\n\tbool HandleFrame(MediaChannelId channel_id, AVFrame frame);\n\n\tbool AddClient(SOCKET rtspfd, std::shared_ptr<RtpConnection> rtp_conn,\n\t               std::string ip, uint16_t port);\n\tvoid RemoveClient(SOCKET rtspfd, std::string ip, uint16_t port);\n\n\tMediaSessionId GetMediaSessionId() const { return session_id_; }\n\n\tuint32_t GetNumClient() const\n\t{\n\t\treturn static_cast<uint32_t>(clients_.size());\n\t}\n\n\tuint8_t GetMaxChannelCount() const { return max_channel_count_; }\n\n\tbool IsMulticast() const { return is_multicast_; }\n\n\tstd::string GetMulticastIp(const bool ipv6) const\n\t{\n\t\treturn ipv6 ? multicast_ip6_ : multicast_ip_;\n\t}\n\n\tuint16_t GetMulticastPort(MediaChannelId channel_id) const\n\t{\n\t\tif (static_cast<uint8_t>(channel_id) >=\n\t\t    multicast_port_.size()) {\n\t\t\treturn 0;\n\t\t}\n\t\treturn multicast_port_[static_cast<uint8_t>(channel_id)];\n\t}\n\n\tstd::shared_ptr<RtpConnection> GetMulticastRtpConnection(const bool ipv6) const\n\t{\n\t\treturn ipv6 ? multicast_v6_client_\n\t\t\t    : multicast_client_;\n\t}\n\nprivate:\n\tfriend class MediaSource;\n\tfriend class RtspServer;\n\tMediaSession(std::string url_suffix, uint8_t max_channel_count);\n\n\tuint8_t max_channel_count_ = 0;\n\n\tMediaSessionId session_id_ = 0;\n\tstd::string suffix_;\n\n\tstd::vector<std::unique_ptr<MediaSource>> media_sources_;\n\tstd::vector<RingBuffer<AVFrame>> buffer_;\n\n\tstd::vector<NotifyConnectedCallback> notify_connected_callbacks_;\n\tstd::vector<NotifyDisconnectedCallback> notify_disconnected_callbacks_;\n\tstd::mutex mutex_;\n\tstd::mutex map_mutex_;\n\tstd::map<SOCKET, std::weak_ptr<RtpConnection>> clients_;\n\n\tstd::shared_ptr<RtpConnection> multicast_client_;\n\tstd::shared_ptr<RtpConnection> multicast_v6_client_;\n\n\tbool is_multicast_ = false;\n\tstd::vector<uint16_t> multicast_port_;\n\tstd::string multicast_ip_;\n\tstd::string multicast_ip6_;\n\tstd::atomic_bool has_new_client_;\n\n\tstatic std::atomic_uint last_session_id_;\n\n\tstd::weak_ptr<TaskScheduler> task_scheduler_;\n};\n\nclass MulticastAddr {\npublic:\n\tstatic MulticastAddr &instance()\n\t{\n\t\tstatic MulticastAddr s_multi_addr;\n\t\treturn s_multi_addr;\n\t}\n\n\tstd::string GetAddr6()\n\t{\n\t\tstd::lock_guard<std::mutex> lock(mutex_);\n\t\tstd::random_device rd;\n\t\tchar addr6_str[INET6_ADDRSTRLEN];\n\t\tfor (int n = 0; n <= 10; n++) {\n\t\t\tin6_addr addr6{};\n\t\t\tuint8_t *addr_bytes = addr6.s6_addr;\n\t\t\taddr_bytes[0] = 0xff;\n\t\t\taddr_bytes[1] =        //flgs: |0|R|P|T|\n\t\t\t\t0x00 << 7 |          //0: reserved\n\t\t\t\t0x00 << 6 |          //R: default\n\t\t\t\t0x00 << 5 |          //P: default\n\t\t\t\t0x01 << 4;           //T: dynamic\n\t\t\taddr_bytes[1] |= 0x02; //scop: Link-Local scope\n\t\t\t//group id\n\t\t\tuint32_t group_id = rd();\n\t\t\tmemcpy(addr_bytes + 2, &group_id, 4);\n\t\t\tgroup_id = rd();\n\t\t\tmemcpy(addr_bytes + 6, &group_id, 4);\n\t\t\tgroup_id = rd();\n\t\t\tmemcpy(addr_bytes + 10, &group_id, 4);\n\t\t\tgroup_id = rd();\n\t\t\tmemcpy(addr_bytes + 14, &group_id, 2);\n\t\t\tinet_ntop(AF_INET6, &addr6, addr6_str,\n\t\t\t\t  INET6_ADDRSTRLEN);\n\t\t\tif (addrs_.find(addr6_str) == addrs_.end()) {\n\t\t\t\taddrs_.insert(addr6_str);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn addr6_str;\n\t}\n\n\tstd::string GetAddr6(const in6_addr local_addr6, uint8_t plen)\n\t{\n\t\tstd::lock_guard<std::mutex> lock(mutex_);\n\t\tstd::random_device rd;\n\t\tchar addr6_str[INET6_ADDRSTRLEN];\n\t\tfor (int n = 0; n <= 10; n++) {\n\t\t\tin6_addr addr6{};\n\t\t\tuint8_t *addr_bytes = addr6.s6_addr;\n\t\t\tconst uint8_t *local_addr_bytes = local_addr6.s6_addr;\n\t\t\taddr_bytes[0] = 0xff;\n\t\t\taddr_bytes[1] =          //flgs: |0|R|P|T|\n\t\t\t\t0x00 << 7 |            //0: reserved\n\t\t\t\t0x00 << 6 |            //R: default\n\t\t\t\t0x01 << 5 |            //P: RFC3306\n\t\t\t\t0x01 << 4;             //T: dynamic\n\t\t\taddr_bytes[1] |= 0x02;   //scop: Link-Local scope\n\t\t\taddr_bytes[2] = 0x01;    //reserved\n\t\t\taddr_bytes[3] = plen;    //plen\n\t\t\t//network prefix\n\t\t\tconst size_t plan_size = plen / 8;\n\t\t\tmemcpy(addr_bytes + 4, local_addr_bytes, plan_size);\n\t\t\tconst uint8_t plan_remainder = plen % 8;\n\t\t\tif (plan_remainder > 0)\n\t\t\t\taddr_bytes[4 + plan_size] = local_addr_bytes[plan_size] & (0xff << (8 - plan_remainder));\n\t\t\t//group id\n\t\t\tconst uint32_t group_id = rd();\n\t\t\tmemcpy(addr_bytes + 13, &group_id, sizeof(uint32_t));\n\t\t\tinet_ntop(AF_INET6, &addr6, addr6_str,\n\t\t\t\t  INET6_ADDRSTRLEN);\n\t\t\tif (addrs_.find(addr6_str) == addrs_.end()) {\n\t\t\t\taddrs_.insert(addr6_str);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn addr6_str;\n\t}\n\n\tstd::string GetAddr()\n\t{\n\t\tstd::lock_guard<std::mutex> lock(mutex_);\n\t\tstd::random_device rd;\n\n\t\tchar addr_str[INET_ADDRSTRLEN];\n\t\tfor (int n = 0; n <= 10; n++) {\n\t\t\tin_addr addr{};\n\t\t\tconstexpr uint32_t range = 0xE8FFFFFF - 0xE8000100;\n\t\t\taddr.s_addr = htonl(0xE8000100 + rd() % range);\n\t\t\tinet_ntop(AF_INET, &addr, addr_str, INET_ADDRSTRLEN);\n\t\t\tif (addrs_.find(addr_str) == addrs_.end()) {\n\t\t\t\taddrs_.insert(addr_str);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn addr_str;\n\t}\n\n\tvoid Release(const std::string &addr)\n\t{\n\t\tstd::lock_guard<std::mutex> lock(mutex_);\n\t\taddrs_.erase(addr);\n\t}\n\nprivate:\n\tstd::mutex mutex_;\n\tstd::unordered_set<std::string> addrs_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/MediaSource.h",
    "content": "// PHZ\n// 2018-6-8\n\n#ifndef XOP_MEDIA_SOURCE_H\n#define XOP_MEDIA_SOURCE_H\n\n#include \"media.h\"\n#include \"rtp.h\"\n#include <string>\n#include <cstdint>\n#include <functional>\n\nnamespace xop {\n\nclass MediaSource {\npublic:\n\tusing SendFrameCallback =\n\t\tstd::function<bool(MediaChannelId channel_id, RtpPacket pkt)>;\n\n\tMediaSource() = default;\n\tvirtual ~MediaSource() = default;\n\n\tvirtual MediaType GetMediaType() const { return media_type_; }\n\n\tvirtual std::string GetMediaDescription(uint16_t port = 0) = 0;\n\n\tvirtual std::string GetAttribute() = 0;\n\n\tvirtual bool HandleFrame(MediaChannelId channelId, AVFrame frame) = 0;\n\tvirtual void SetSendFrameCallback(const SendFrameCallback callback)\n\t{\n\t\tsend_frame_callback_ = callback;\n\t}\n\n\tvirtual uint32_t GetPayloadType() const { return payload_; }\n\n\tvirtual uint32_t GetClockRate() const { return clock_rate_; }\n\nprotected:\n\tMediaType media_type_ = MediaType::NONE;\n\tuint32_t payload_ = 0;\n\tuint32_t clock_rate_ = 0;\n\tSendFrameCallback send_frame_callback_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/Nal.cpp",
    "content": "#include \"Nal.h\"\n\nusing namespace std;\nusing namespace xop;\n\nbool NalHelper::NalUnitWhile(const uint8_t *data, size_t dataSize,\n\t\t\t     NalUnitWhileCallback callback)\n{\n\tif (dataSize == 0)\n\t\treturn true;\n\tconst uint8_t *end = data + dataSize;\n\tconst uint8_t *nal_start = FindStartCode(data, end);\n\n\twhile (true) {\n\t\twhile (nal_start < end && !*nal_start++)\n\t\t\t;\n\n\t\tif (nal_start == end)\n\t\t\tbreak;\n\n\t\tconst uint8_t *nalEnd = FindStartCode(nal_start, end);\n\n\t\tif (!callback(const_cast<uint8_t *>(nal_start),\n\t\t\t      nalEnd - nal_start))\n\t\t\treturn false;\n\n\t\tnal_start = nalEnd;\n\t}\n\treturn true;\n}\n\nuint32_t NalHelper::GetNalUnitCount(const uint8_t *data, size_t dataSize)\n{\n\tuint32_t count = 0;\n\tNalUnitWhile(data, dataSize, [&count](const uint8_t *, size_t) {\n\t\tcount++;\n\t\treturn true;\n\t});\n\treturn count;\n}\n\nconst uint8_t *NalHelper::FindStartCode(const uint8_t *p, const uint8_t *end)\n{\n\tconst uint8_t *out = FFmpegFindStartcodeInternal(p, end);\n\tif (p < out && out < end && !out[-1])\n\t\t--out;\n\treturn out;\n}\n\n/* NOTE: I noticed that FFmpeg does some unusual special handling of certain\n * scenarios that I was unaware of, so instead of just searching for {0, 0, 1}\n * we'll just use the code from FFmpeg - http://www.ffmpeg.org/ */\nconst uint8_t *NalHelper::FFmpegFindStartcodeInternal(const uint8_t *p,\n\t\t\t\t\t\t      const uint8_t *end)\n{\n\tconst uint8_t *a = p + 4 - (reinterpret_cast<intptr_t>(p) & 3);\n\n\tfor (end -= 3; p < a && p < end; p++) {\n\t\tif (p[0] == 0 && p[1] == 0 && p[2] == 1)\n\t\t\treturn p;\n\t}\n\n\tfor (end -= 3; p < end; p += 4) {\n\t\tconst uint32_t x = *reinterpret_cast<const uint32_t *>(p);\n\n\t\tif (x - 0x01010101 & ~x & 0x80808080) {\n\t\t\tif (p[1] == 0) {\n\t\t\t\tif (p[0] == 0 && p[2] == 1)\n\t\t\t\t\treturn p;\n\t\t\t\tif (p[2] == 0 && p[3] == 1)\n\t\t\t\t\treturn p + 1;\n\t\t\t}\n\n\t\t\tif (p[3] == 0) {\n\t\t\t\tif (p[2] == 0 && p[4] == 1)\n\t\t\t\t\treturn p + 2;\n\t\t\t\tif (p[4] == 0 && p[5] == 1)\n\t\t\t\t\treturn p + 3;\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (end += 3; p < end; p++) {\n\t\tif (p[0] == 0 && p[1] == 0 && p[2] == 1)\n\t\t\treturn p;\n\t}\n\n\treturn end + 3;\n}\n"
  },
  {
    "path": "rtsp-server/xop/Nal.h",
    "content": "#ifndef _XOP_NAL_H\n#define _XOP_NAL_H\n\n#include <memory>\n#include <vector>\n#include <functional>\n#include <type_traits>\n#include <cstddef>\n#include <cstdint>\n#include <cstring>\n\n#include \"NalUnit.h\"\n\nnamespace xop {\n\ntemplate<class T, class = std::enable_if_t<std::is_base_of_v<NalUnit, T>>>\nclass Nal {\npublic:\n\tNal(const std::vector<uint8_t> &data);\n\tNal(const uint8_t *data, size_t dataSize);\n\tsize_t GetSize() const;\n\tsize_t GetData(uint8_t **data) const;\n\tstd::vector<uint8_t> GetData() const;\n\tstd::shared_ptr<T> operator[](size_t index);\n\tsize_t GetCount();\n\tsize_t CopyData(uint8_t *start, size_t size) const;\n\tstd::shared_ptr<T> GetNalUnitByType(uint8_t nalUnitType);\n\nprivate:\n\tconst uint8_t *data_;\n\tsize_t data_size_;\n\tstd::vector<std::shared_ptr<T>> nal_unit_list_;\n};\n\nclass NalHelper {\npublic:\n\tusing NalUnitWhileCallback =\n\t\tstd::function<bool(const uint8_t *data, size_t dataSize)>;\n\tstatic bool NalUnitWhile(const uint8_t *data, size_t dataSize,\n\t\t\t\t NalUnitWhileCallback callback);\n\tstatic uint32_t GetNalUnitCount(const uint8_t *data, size_t dataSize);\n\tstatic const uint8_t *FindStartCode(const uint8_t *p,\n\t\t\t\t\t    const uint8_t *end);\n\nprivate:\n\tNalHelper() = default;\n\tstatic const uint8_t *FFmpegFindStartcodeInternal(const uint8_t *p,\n\t\t\t\t\t\t\t  const uint8_t *end);\n};\n\ntemplate<class T, class T0>\nNal<T, T0>::Nal(const std::vector<uint8_t> &data) : Nal(data.data(), data.size())\n{\n}\n\ntemplate<class T, class T0>\nNal<T, T0>::Nal(const uint8_t *data, size_t dataSize)\n\t: data_(data), data_size_(dataSize)\n{\n\tnal_unit_list_.resize(NalHelper::GetNalUnitCount(data, dataSize));\n\tauto it = nal_unit_list_.begin();\n\tauto end = nal_unit_list_.end();\n\tNalHelper::NalUnitWhile(\n\t\tdata, dataSize,\n\t\t[&it, end](const uint8_t *unitData, size_t unitDataSize) {\n\t\t\t*it = std::shared_ptr<T>(static_cast<T *>(\n\t\t\t\tT::GetNalUnit(unitData, unitDataSize)));\n\t\t\tif (it++ == end)\n\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t});\n}\n\ntemplate<class T, class T0> size_t Nal<T, T0>::GetSize() const\n{\n\treturn data_size_;\n}\n\ntemplate<class T, class T0> size_t Nal<T, T0>::GetData(uint8_t **data) const\n{\n\t*data = const_cast<uint8_t *>(data_);\n\treturn data_size_;\n}\n\ntemplate<class T, class T0> std::vector<uint8_t> Nal<T, T0>::GetData() const\n{\n\tuint8_t *data = nullptr;\n\tconst auto size = GetData(&data);\n\treturn std::vector<uint8_t>(data, data + size);\n}\n\ntemplate<class T, class T0>\nstd::shared_ptr<T> Nal<T, T0>::operator[](size_t index)\n{\n\treturn nal_unit_list_[index];\n}\n\ntemplate<class T, class T0> size_t Nal<T, T0>::GetCount()\n{\n\treturn nal_unit_list_.size();\n}\n\ntemplate<class T, class T0>\nsize_t Nal<T, T0>::CopyData(uint8_t *start, size_t size) const\n{\n\tif (size > data_size_)\n\t\tsize = data_size_;\n\tmemcpy(start, data_, size);\n\treturn size;\n}\n\ntemplate<class T, class T0>\nstd::shared_ptr<T> Nal<T, T0>::GetNalUnitByType(uint8_t nalUnitType)\n{\n\tfor (auto iter = nal_unit_list_.begin(); iter != nal_unit_list_.end();\n\t     ++iter) {\n\t\tif (iter->get()->GetType() == nalUnitType)\n\t\t\treturn *iter;\n\t}\n\treturn nullptr;\n}\n\n}\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/NalUnit.cpp",
    "content": "#include \"NalUnit.h\"\n#include <cstring>\n\nusing namespace xop;\nusing namespace std;\n\nNalUnit::NalUnit(const uint8_t *data, size_t dataSize)\n\t: data_(data), data_size_(dataSize)\n{\n}\n\nsize_t NalUnit::GetData(uint8_t **data) const\n{\n\t*data = const_cast<uint8_t *>(data_);\n\treturn data_size_;\n}\n\nvector<uint8_t> NalUnit::GetData() const\n{\n\tuint8_t *data = nullptr;\n\tconst auto size = GetData(&data);\n\treturn vector<uint8_t>(data, data + size);\n}\n\nsize_t NalUnit::CopyData(uint8_t *start, size_t size, size_t skip) const\n{\n\tif (skip > data_size_)\n\t\treturn 0;\n\tif (skip + size > data_size_)\n\t\tsize = data_size_ - skip;\n\tmemcpy(start, data_ + skip, size);\n\treturn size;\n}\n\nsize_t NalUnit::GetSize() const\n{\n\treturn data_size_;\n}\n\nstd::vector<uint8_t> NalUnit::GetHeader()\n{\n\tuint8_t *data = nullptr;\n\tconst auto size = GetHeader(&data);\n\treturn vector<uint8_t>(data, data + size);\n}\n\nstd::vector<uint8_t> NalUnit::GetBody()\n{\n\tuint8_t *data = nullptr;\n\tconst auto size = GetBody(&data);\n\treturn vector<uint8_t>(data, data + size);\n}\n\nNalUnit *NalUnit::GetNalUnit(const uint8_t *, size_t)\n{\n\treturn nullptr;\n}\n"
  },
  {
    "path": "rtsp-server/xop/NalUnit.h",
    "content": "#ifndef _XOP_NALUNIT_H\n#define _XOP_NALUNIT_H\n\n#include <cstddef>\n#include <cstdint>\n#include <vector>\n\nnamespace xop {\nclass NalUnit {\npublic:\n\tsize_t GetData(uint8_t **data) const;\n\tstd::vector<uint8_t> GetData() const;\n\tsize_t CopyData(uint8_t *start, size_t size, size_t skip = 0) const;\n\tsize_t GetSize() const;\n\tvirtual uint8_t GetType() = 0;\n\tvirtual size_t GetHeader(uint8_t **data) = 0;\n\tstd::vector<uint8_t> GetHeader();\n\tvirtual size_t CopyHeader(uint8_t *start, size_t size) = 0;\n\tvirtual size_t GetHeaderSize() = 0;\n\tvirtual size_t GetBody(uint8_t **data) = 0;\n\tstd::vector<uint8_t> GetBody();\n\tvirtual size_t CopyBody(uint8_t *start, size_t size, size_t skip = 0) = 0;\n\tvirtual size_t GetBodySize() = 0;\n\tvirtual bool IsIdrFrame() = 0;\n\tvirtual bool IsFrame() = 0;\n\tstatic NalUnit* GetNalUnit(const uint8_t *data, size_t dataSize);\n\nprotected:\n\tNalUnit(const uint8_t *data, size_t dataSize);\n\nprivate:\n\tconst uint8_t *data_;\n\tsize_t data_size_;\n};\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/RtpConnection.cpp",
    "content": "// PHZ\n// 2018-9-30\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#include \"RtpConnection.h\"\n#include \"RtspConnection.h\"\n#include \"net/SocketUtil.h\"\n#include \"net/TaskScheduler.h\"\n\nusing namespace std;\nusing namespace xop;\n\nRtpConnection::RtpConnection(std::weak_ptr<RtspConnection> rtsp_connection,\n\tconst uint8_t max_channel_count)\n\t: RtpConnection(max_channel_count,\n\t\t\trtsp_connection.lock()->GetTaskScheduler(),\n\t\t\trtsp_connection.lock()->IsIpv6())\n{\n\trtsp_connection_ = rtsp_connection;\n}\n\nRtpConnection::RtpConnection(const uint8_t max_channel_count,\n\t\t\t     std::weak_ptr<TaskScheduler> task_scheduler,\n\t\t\t     bool ipv6)\n\t: max_channel_count_(max_channel_count),\n\t  task_scheduler_(task_scheduler),\n\t  transport_mode_(TransportMode::NONE),\n\t  local_rtp_port_(max_channel_count),\n\t  local_rtcp_port_(max_channel_count),\n\t  rtpfd_(max_channel_count, 0),\n\t  rtcpfd_(max_channel_count, 0),\n\t  peer_rtp_addr_(max_channel_count),\n\t  peer_rtcp_sddr_(max_channel_count),\n\t  media_channel_info_(max_channel_count),\n\t  ipv6_(ipv6)\n{\n\tstd::random_device rd;\n\n\tfor (uint8_t chn = 0; chn < max_channel_count; chn++) {\n\t\tmemset(&media_channel_info_[chn], 0,\n\t\t       sizeof media_channel_info_[chn]);\n\t\tmedia_channel_info_[chn].rtp_header.version = RTP_VERSION;\n\t\tmedia_channel_info_[chn].packet_seq = rd() & 0xffff;\n\t\tmedia_channel_info_[chn].rtp_header.seq = 0; //htons(1);\n\t\tmedia_channel_info_[chn].rtp_header.ts = htonl(rd());\n\t\tmedia_channel_info_[chn].rtp_header.ssrc = htonl(rd());\n\t}\n}\n\nRtpConnection::~RtpConnection()\n{\n\tfor (uint8_t chn = 0; chn < max_channel_count_; chn++) {\n\t\tif (rtpfd_[chn] > 0) {\n\t\t\tSocketUtil::Close(rtpfd_[chn]);\n\t\t}\n\n\t\tif (rtcpfd_[chn] > 0) {\n\t\t\tSocketUtil::Close(rtcpfd_[chn]);\n\t\t}\n\t}\n}\n\nint RtpConnection::GetId() const\n{\n\treturn task_scheduler_.lock()->GetId();\n}\n\nbool RtpConnection::SetupRtpOverTcp(MediaChannelId channel_id,\n\t\t\t\t    const uint8_t rtp_channel,\n\t\t\t\t    const uint8_t rtcp_channel)\n{\n\tconst auto conn = rtsp_connection_.lock();\n\tif (!conn) {\n\t\treturn false;\n\t}\n\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].rtp_channel =\n\t\trtp_channel;\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].rtcp_channel =\n\t\trtcp_channel;\n\trtpfd_[static_cast<uint8_t>(channel_id)] = conn->GetSocket();\n\trtcpfd_[static_cast<uint8_t>(channel_id)] = conn->GetSocket();\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].is_setup = true;\n\ttransport_mode_ = TransportMode::RTP_OVER_TCP;\n\n\treturn true;\n}\n\nbool RtpConnection::SetupRtpOverUdp(MediaChannelId channel_id,\n\t\t\t\t    const uint16_t rtp_port,\n\t\t\t\t    const uint16_t rtcp_port)\n{\n\tconst auto conn = rtsp_connection_.lock();\n\tif (!conn) {\n\t\treturn false;\n\t}\n\n\tif (ipv6_ ? SocketUtil::GetPeerAddr6(conn->GetSocket(), &peer_addr_)\n\t\t  : SocketUtil::GetPeerAddr(\n\t\t\t    conn->GetSocket(),\n\t\t\t    reinterpret_cast<sockaddr_in *>(&peer_addr_)) < 0) {\n\t\treturn false;\n\t}\n\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].rtp_port =\n\t\trtp_port;\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].rtcp_port =\n\t\trtcp_port;\n\n\tstd::random_device rd;\n\tfor (int n = 0; n <= 10; n++) {\n\t\tif (n == 10) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlocal_rtp_port_[static_cast<uint8_t>(channel_id)] = rd() &\n\t\t\t\t\t\t\t\t    0xfffe;\n\t\tlocal_rtcp_port_[static_cast<uint8_t>(channel_id)] =\n\t\t\tlocal_rtp_port_[static_cast<uint8_t>(channel_id)] + 1;\n\n\t\trtpfd_[static_cast<uint8_t>(channel_id)] =\n\t\t\tsocket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);\n\t\tif (!SocketUtil::Bind(\n\t\t\t    rtpfd_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_ ? \"::0\" : \"0.0.0.0\", //TODO: Bing all address?\n\t\t\t    local_rtp_port_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_)) {\n\t\t\tSocketUtil::Close(\n\t\t\t\trtpfd_[static_cast<uint8_t>(channel_id)]);\n\t\t\tcontinue;\n\t\t}\n\n\t\trtcpfd_[static_cast<uint8_t>(channel_id)] =\n\t\t\tsocket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);\n\t\tif (!SocketUtil::Bind(\n\t\t\t    rtcpfd_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_ ? \"::0\" : \"0.0.0.0\", //TODO: Bing all address?\n\t\t\t    local_rtcp_port_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_)) {\n\t\t\tSocketUtil::Close(\n\t\t\t\trtpfd_[static_cast<uint8_t>(channel_id)]);\n\t\t\tSocketUtil::Close(\n\t\t\t\trtcpfd_[static_cast<uint8_t>(channel_id)]);\n\t\t\tcontinue;\n\t\t}\n\t\tbreak;\n\t}\n\n\tSocketUtil::SetSendBufSize(rtpfd_[static_cast<uint8_t>(channel_id)],\n\t\t\t\t   50 * 1024);\n\n\tif (ipv6_) {\n\t\tconst auto peer_addr = &peer_addr_;\n\t\tconst auto peer_rtp_addr =\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)];\n\t\tconst auto peer_rtcp_sddr =\n\t\t\t&peer_rtcp_sddr_[static_cast<uint8_t>(channel_id)];\n\t\tpeer_rtp_addr->sin6_family = AF_INET6;\n\t\tpeer_rtp_addr->sin6_addr = peer_addr->sin6_addr;\n\t\tpeer_rtp_addr->sin6_port = htons(\n\t\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.rtp_port);\n\n\t\tpeer_rtcp_sddr->sin6_family = AF_INET6;\n\t\tpeer_rtcp_sddr->sin6_addr = peer_addr->sin6_addr;\n\t\tpeer_rtcp_sddr->sin6_port = htons(\n\t\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.rtcp_port);\n\t} else {\n\t\tconst auto peer_addr =\n\t\t\treinterpret_cast<sockaddr_in *>(&peer_addr_);\n\t\tconst auto peer_rtp_addr = reinterpret_cast<sockaddr_in *>(\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)]);\n\t\tconst auto peer_rtcp_sddr = reinterpret_cast<sockaddr_in *>(\n\t\t\t&peer_rtcp_sddr_[static_cast<uint8_t>(channel_id)]);\n\t\tpeer_rtp_addr->sin_family = AF_INET;\n\t\tpeer_rtp_addr->sin_addr = peer_addr->sin_addr;\n\t\tpeer_rtp_addr->sin_port = htons(\n\t\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.rtp_port);\n\n\t\tpeer_rtcp_sddr->sin_family = AF_INET;\n\t\tpeer_rtcp_sddr->sin_addr = peer_addr->sin_addr;\n\t\tpeer_rtcp_sddr->sin_port = htons(\n\t\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.rtcp_port);\n\t}\n\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].is_setup = true;\n\ttransport_mode_ = TransportMode::RTP_OVER_UDP;\n\n\treturn true;\n}\n\nbool RtpConnection::SetupRtpOverMulticast(MediaChannelId channel_id,\n\t\t\t\t\t  const std::string &ip,\n\t\t\t\t\t  const uint16_t port)\n{\n\tstd::random_device rd;\n\tfor (int n = 0; n <= 10; n++) {\n\t\tif (n == 10) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlocal_rtp_port_[static_cast<uint8_t>(channel_id)] = rd() &\n\t\t\t\t\t\t\t\t    0xfffe;\n\t\trtpfd_[static_cast<uint8_t>(channel_id)] =\n\t\t\t::socket(ipv6_ ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);\n\t\tif (!SocketUtil::Bind(\n\t\t\t    rtpfd_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_ ? \"::0\" : \"0.0.0.0\",\n\t\t\t    local_rtp_port_[static_cast<uint8_t>(channel_id)],\n\t\t\t    ipv6_)) {\n\t\t\tSocketUtil::Close(\n\t\t\t\trtpfd_[static_cast<uint8_t>(channel_id)]);\n\t\t\tcontinue;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].rtp_port = port;\n\n\tif (ipv6_) {\n\t\tconst auto peer_rtp_addr =\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)];\n\t\tpeer_rtp_addr->sin6_family = AF_INET6;\n\t\tpeer_rtp_addr->sin6_port = htons(port);\n\t\tinet_pton(AF_INET6, ip.c_str(), &peer_rtp_addr->sin6_addr);\n\t} else {\n\t\tconst auto peer_rtp_addr = reinterpret_cast<sockaddr_in *>(\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)]);\n\t\tpeer_rtp_addr->sin_family = AF_INET;\n\t\tpeer_rtp_addr->sin_port = htons(port);\n\t\tinet_pton(AF_INET, ip.c_str(), &peer_rtp_addr->sin_addr);\n\t}\n\n\tmedia_channel_info_[static_cast<uint8_t>(channel_id)].is_setup = true;\n\ttransport_mode_ = TransportMode::RTP_OVER_MULTICAST;\n\tis_multicast_ = true;\n\treturn true;\n}\n\nvoid RtpConnection::Play()\n{\n\tfor (uint8_t chn = 0; chn < max_channel_count_; chn++) {\n\t\tif (media_channel_info_[chn].is_setup) {\n\t\t\tmedia_channel_info_[chn].is_play = true;\n\t\t}\n\t}\n}\n\nvoid RtpConnection::Record()\n{\n\tfor (uint8_t chn = 0; chn < max_channel_count_; chn++) {\n\t\tif (media_channel_info_[chn].is_setup) {\n\t\t\tmedia_channel_info_[chn].is_record = true;\n\t\t\tmedia_channel_info_[chn].is_play = true;\n\t\t}\n\t}\n}\n\nvoid RtpConnection::Teardown()\n{\n\tif (!is_closed_ &&\n\t    transport_mode_ != TransportMode::RTP_OVER_MULTICAST) {\n\t\tis_closed_ = true;\n\t\tfor (uint8_t chn = 0; chn < max_channel_count_; chn++) {\n\t\t\tmedia_channel_info_[chn].is_play = false;\n\t\t\tmedia_channel_info_[chn].is_record = false;\n\t\t}\n\t}\n}\n\nstring RtpConnection::GetMulticastIp(MediaChannelId channel_id)\n{\n\tif (ipv6_) {\n\t\tconst auto peer_rtp_addr =\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)];\n\t\tchar str[INET6_ADDRSTRLEN] = \"::0\";\n\t\tinet_ntop(AF_INET6, &peer_rtp_addr->sin6_addr, str, sizeof str);\n\t\treturn str;\n\t} else {\n\t\tconst auto peer_rtp_addr = reinterpret_cast<sockaddr_in *>(\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)]);\n\t\tchar str[INET_ADDRSTRLEN] = \"0.0.0.0\";\n\t\tinet_ntop(AF_INET, &peer_rtp_addr->sin_addr, str, sizeof str);\n\t\treturn str;\n\t}\n}\n\nstring RtpConnection::GetRtpInfo(const std::string &rtsp_url) const\n{\n\tchar buf[2048] = {0};\n\tsnprintf(buf, 1024, \"RTP-Info: \");\n\n\tint num_channel = 0;\n\n\tconst auto time_point = chrono::time_point_cast<chrono::milliseconds>(\n\t\tchrono::steady_clock::now());\n\tconst auto ts = time_point.time_since_epoch().count();\n\tfor (int chn = 0; chn < max_channel_count_; chn++) {\n\t\tconst auto rtpTime = static_cast<uint32_t>(\n\t\t\tts * media_channel_info_[chn].clock_rate / 1000);\n\t\tif (media_channel_info_[chn].is_setup) {\n\t\t\tif (num_channel != 0) {\n\t\t\t\tsnprintf(buf + strlen(buf),\n\t\t\t\t\t sizeof(buf) - strlen(buf), \",\");\n\t\t\t}\n\n\t\t\tsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),\n\t\t\t\t \"url=%s/track%d;seq=0;rtptime=%u\",\n\t\t\t\t rtsp_url.c_str(), chn, rtpTime);\n\t\t\tnum_channel++;\n\t\t}\n\t}\n\n\treturn buf;\n}\n\nvoid RtpConnection::SetFrameType(const FrameType frame_type)\n{\n\tframe_type_ = frame_type;\n\tif (!has_key_frame_ && (frame_type == FrameType::NONE ||\n\t\t\t\tframe_type == FrameType::VIDEO_FRAME_IDR)) {\n\t\thas_key_frame_ = true;\n\t}\n}\n\nvoid RtpConnection::SetRtpHeader(MediaChannelId channel_id,\n\t\t\t\t const RtpPacket &pkt)\n{\n\tif ((media_channel_info_[static_cast<uint8_t>(channel_id)].is_play ||\n\t     media_channel_info_[static_cast<uint8_t>(channel_id)].is_record) &&\n\t    has_key_frame_) {\n\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.rtp_header.marker = pkt.last;\n\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.rtp_header.ts = htonl(pkt.timestamp);\n\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.rtp_header.seq = htons(\n\t\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.packet_seq++);\n\t\tmemcpy(pkt.data.get() + 4,\n\t\t       &media_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t\t.rtp_header,\n\t\t       RTP_HEADER_SIZE);\n\t}\n}\n\nint RtpConnection::SendRtpPacket(MediaChannelId channel_id,\n\t\t\t\t const RtpPacket &pkt)\n{\n\tif (is_closed_) {\n\t\treturn -1;\n\t}\n\n\tconst bool ret = task_scheduler_.lock()->AddTriggerEvent([this, channel_id,\n\t\t\t\t\t\t\t   pkt] {\n\t\tthis->SetFrameType(pkt.type);\n\t\tthis->SetRtpHeader(channel_id, pkt);\n\t\tif ((media_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t     .is_play ||\n\t\t     media_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t     .is_record) &&\n\t\t    has_key_frame_) {\n\t\t\tif (transport_mode_ == TransportMode::RTP_OVER_TCP) {\n\t\t\t\tSendRtpOverTcp(channel_id, pkt);\n\t\t\t} else if (transport_mode_ ==\n\t\t\t\t\t   TransportMode::RTP_OVER_UDP ||\n\t\t\t\t   transport_mode_ ==\n\t\t\t\t\t   TransportMode::RTP_OVER_MULTICAST) {\n\t\t\t\tSendRtpOverUdp(channel_id, pkt);\n\t\t\t}\n\n\t\t\t//media_channel_info_[channel_id].octetCount  += pkt.size;\n\t\t\t//media_channel_info_[channel_id].packetCount += 1;\n\t\t}\n\t});\n\n\treturn ret ? 0 : -1;\n}\n\nint RtpConnection::SendRtpOverTcp(MediaChannelId channel_id,\n\t\t\t\t  const RtpPacket &pkt) const\n{\n\tconst auto conn = rtsp_connection_.lock();\n\tif (!conn) {\n\t\treturn -1;\n\t}\n\n\tuint8_t *rtpPktPtr = pkt.data.get();\n\trtpPktPtr[0] = '$';\n\trtpPktPtr[1] = media_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t       .rtp_channel;\n\trtpPktPtr[2] = static_cast<uint8_t>((pkt.size - 4 & 0xFF00) >> 8);\n\trtpPktPtr[3] = static_cast<uint8_t>(pkt.size - 4 & 0xFF);\n\n\tconn->Send(reinterpret_cast<char *>(rtpPktPtr), pkt.size);\n\treturn pkt.size;\n}\n\nint RtpConnection::SendRtpOverUdp(MediaChannelId channel_id,\n\t\t\t\t  const RtpPacket &pkt)\n{\n\tconst int ret = sendto(\n\t\trtpfd_[static_cast<uint8_t>(channel_id)],\n\t\treinterpret_cast<const char *>(pkt.data.get()) + 4,\n\t\tpkt.size - 4, 0,\n\t\treinterpret_cast<sockaddr *>(\n\t\t\t&peer_rtp_addr_[static_cast<uint8_t>(channel_id)]),\n\t\tipv6_ ? sizeof(struct sockaddr_in6)\n\t\t      : sizeof(struct sockaddr_in));\n\n\tif (ret < 0) {\n\t\tTeardown();\n\t\treturn -1;\n\t}\n\n\treturn ret;\n}\n"
  },
  {
    "path": "rtsp-server/xop/RtpConnection.h",
    "content": "// PHZ\n// 2018-6-8\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#ifndef XOP_RTP_CONNECTION_H\n#define XOP_RTP_CONNECTION_H\n\n#include <cstdint>\n#include <vector>\n#include <string>\n#include <memory>\n#include <random>\n#include \"rtp.h\"\n#include \"media.h\"\n#include \"net/Socket.h\"\n\nnamespace xop {\n\nclass RtspConnection;\nclass TaskScheduler;\n\nclass RtpConnection {\npublic:\n\tRtpConnection(std::weak_ptr<RtspConnection> rtsp_connection,\n\t\t      uint8_t max_channel_count);\n\n\tRtpConnection(uint8_t max_channel_count,\n\t\t      std::weak_ptr<TaskScheduler> task_scheduler,\n\t\t      bool ipv6 = false);\n\n\tvirtual ~RtpConnection();\n\n\tvoid SetClockRate(MediaChannelId channel_id, const uint32_t clock_rate)\n\t{\n\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.clock_rate = clock_rate;\n\t}\n\n\tvoid SetPayloadType(MediaChannelId channel_id, const uint32_t payload)\n\t{\n\t\tmedia_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.rtp_header.payload = payload;\n\t}\n\n\tbool SetupRtpOverTcp(MediaChannelId channel_id, uint8_t rtp_channel,\n\t\t\t     uint8_t rtcp_channel);\n\tbool SetupRtpOverUdp(MediaChannelId channel_id, uint16_t rtp_port,\n\t\t\t     uint16_t rtcp_port);\n\tbool SetupRtpOverMulticast(MediaChannelId channel_id,\n\t\t\t\t   const std::string &ip, uint16_t port);\n\n\tuint16_t GetRtpSessionId() const\n\t{\n\t\treturn static_cast<uint16_t>(reinterpret_cast<size_t>(this));\n\t}\n\n\tuint16_t GetRtpPort(MediaChannelId channel_id) const\n\t{\n\t\treturn local_rtp_port_[static_cast<uint8_t>(channel_id)];\n\t}\n\n\tuint16_t GetRtcpPort(MediaChannelId channel_id) const\n\t{\n\t\treturn local_rtcp_port_[static_cast<uint8_t>(channel_id)];\n\t}\n\n\tSOCKET GetRtcpfd(MediaChannelId channel_id) const\n\t{\n\t\treturn rtcpfd_[static_cast<uint8_t>(channel_id)];\n\t}\n\n\tbool IsMulticast() const { return is_multicast_; }\n\n\tbool IsSetup(MediaChannelId channel_id) const\n\t{\n\t\treturn media_channel_info_[static_cast<uint8_t>(channel_id)]\n\t\t\t.is_setup;\n\t}\n\n\tstd::string GetMulticastIp(MediaChannelId channel_id);\n\n\tvoid Play();\n\tvoid Record();\n\tvoid Teardown();\n\n\tstd::string GetRtpInfo(const std::string &rtsp_url) const;\n\tint SendRtpPacket(MediaChannelId channel_id, const RtpPacket &pkt);\n\n\tbool IsClosed() const { return is_closed_; }\n\n\tint GetId() const;\n\n\tbool HasKeyFrame() const { return has_key_frame_; }\n\nprivate:\n\tfriend class RtspConnection;\n\tfriend class MediaSession;\n\tvoid SetFrameType(FrameType frameType = FrameType::NONE);\n\tvoid SetRtpHeader(MediaChannelId channel_id, const RtpPacket &pkt);\n\tint SendRtpOverTcp(MediaChannelId channel_id,\n\t\t\t   const RtpPacket &pkt) const;\n\tint SendRtpOverUdp(MediaChannelId channel_id, const RtpPacket &pkt);\n\n\tuint8_t max_channel_count_ = 0;\n\n\tstd::weak_ptr<RtspConnection> rtsp_connection_;\n\tstd::weak_ptr<TaskScheduler> task_scheduler_;\n\n\tTransportMode transport_mode_;\n\tbool is_multicast_ = false;\n\n\tbool is_closed_ = false;\n\tbool has_key_frame_ = false;\n\n\tFrameType frame_type_ = FrameType::NONE;\n\tstd::vector<uint16_t> local_rtp_port_;\n\tstd::vector<uint16_t> local_rtcp_port_;\n\tstd::vector<SOCKET> rtpfd_;\n\tstd::vector<SOCKET> rtcpfd_;\n\n\tsockaddr_in6 peer_addr_{};\n\tstd::vector<sockaddr_in6> peer_rtp_addr_;\n\tstd::vector<sockaddr_in6> peer_rtcp_sddr_;\n\tstd::vector<MediaChannelInfo> media_channel_info_;\n\n\tbool ipv6_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/RtspConnection.cpp",
    "content": "// PHZ\n// 2018-6-10\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#include <utility>\n#include \"RtspConnection.h\"\n#include \"RtspServer.h\"\n#include \"MediaSession.h\"\n#include \"MediaSource.h\"\n#include \"net/SocketUtil.h\"\n\n#define USER_AGENT \"-_-\"\n#define RTSP_DEBUG 0\n#define MAX_RTSP_MESSAGE_SIZE 2048\n\nusing namespace xop;\nusing namespace std;\n\nRtspConnection::RtspConnection(const SOCKET sockfd,\n                               std::shared_ptr<TaskScheduler> task_scheduler,\n                               const std::shared_ptr<Rtsp> &rtsp)\n\t: TcpConnection(sockfd, std::move(task_scheduler)),\n\t  alive_count_(1),\n\t  rtsp_(rtsp)\n\t  //, rtp_channel_(new Channel(sockfd))\n\t  ,\n\t  rtsp_request_(new RtspRequest),\n\t  rtsp_response_(new RtspResponse)\n{\n\tthis->SetReadCallback([this](Weak /*conn*/,\n\t                             BufferReader &buffer) { return this->OnRead(buffer); });\n\n\tthis->SetCloseCallback([this](const Weak & /*conn*/) {\n\t\tthis->OnClose();\n\t});\n\n\t/*rtp_channel_->SetReadCallback([this]() { this->HandleRead(); });\n\trtp_channel_->SetWriteCallback([this]() { this->HandleWrite(); });\n\trtp_channel_->SetCloseCallback([this]() { this->HandleClose(); });\n\trtp_channel_->SetErrorCallback([this]() { this->HandleError(); });*/\n\n\tif (rtsp->has_auth_info_) {\n\t\thas_auth_ = false;\n\t\tauth_info_.reset(new DigestAuthentication(\n\t\t\trtsp->realm_, rtsp->username_, rtsp->password_));\n\t}\n}\n\nRtspConnection::~RtspConnection() = default;\n\nbool RtspConnection::OnRead(BufferReader &buffer)\n{\n\tKeepAlive();\n\n\tif (const size_t size = buffer.ReadableBytes(); size <= 0) {\n\t\treturn false; //close\n\t}\n\n\tif (conn_mode_ == ConnectionMode::RTSP_SERVER) {\n\t\tif (!HandleRtspRequest(buffer)) {\n\t\t\treturn false;\n\t\t}\n\t} else if (conn_mode_ == ConnectionMode::RTSP_PUSHER) {\n\t\tif (!HandleRtspResponse(buffer)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (buffer.ReadableBytes() > MAX_RTSP_MESSAGE_SIZE) {\n\t\tbuffer.RetrieveAll();\n\t}\n\n\treturn true;\n}\n\nvoid RtspConnection::OnClose()\n{\n\tif (session_id_ != 0) {\n\t\tif (const auto rtsp = rtsp_.lock()) {\n\t\t\tif (const MediaSession::Ptr media_session =\n\t\t\t\t    rtsp->LookMediaSession(session_id_)) {\n\t\t\t\tmedia_session->RemoveClient(this->GetSocket(),\n\t\t\t\t\t\t\t    GetIp(), GetPort());\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (auto iter = rtcp_channels_.begin();\n\t     iter != rtcp_channels_.end();) {\n\t\tif (auto channel = iter->second; !channel->IsNoneEvent()) {\n\t\t\tGetTaskScheduler()->RemoveChannel(channel);\n\t\t\trtcp_channels_.erase(iter++);\n\t\t} else\n\t\t\t++iter;\n\t}\n}\n\nbool RtspConnection::HandleRtspRequest(BufferReader &buffer)\n{\n#if RTSP_DEBUG\n\tstring str(buffer.Peek(), buffer.ReadableBytes());\n\tif (str.find(\"rtsp\") != string::npos ||\n\t    str.find(\"RTSP\") != string::npos) {\n\t\tstd::cout << str << std::endl;\n\t}\n#endif\n\n\tif (rtsp_request_->ParseRequest(&buffer)) {\n\t\tconst RtspRequest::Method method = rtsp_request_->GetMethod();\n\t\tif (method == RtspRequest::Method::RTCP) {\n\t\t\tHandleRtcp(buffer);\n\t\t\treturn true;\n\t\t}\n\t\tif (!rtsp_request_->GotAll()) {\n\t\t\treturn true;\n\t\t}\n\n\t\tswitch (method) {\n\t\tcase RtspRequest::Method::OPTIONS:\n\t\t\tHandleCmdOption();\n\t\t\tbreak;\n\t\tcase RtspRequest::Method::DESCRIBE:\n\t\t\tHandleCmdDescribe();\n\t\t\tbreak;\n\t\tcase RtspRequest::Method::SETUP:\n\t\t\tHandleCmdSetup();\n\t\t\tbreak;\n\t\tcase RtspRequest::Method::PLAY:\n\t\t\tHandleCmdPlay();\n\t\t\tbreak;\n\t\tcase RtspRequest::Method::TEARDOWN:\n\t\t\tHandleCmdTeardown();\n\t\t\tbreak;\n\t\tcase RtspRequest::Method::GET_PARAMETER:\n\t\t\tHandleCmdGetParamter();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\n\t\tif (rtsp_request_->GotAll()) {\n\t\t\trtsp_request_->Reset();\n\t\t}\n\t} else {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool RtspConnection::HandleRtspResponse(BufferReader &buffer)\n{\n#if RTSP_DEBUG\n\tstring str(buffer.Peek(), buffer.ReadableBytes());\n\tif (str.find(\"rtsp\") != string::npos ||\n\t    str.find(\"RTSP\") != string::npos) {\n\t\tcout << str << endl;\n\t}\n#endif\n\n\tif (rtsp_response_->ParseResponse(&buffer)) {\n\t\tswitch (rtsp_response_->GetMethod()) {\n\t\tcase RtspResponse::Method::OPTIONS:\n\t\t\tif (conn_mode_ == ConnectionMode::RTSP_PUSHER) {\n\t\t\t\tSendAnnounce();\n\t\t\t}\n\t\t\tbreak;\n\t\tcase RtspResponse::Method::ANNOUNCE:\n\t\tcase RtspResponse::Method::DESCRIBE:\n\t\t\tSendSetup();\n\t\t\tbreak;\n\t\tcase RtspResponse::Method::SETUP:\n\t\t\tSendSetup();\n\t\t\tbreak;\n\t\tcase RtspResponse::Method::RECORD:\n\t\t\tHandleRecord();\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t\t}\n\t} else {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid RtspConnection::SendRtspMessage(const std::shared_ptr<char> buf,\n\t\t\t\t     const uint32_t size)\n{\n#if RTSP_DEBUG\n\tcout << buf.get() << endl;\n#endif\n\n\tthis->Send(buf, size);\n\treturn;\n}\n\nvoid RtspConnection::HandleRtcp(BufferReader &buffer)\n{\n\tif (const char *peek = buffer.Peek();\n\t    peek[0] == '$' && buffer.ReadableBytes() > 4) {\n\t\tif (const size_t pkt_size = peek[2] << 8 | peek[3];\n\t\t    pkt_size + 4 >= buffer.ReadableBytes()) {\n\t\t\tbuffer.Retrieve(pkt_size + 4);\n\t\t}\n\t}\n}\n\nvoid RtspConnection::HandleRtcp(const SOCKET sockfd)\n{\n\tif (char buf[1024] = {0}; recv(sockfd, buf, 1024, 0) > 0) {\n\t\tKeepAlive();\n\t}\n}\n\nvoid RtspConnection::HandleCmdOption()\n{\n\tconst std::shared_ptr<char> res(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size = rtsp_request_->BuildOptionRes(res.get(), 2048);\n\tthis->SendRtspMessage(res, size);\n}\n\nvoid RtspConnection::HandleCmdDescribe()\n{\n\tif (auth_info_ != nullptr && !HandleAuthentication()) {\n\t\treturn;\n\t}\n\n\tint size;\n\tconst std::shared_ptr<char> res(new char[4096],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tMediaSession::Ptr media_session = nullptr;\n\n\tconst auto rtsp = rtsp_.lock();\n\tif (rtsp) {\n\t\tmedia_session = rtsp->LookMediaSession(\n\t\t\trtsp_request_->GetRtspUrlSuffix());\n\t}\n\n\tif (!rtp_conn_ && media_session) {\n\t\tif (media_session->IsMulticast())\n\t\t\trtp_conn_ = media_session->GetMulticastRtpConnection(\n\t\t\t\tIsIpv6());\n\t\telse\n\t\t\trtp_conn_ = make_shared<RtpConnection>(\n\t\t\t\tstd::dynamic_pointer_cast<RtspConnection>(\n\t\t\t\t\tshared_from_this()),\n\t\t\t\tmedia_session->GetMaxChannelCount());\n\t}\n\n\tif (!rtsp || !media_session) {\n\t\tsize = rtsp_request_->BuildNotFoundRes(res.get(), 4096);\n\t} else {\n\t\tsession_id_ = media_session->GetMediaSessionId();\n\t\tmedia_session->AddClient(this->GetSocket(), rtp_conn_, GetIp(),\n\t\t\t\t\t GetPort());\n\t\tfor (uint16_t chn = 0;\n\t\t     chn < media_session->GetMaxChannelCount(); chn++) {\n\t\t\tif (MediaSource *source = media_session->GetMediaSource(\n\t\t\t\t    static_cast<MediaChannelId>(chn));\n\t\t\t    source != nullptr) {\n\t\t\t\trtp_conn_->SetClockRate(\n\t\t\t\t\tstatic_cast<MediaChannelId>(chn),\n\t\t\t\t\tsource->GetClockRate());\n\t\t\t\trtp_conn_->SetPayloadType(\n\t\t\t\t\tstatic_cast<MediaChannelId>(chn),\n\t\t\t\t\tsource->GetPayloadType());\n\t\t\t}\n\t\t}\n\n\t\tconst auto sdp = media_session->GetSdpMessage(\n\t\t\tSocketUtil::GetSocketIp(GetSocket(), IsIpv6()),\n\t\t\trtsp->GetVersion(), IsIpv6());\n\t\tif (sdp.empty()) {\n\t\t\tsize = rtsp_request_->BuildServerErrorRes(res.get(),\n\t\t\t\t\t\t\t\t  4096);\n\t\t} else {\n\t\t\tsize = rtsp_request_->BuildDescribeRes(res.get(), 4096,\n\t\t\t\t\t\t\t       sdp.c_str());\n\t\t}\n\t}\n\n\tSendRtspMessage(res, size);\n}\n\nvoid RtspConnection::HandleCmdSetup()\n{\n\tif (auth_info_ != nullptr && !HandleAuthentication()) {\n\t\treturn;\n\t}\n\n\tint size;\n\tconst std::shared_ptr<char> res(new char[4096],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tMediaChannelId channel_id = rtsp_request_->GetChannelId();\n\tMediaSession::Ptr media_session = nullptr;\n\n\tconst auto rtsp = rtsp_.lock();\n\tif (rtsp && session_id_) {\n\t\tmedia_session = rtsp->LookMediaSession(session_id_);\n\t}\n\n\tif (!rtsp || !media_session) {\n\t\tgoto server_error;\n\t}\n\n\tif (media_session->IsMulticast()) {\n\t\tif (rtsp_request_->GetTransportMode() ==\n\t\t    TransportMode::RTP_OVER_MULTICAST) {\n\t\t\tconst std::string multicast_ip =\n\t\t\t\tmedia_session->GetMulticastIp(IsIpv6());\n\t\t\tconst uint16_t port =\n\t\t\t\tmedia_session->GetMulticastPort(channel_id);\n\t\t\tconst uint16_t session_id =\n\t\t\t\trtp_conn_->GetRtpSessionId();\n\t\t\t/*if (!rtp_conn_->SetupRtpOverMulticast(\n\t\t\t\t    channel_id, multicast_ip, port)) {\n\t\t\t\tgoto server_error;\n\t\t\t}*/\n\t\t\tif (!rtp_conn_->IsSetup(channel_id))\n\t\t\t\tgoto server_error;\n\n\t\t\tsize = rtsp_request_->BuildSetupMulticastRes(\n\t\t\t\tres.get(), 4096, multicast_ip.c_str(), port,\n\t\t\t\tsession_id);\n\t\t} else {\n\t\t\tgoto transport_unsupport;\n\t\t}\n\t} else {\n\t\tif (rtsp_request_->GetTransportMode() ==\n\t\t    TransportMode::RTP_OVER_TCP) {\n\t\t\tconst uint8_t rtp_channel =\n\t\t\t\trtsp_request_->GetRtpChannel();\n\t\t\tconst uint8_t rtcp_channel =\n\t\t\t\trtsp_request_->GetRtcpChannel();\n\t\t\tconst uint16_t session_id =\n\t\t\t\trtp_conn_->GetRtpSessionId();\n\n\t\t\trtp_conn_->SetupRtpOverTcp(channel_id, rtp_channel,\n\t\t\t\t\t\t   rtcp_channel);\n\t\t\tsize = rtsp_request_->BuildSetupTcpRes(res.get(), 4096,\n\t\t\t\t\t\t\t       rtp_channel,\n\t\t\t\t\t\t\t       rtcp_channel,\n\t\t\t\t\t\t\t       session_id);\n\t\t} else if (rtsp_request_->GetTransportMode() ==\n\t\t\t   TransportMode::RTP_OVER_UDP) {\n\t\t\tconst uint16_t peer_rtp_port =\n\t\t\t\trtsp_request_->GetRtpPort();\n\t\t\tconst uint16_t peer_rtcp_port =\n\t\t\t\trtsp_request_->GetRtcpPort();\n\t\t\tconst uint16_t session_id =\n\t\t\t\trtp_conn_->GetRtpSessionId();\n\n\t\t\tif (rtp_conn_->SetupRtpOverUdp(channel_id,\n\t\t\t\t\t\t       peer_rtp_port,\n\t\t\t\t\t\t       peer_rtcp_port)) {\n\t\t\t\tauto rtcp_fd = rtp_conn_->GetRtcpfd(channel_id);\n\t\t\t\tconst auto channel =\n\t\t\t\t\tmake_shared<Channel>(rtcp_fd);\n\t\t\t\tchannel->SetReadCallback([rtcp_fd, this]() {\n\t\t\t\t\tthis->HandleRtcp(rtcp_fd);\n\t\t\t\t});\n\t\t\t\tchannel->EnableReading();\n\t\t\t\tGetTaskScheduler()->UpdateChannel(channel);\n\t\t\t\trtcp_channels_[static_cast<uint8_t>(channel_id)] =\n\t\t\t\t\tchannel;\n\t\t\t} else {\n\t\t\t\tgoto server_error;\n\t\t\t}\n\n\t\t\tconst uint16_t serRtpPort =\n\t\t\t\trtp_conn_->GetRtpPort(channel_id);\n\t\t\tconst uint16_t serRtcpPort =\n\t\t\t\trtp_conn_->GetRtcpPort(channel_id);\n\t\t\tsize = rtsp_request_->BuildSetupUdpRes(res.get(), 4096,\n\t\t\t\t\t\t\t       serRtpPort,\n\t\t\t\t\t\t\t       serRtcpPort,\n\t\t\t\t\t\t\t       session_id);\n\t\t} else {\n\t\t\tgoto transport_unsupport;\n\t\t}\n\t}\n\n\tSendRtspMessage(res, size);\n\treturn;\n\ntransport_unsupport:\n\tsize = rtsp_request_->BuildUnsupportedRes(res.get(), 4096);\n\tSendRtspMessage(res, size);\n\treturn;\n\nserver_error:\n\tsize = rtsp_request_->BuildServerErrorRes(res.get(), 4096);\n\tSendRtspMessage(res, size);\n}\n\nvoid RtspConnection::HandleCmdPlay()\n{\n\tif (auth_info_ != nullptr) {\n\t\tif (!HandleAuthentication()) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (rtp_conn_ == nullptr) {\n\t\treturn;\n\t}\n\n\tconn_state_ = ConnectionState::START_PLAY;\n\trtp_conn_->Play();\n\n\tconst uint16_t session_id = rtp_conn_->GetRtpSessionId();\n\tconst std::shared_ptr<char> res(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\n\tconst int size = rtsp_request_->BuildPlayRes(res.get(), 2048, nullptr,\n\t\t\t\t\t\t     session_id);\n\tSendRtspMessage(res, size);\n}\n\nvoid RtspConnection::HandleCmdTeardown()\n{\n\tif (rtp_conn_ == nullptr) {\n\t\treturn;\n\t}\n\n\trtp_conn_->Teardown();\n\n\tconst uint16_t session_id = rtp_conn_->GetRtpSessionId();\n\tconst std::shared_ptr<char> res(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size =\n\t\trtsp_request_->BuildTeardownRes(res.get(), 2048, session_id);\n\tSendRtspMessage(res, size);\n\n\t//HandleClose();\n}\n\nvoid RtspConnection::HandleCmdGetParamter()\n{\n\tif (rtp_conn_ == nullptr) {\n\t\treturn;\n\t}\n\n\tconst uint16_t session_id = rtp_conn_->GetRtpSessionId();\n\tconst std::shared_ptr<char> res(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size =\n\t\trtsp_request_->BuildGetParamterRes(res.get(), 2048, session_id);\n\tSendRtspMessage(res, size);\n}\n\nbool RtspConnection::HandleAuthentication()\n{\n\tif (auth_info_ != nullptr && !has_auth_) {\n\t\tconst std::string cmd =\n\t\t\trtsp_request_->MethodToString[static_cast<uint8_t>(\n\t\t\t\trtsp_request_->GetMethod())];\n\n\t\tif (const std::string url = rtsp_request_->GetRtspUrl();\n\t\t    !nonce_.empty() &&\n\t\t    (auth_info_->GetResponse(nonce_, cmd, url) ==\n\t\t     rtsp_request_->GetAuthResponse())) {\n\t\t\tnonce_.clear();\n\t\t\thas_auth_ = true;\n\t\t} else {\n\t\t\tconst std::shared_ptr<char> res(\n\t\t\t\tnew char[4096], std::default_delete<char[]>());\n\t\t\tnonce_ = auth_info_->GetNonce();\n\t\t\tconst int size = rtsp_request_->BuildUnauthorizedRes(\n\t\t\t\tres.get(), 4096, auth_info_->GetRealm().c_str(),\n\t\t\t\tnonce_.c_str());\n\t\t\tSendRtspMessage(res, size);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nvoid RtspConnection::SendOptions(const ConnectionMode mode)\n{\n\tconst auto rtsp = rtsp_.lock();\n\tif (!rtsp) {\n\t\tHandleClose();\n\t\treturn;\n\t}\n\n\tconst auto media_session = rtsp->LookMediaSession(1);\n\n\tif (rtp_conn_ == nullptr) {\n\n\t\tif (media_session->IsMulticast())\n\t\t\trtp_conn_ = media_session->GetMulticastRtpConnection(IsIpv6());\n\t\telse\n\t\t\trtp_conn_ = make_shared<RtpConnection>(\n\t\t\t\tstd::dynamic_pointer_cast<RtspConnection>(\n\t\t\t\t\tshared_from_this()),\n\t\t\t\tmedia_session->GetMaxChannelCount());\n\t}\n\n\tconn_mode_ = mode;\n\trtsp_response_->SetUserAgent(USER_AGENT);\n\trtsp_response_->SetRtspUrl(rtsp->GetRtspUrl().c_str());\n\n\tconst std::shared_ptr<char> req(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size = rtsp_response_->BuildOptionReq(req.get(), 2048);\n\tSendRtspMessage(req, size);\n}\n\nvoid RtspConnection::SendAnnounce()\n{\n\tMediaSession::Ptr media_session = nullptr;\n\n\tconst auto rtsp = rtsp_.lock();\n\tif (rtsp) {\n\t\tmedia_session = rtsp->LookMediaSession(1);\n\t}\n\n\tif (!rtsp || !media_session) {\n\t\tHandleClose();\n\t\treturn;\n\t}\n\tsession_id_ = media_session->GetMediaSessionId();\n\tmedia_session->AddClient(this->GetSocket(), rtp_conn_, GetIp(),\n\t\t\t\t GetPort());\n\n\tfor (uint16_t chn = 0; chn < media_session->GetMaxChannelCount();\n\t     chn++) {\n\t\tif (MediaSource *source = media_session->GetMediaSource(\n\t\t\t    static_cast<MediaChannelId>(chn));\n\t\t    source != nullptr) {\n\t\t\trtp_conn_->SetClockRate(\n\t\t\t\tstatic_cast<MediaChannelId>(chn),\n\t\t\t\tsource->GetClockRate());\n\t\t\trtp_conn_->SetPayloadType(\n\t\t\t\tstatic_cast<MediaChannelId>(chn),\n\t\t\t\tsource->GetPayloadType());\n\t\t}\n\t}\n\n\tconst auto sdp = media_session->GetSdpMessage(\n\t\tSocketUtil::GetSocketIp(GetSocket(), IsIpv6()),\n\t\trtsp->GetVersion(), IsIpv6());\n\tif (sdp.empty()) {\n\t\tHandleClose();\n\t\treturn;\n\t}\n\n\tconst std::shared_ptr<char> req(new char[4096],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size =\n\t\trtsp_response_->BuildAnnounceReq(req.get(), 4096, sdp.c_str());\n\tSendRtspMessage(req, size);\n}\n\nvoid RtspConnection::SendDescribe()\n{\n\tconst std::shared_ptr<char> req(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tconst int size = rtsp_response_->BuildDescribeReq(req.get(), 2048);\n\tSendRtspMessage(req, size);\n}\n\nvoid RtspConnection::SendSetup()\n{\n\tint size = 0;\n\tconst std::shared_ptr<char> buf(new char[2048],\n\t\t\t\t\tstd::default_delete<char[]>());\n\tMediaSession::Ptr media_session = nullptr;\n\n\tconst auto rtsp = rtsp_.lock();\n\tif (rtsp) {\n\t\tmedia_session = rtsp->LookMediaSession(session_id_);\n\t}\n\n\tif (!rtsp || !media_session) {\n\t\tHandleClose();\n\t\treturn;\n\t}\n\n\tfor (uint16_t chn = 0; chn < media_session->GetMaxChannelCount();\n\t     chn++) {\n\t\tif (const auto mediaChannelId =\n\t\t\t    static_cast<MediaChannelId>(chn);\n\t\t    media_session->GetMediaSource(mediaChannelId) &&\n\t\t    !rtp_conn_->IsSetup(mediaChannelId)) {\n\t\t\trtp_conn_->SetupRtpOverTcp(mediaChannelId, chn * 2,\n\t\t\t\t\t\t   chn * 2 + 1);\n\t\t\tsize = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048,\n\t\t\t\t\t\t\t\tchn);\n\t\t} else {\n\t\t\tsize = rtsp_response_->BuildRecordReq(buf.get(), 2048);\n\t\t}\n\t}\n\n\t/*if (media_session->GetMediaSource(MediaChannelId::channel_0) && !rtp_conn_->IsSetup(MediaChannelId::channel_0)) {\n\t\trtp_conn_->SetupRtpOverTcp(MediaChannelId::channel_0, 0, 1);\n\t\tsize = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048, static_cast<uint8_t>(MediaChannelId::channel_0));\n\t}\n\telse if (media_session->GetMediaSource(MediaChannelId::channel_1) && !rtp_conn_->IsSetup(MediaChannelId::channel_1)) {\n\t\trtp_conn_->SetupRtpOverTcp(MediaChannelId::channel_1, 2, 3);\n\t\tsize = rtsp_response_->BuildSetupTcpReq(buf.get(), 2048, static_cast<uint8_t>(MediaChannelId::channel_1));\n\t}\n\telse {\n\t\tsize = rtsp_response_->BuildRecordReq(buf.get(), 2048);\n\t}*/\n\n\tSendRtspMessage(buf, size);\n}\n\nvoid RtspConnection::HandleRecord()\n{\n\tconn_state_ = ConnectionState::START_PUSH;\n\trtp_conn_->Record();\n}\n"
  },
  {
    "path": "rtsp-server/xop/RtspConnection.h",
    "content": "// PHZ\n// 2018-6-8\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#ifndef _RTSP_CONNECTION_H\n#define _RTSP_CONNECTION_H\n\n#include \"net/TcpConnection.h\"\n#include \"RtpConnection.h\"\n#include \"RtspMessage.h\"\n#include \"DigestAuthentication.h\"\n#include \"rtsp.h\"\n#include <functional>\n#include <memory>\n#include <cstdint>\n\nnamespace xop {\n\nclass RtspConnection : public TcpConnection {\npublic:\n\tusing CloseCallback = std::function<void(SOCKET sockfd)>;\n\n\tenum class ConnectionMode {\n\t\tRTSP_SERVER,\n\t\tRTSP_PUSHER,\n\t\t//RTSP_CLIENT,\n\t};\n\n\tenum class ConnectionState { START_CONNECT, START_PLAY, START_PUSH };\n\n\tRtspConnection() = delete;\n\tRtspConnection(SOCKET sockfd,\n\t\t       std::shared_ptr<TaskScheduler> task_scheduler,\n\t\t       const std::shared_ptr<Rtsp> &rtsp);\n\t~RtspConnection() override;\n\n\tMediaSessionId GetMediaSessionId() const { return session_id_; }\n\n\tvoid KeepAlive() { ++alive_count_; }\n\n\tbool IsAlive() const\n\t{\n\t\tif (IsClosed()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (rtp_conn_ != nullptr) {\n\t\t\tif (rtp_conn_->IsMulticast()) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn (alive_count_ > 0);\n\t}\n\n\tvoid ResetAliveCount() { alive_count_ = 0; }\n\n\tint GetId() const { return GetTaskScheduler()->GetId(); }\n\n\tbool IsPlay() const\n\t{\n\t\treturn conn_state_ == ConnectionState::START_PLAY;\n\t}\n\n\tbool IsRecord() const\n\t{\n\t\treturn conn_state_ == ConnectionState::START_PUSH;\n\t}\n\nprivate:\n\tfriend class RtpConnection;\n\tfriend class MediaSession;\n\tfriend class RtspServer;\n\tfriend class RtspPusher;\n\n\tbool OnRead(BufferReader &buffer);\n\tvoid OnClose();\n\tvoid HandleRtcp(SOCKET sockfd);\n\tstatic void HandleRtcp(BufferReader &buffer);\n\tbool HandleRtspRequest(BufferReader &buffer);\n\tbool HandleRtspResponse(BufferReader &buffer);\n\n\tvoid SendRtspMessage(std::shared_ptr<char> buf, uint32_t size);\n\n\tvoid HandleCmdOption();\n\tvoid HandleCmdDescribe();\n\tvoid HandleCmdSetup();\n\tvoid HandleCmdPlay();\n\tvoid HandleCmdTeardown();\n\tvoid HandleCmdGetParamter();\n\tbool HandleAuthentication();\n\n\tvoid SendOptions(ConnectionMode mode = ConnectionMode::RTSP_SERVER);\n\tvoid SendDescribe();\n\tvoid SendAnnounce();\n\tvoid SendSetup();\n\tvoid HandleRecord();\n\n\tstd::atomic_int alive_count_;\n\tstd::weak_ptr<Rtsp> rtsp_;\n\n\tConnectionMode conn_mode_ = ConnectionMode::RTSP_SERVER;\n\tConnectionState conn_state_ = ConnectionState::START_CONNECT;\n\tMediaSessionId session_id_ = 0;\n\n\tbool has_auth_ = true;\n\tstd::string nonce_;\n\tstd::unique_ptr<DigestAuthentication> auth_info_;\n\n\t//std::shared_ptr<Channel>       rtp_channel_;\n\tstd::map<uint32_t, std::shared_ptr<Channel>> rtcp_channels_;\n\tstd::unique_ptr<RtspRequest> rtsp_request_;\n\tstd::unique_ptr<RtspResponse> rtsp_response_;\n\tstd::shared_ptr<RtpConnection> rtp_conn_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/RtspMessage.cpp",
    "content": "// PHZ\n// 2018-5-16\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n\n#include \"RtspMessage.h\"\n#include \"media.h\"\n\nusing namespace std;\nusing namespace xop;\n\nbool RtspRequest::ParseRequest(BufferReader *buffer)\n{\n\tif (buffer->Peek()[0] == '$') {\n\t\tmethod_ = Method::RTCP;\n\t\treturn true;\n\t}\n\n\tbool ret = true;\n\twhile (true) {\n\t\tif (state_ == RtspRequestParseState::kParseRequestLine) {\n\t\t\tif (const char *firstCrlf = buffer->FindFirstCrlf(); firstCrlf != nullptr) {\n\t\t\t\tret = ParseRequestLine(buffer->Peek(),\n\t\t\t\t\t\t       firstCrlf);\n\t\t\t\tbuffer->RetrieveUntil(firstCrlf + 2);\n\t\t\t}\n\n\t\t\tif (state_ ==\n\t\t\t    RtspRequestParseState::kParseHeadersLine) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (state_ == RtspRequestParseState::kParseHeadersLine) {\n\t\t\tif (const char *lastCrlf = buffer->FindLastCrlf(); lastCrlf != nullptr) {\n\t\t\t\tret = ParseHeadersLine(buffer->Peek(),\n\t\t\t\t\t\t       lastCrlf);\n\t\t\t\tbuffer->RetrieveUntil(lastCrlf + 2);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (state_ == RtspRequestParseState::kGotAll) {\n\t\t\tbuffer->RetrieveAll();\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nbool RtspRequest::ParseRequestLine(const char *begin, const char *end)\n{\n\tconst string message(begin, end);\n\tchar method[64] = {0};\n\tchar url[512] = {0};\n\tchar version[64] = {0};\n\n\tif (sscanf(message.c_str(), \"%s %s %s\", method, url, version) != 3) {\n\t\treturn true;\n\t}\n\n\tstring method_str(method);\n\tif (method_str == \"OPTIONS\") {\n\t\tmethod_ = Method::OPTIONS;\n\t} else if (method_str == \"DESCRIBE\") {\n\t\tmethod_ = Method::DESCRIBE;\n\t} else if (method_str == \"SETUP\") {\n\t\tmethod_ = Method::SETUP;\n\t} else if (method_str == \"PLAY\") {\n\t\tmethod_ = Method::PLAY;\n\t} else if (method_str == \"TEARDOWN\") {\n\t\tmethod_ = Method::TEARDOWN;\n\t} else if (method_str == \"GET_PARAMETER\") {\n\t\tmethod_ = Method::GET_PARAMETER;\n\t} else {\n\t\tmethod_ = Method::NONE;\n\t\treturn false;\n\t}\n\n\tif (strncmp(url, \"rtsp://\", 7) != 0) {\n\t\treturn false;\n\t}\n\n\t// parse url\n\tuint16_t port = 0;\n\tchar host[64] = {0};\n\tchar suffix[64] = {0};\n\n\tif (sscanf(url + 7, \"[%[^]]]:%hu/%s\", host, &port, suffix) >=\n\t    2) { //IPv6\n\n\t} else if (sscanf(url + 7, \"[%[^]]]/%s\", host, suffix) >= 1) {\n\t\tport = 554;\n\t} else if (sscanf(url + 7, \"%[^:]:%hu/%s\", host, &port, suffix) >=\n\t\t   2) { //IPv4, domain\n\n\t} else if (sscanf(url + 7, \"%[^/]/%s\", host, suffix) >= 1) {\n\t\tport = 554;\n\t} else {\n\t\treturn false;\n\t}\n\n\trequest_line_param_.emplace(\"url\", make_pair(string(url), 0));\n\trequest_line_param_.emplace(\"url_host\", make_pair(string(host), 0));\n\trequest_line_param_.emplace(\"url_port\",\n\t\t\t\t    make_pair(\"\", static_cast<uint32_t>(port)));\n\trequest_line_param_.emplace(\"url_suffix\", make_pair(string(suffix), 0));\n\trequest_line_param_.emplace(\"version\", make_pair(string(version), 0));\n\trequest_line_param_.emplace(\"method\", make_pair(move(method_str), 0));\n\n\tstate_ = RtspRequestParseState::kParseHeadersLine;\n\treturn true;\n}\n\nbool RtspRequest::ParseHeadersLine(const char *begin, const char *end)\n{\n\tstring message(begin, end);\n\tif (!ParseCSeq(message)) {\n\t\tif (header_line_param_.find(\"cseq\") ==\n\t\t    header_line_param_.end()) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (method_ == Method::DESCRIBE || method_ == Method::SETUP ||\n\t    method_ == Method::PLAY) {\n\t\tParseAuthorization(message);\n\t}\n\n\tif (method_ == Method::OPTIONS) {\n\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\treturn true;\n\t}\n\n\tif (method_ == Method::DESCRIBE) {\n\t\tif (ParseAccept(message)) {\n\t\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method_ == Method::SETUP) {\n\t\tif (ParseTransport(message) && ParseMediaChannel(message)) {\n\t\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tif (method_ == Method::PLAY) {\n\t\tif (ParseSessionId(message)) {\n\t\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\t}\n\t\treturn true;\n\t}\n\n\tif (method_ == Method::TEARDOWN) {\n\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\treturn true;\n\t}\n\n\tif (method_ == Method::GET_PARAMETER) {\n\t\tstate_ = RtspRequestParseState::kGotAll;\n\t\treturn true;\n\t}\n\n\treturn true;\n}\n\nbool RtspRequest::ParseCSeq(std::string &message)\n{\n\tif (const std::size_t pos = message.find(\"CSeq\");\n\t    pos != std::string::npos) {\n\t\tuint32_t cseq = 0;\n\t\tsscanf(message.c_str() + pos, \"%*[^:]: %u\", &cseq); //TODO\n\t\theader_line_param_.emplace(\"cseq\", make_pair(\"\", cseq));\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nbool RtspRequest::ParseAccept(const std::string &message)\n{\n\tif (message.rfind(\"Accept\") == std::string::npos ||\n\t    message.rfind(\"sdp\") == std::string::npos) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool RtspRequest::ParseTransport(std::string &message)\n{\n\tif (std::size_t pos = message.find(\"Transport\");\n\t    pos != std::string::npos) {\n\t\tif ((pos = message.find(\"RTP/AVP/TCP\")) != std::string::npos) {\n\t\t\ttransport_ = TransportMode::RTP_OVER_TCP;\n\t\t\tuint16_t rtpChannel = 0, rtcpChannel = 0;\n\t\t\tif (sscanf(message.c_str() + pos, //TODO\n\t\t\t\t   \"%*[^;];%*[^;];%*[^=]=%hu-%hu\", &rtpChannel,\n\t\t\t\t   &rtcpChannel) != 2) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\theader_line_param_.emplace(\"rtp_channel\",\n\t\t\t\t\t\t   make_pair(\"\", rtpChannel));\n\t\t\theader_line_param_.emplace(\"rtcp_channel\",\n\t\t\t\t\t\t   make_pair(\"\", rtcpChannel));\n\t\t} else if ((pos = message.find(\"RTP/AVP\")) !=\n\t\t\t   std::string::npos) {\n\t\t\tuint16_t rtp_port = 0, rtcpPort = 0;\n\t\t\tif (((message.find(\"unicast\", pos)) !=\n\t\t\t     std::string::npos)) {\n\t\t\t\ttransport_ = TransportMode::RTP_OVER_UDP;\n\t\t\t\tif (sscanf(message.c_str() + pos, //TODO\n\t\t\t\t\t   \"%*[^;];%*[^;];%*[^=]=%hu-%hu\",\n\t\t\t\t\t   &rtp_port, &rtcpPort) != 2) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t} else if ((message.find(\"multicast\", pos)) !=\n\t\t\t\t   std::string::npos) {\n\t\t\t\ttransport_ = TransportMode::RTP_OVER_MULTICAST;\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\theader_line_param_.emplace(\"rtp_port\",\n\t\t\t\t\t\t   make_pair(\"\", rtp_port));\n\t\t\theader_line_param_.emplace(\"rtcp_port\",\n\t\t\t\t\t\t   make_pair(\"\", rtcpPort));\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nbool RtspRequest::ParseSessionId(std::string &message)\n{\n\tconst std::size_t pos = message.find(\"Session\");\n\tif (pos != std::string::npos) {\n\t\tuint32_t session_id = 0;\n\t\tif (sscanf(message.c_str() + pos, \"%*[^:]: %u\", &session_id) != //TODO\n\t\t    1) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nbool RtspRequest::ParseMediaChannel(std::string &message)\n{\n\tif (const auto iter = request_line_param_.find(\"url\");\n\t    iter != request_line_param_.end()) {\n\t\tconst std::string url = iter->second.first;\n\t\tconst std::size_t pos = url.rfind(\"/track\");\n\t\tif (pos != std::string::npos) {\n\t\t\tint channel_index;\n\t\t\tif (sscanf(url.c_str() + pos, \"/track%d\", //TODO\n\t\t\t\t   &channel_index) != 1) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tchannel_id_ =\n\t\t\t\tstatic_cast<MediaChannelId>(channel_index);\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nbool RtspRequest::ParseAuthorization(std::string &message)\n{\n\tif (std::size_t pos = message.find(\"Authorization\");\n\t    pos != std::string::npos) {\n\t\tif ((pos = message.find(\"response=\")) != std::string::npos) {\n\t\t\tauth_response_ = message.substr(pos + 10, 32);\n\t\t\tif (auth_response_.size() == 32) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\tauth_response_.clear();\n\treturn false;\n}\n\nuint32_t RtspRequest::GetCSeq() const\n{\n\tuint32_t cseq = 0;\n\tif (const auto iter = header_line_param_.find(\"cseq\");\n\t    iter != header_line_param_.end()) {\n\t\tcseq = iter->second.second;\n\t}\n\n\treturn cseq;\n}\n\nstd::string RtspRequest::GetHost() const\n{\n\tif (const auto iter = request_line_param_.find(\"url_ip\");\n\t    iter != request_line_param_.end()) {\n\t\treturn iter->second.first;\n\t}\n\n\treturn \"\";\n}\n\nstd::string RtspRequest::GetRtspUrl() const\n{\n\tif (const auto iter = request_line_param_.find(\"url\");\n\t    iter != request_line_param_.end()) {\n\t\treturn iter->second.first;\n\t}\n\n\treturn \"\";\n}\n\nstd::string RtspRequest::GetRtspUrlSuffix() const\n{\n\tif (const auto iter = request_line_param_.find(\"url_suffix\");\n\t    iter != request_line_param_.end()) {\n\t\treturn iter->second.first;\n\t}\n\n\treturn \"\";\n}\n\nstd::string RtspRequest::GetAuthResponse() const\n{\n\treturn auth_response_;\n}\n\nuint8_t RtspRequest::GetRtpChannel() const\n{\n\tif (const auto iter = header_line_param_.find(\"rtp_channel\");\n\t    iter != header_line_param_.end()) {\n\t\treturn iter->second.second;\n\t}\n\n\treturn 0;\n}\n\nuint8_t RtspRequest::GetRtcpChannel() const\n{\n\tif (const auto iter = header_line_param_.find(\"rtcp_channel\");\n\t    iter != header_line_param_.end()) {\n\t\treturn iter->second.second;\n\t}\n\n\treturn 0;\n}\n\nuint16_t RtspRequest::GetRtpPort() const\n{\n\tif (const auto iter = header_line_param_.find(\"rtp_port\");\n\t    iter != header_line_param_.end()) {\n\t\treturn iter->second.second;\n\t}\n\n\treturn 0;\n}\n\nuint16_t RtspRequest::GetRtcpPort() const\n{\n\tif (const auto iter = header_line_param_.find(\"rtcp_port\");\n\t    iter != header_line_param_.end()) {\n\t\treturn iter->second.second;\n\t}\n\n\treturn 0;\n}\n\nint RtspRequest::BuildOptionRes(const char *buf, int buf_size) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq());\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildDescribeRes(const char *buf, const int buf_size,\n\t\t\t\t  const char *sdp) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"Content-Length: %d\\r\\n\"\n\t\t \"Content-Type: application/sdp\\r\\n\"\n\t\t \"\\r\\n\"\n\t\t \"%s\",\n\t\t this->GetCSeq(), static_cast<int>(strlen(sdp)), sdp);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildSetupMulticastRes(const char *buf, const int buf_size,\n\t\t\t\t\tconst char *multicast_ip,\n\t\t\t\t\tconst uint16_t port,\n\t\t\t\t\tconst uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(\n\t\tconst_cast<char *>(buf), buf_size,\n\t\t\"RTSP/1.0 200 OK\\r\\n\"\n\t\t\"CSeq: %u\\r\\n\"\n\t\t\"Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%u-0;ttl=255\\r\\n\"\n\t\t\"Session: %u\\r\\n\"\n\t\t\"\\r\\n\",\n\t\tthis->GetCSeq(), multicast_ip, this->GetHost().c_str(), port,\n\t\tsession_id);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildSetupUdpRes(const char *buf, const int buf_size,\n\t\t\t\t  const uint16_t rtp_chn,\n\t\t\t\t  const uint16_t rtcp_chn,\n\t\t\t\t  const uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(\n\t\tconst_cast<char *>(buf), buf_size,\n\t\t\"RTSP/1.0 200 OK\\r\\n\"\n\t\t\"CSeq: %u\\r\\n\"\n\t\t\"Transport: RTP/AVP;unicast;client_port=%hu-%hu;server_port=%hu-%hu\\r\\n\"\n\t\t\"Session: %u\\r\\n\"\n\t\t\"\\r\\n\",\n\t\tthis->GetCSeq(), this->GetRtpPort(), this->GetRtcpPort(),\n\t\trtp_chn, rtcp_chn, session_id);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildSetupTcpRes(const char *buf, const int buf_size,\n\t\t\t\t  const uint16_t rtp_chn,\n\t\t\t\t  const uint16_t rtcp_chn,\n\t\t\t\t  const uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"Transport: RTP/AVP/TCP;unicast;interleaved=%d-%d\\r\\n\"\n\t\t \"Session: %u\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq(), rtp_chn, rtcp_chn, session_id);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildPlayRes(const char *buf, const int buf_size,\n\t\t\t      const char *rtp_info,\n\t\t\t      const uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %d\\r\\n\"\n\t\t \"Range: npt=0.000-\\r\\n\"\n\t\t \"Session: %u; timeout=60\\r\\n\",\n\t\t this->GetCSeq(), session_id);\n\n\tif (rtp_info != nullptr) {\n\t\tsnprintf(const_cast<char *>(buf) + strlen(buf),\n\t\t\t buf_size - strlen(buf), \"%s\\r\\n\", rtp_info);\n\t}\n\n\tsnprintf(const_cast<char *>(buf) + strlen(buf), buf_size - strlen(buf),\n\t\t \"\\r\\n\");\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildTeardownRes(const char *buf, const int buf_size,\n\t\t\t\t  const uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %d\\r\\n\"\n\t\t \"Session: %u\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq(), session_id);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildGetParamterRes(const char *buf, const int buf_size,\n\t\t\t\t     const uint32_t session_id) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 200 OK\\r\\n\"\n\t\t \"CSeq: %d\\r\\n\"\n\t\t \"Session: %u\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq(), session_id);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildNotFoundRes(const char *buf, const int buf_size) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 404 Not Found\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq());\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildServerErrorRes(const char *buf, const int buf_size) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 500 Internal Server Error\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq());\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildUnsupportedRes(const char *buf, const int buf_size) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 461 Unsupported transport\\r\\n\"\n\t\t \"CSeq: %d\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq());\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspRequest::BuildUnauthorizedRes(const char *buf, const int buf_size,\n\t\t\t\t      const char *realm,\n\t\t\t\t      const char *nonce) const\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RTSP/1.0 401 Unauthorized\\r\\n\"\n\t\t \"CSeq: %d\\r\\n\"\n\t\t \"WWW-Authenticate: Digest realm=\\\"%s\\\", nonce=\\\"%s\\\"\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t this->GetCSeq(), realm, nonce);\n\n\treturn static_cast<int>(strlen(buf));\n}\n\nbool RtspResponse::ParseResponse(BufferReader *buffer)\n{\n\tif (strstr(buffer->Peek(), \"\\r\\n\\r\\n\") != nullptr) {\n\t\tif (strstr(buffer->Peek(), \"OK\") == nullptr) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (char *ptr = strstr(buffer->Peek(), \"Session\");\n\t\t    ptr != nullptr) {\n\t\t\tif (char session_id[50] = {0};\n\t\t\t    sscanf(ptr, \"%*[^:]: %s\", session_id) == 1)\n\t\t\t\tsession_ = session_id;\n\t\t}\n\n\t\tcseq_++;\n\t\tbuffer->RetrieveUntil(\"\\r\\n\\r\\n\");\n\t}\n\n\treturn true;\n}\n\nint RtspResponse::BuildOptionReq(const char *buf, const int buf_size)\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"OPTIONS %s RTSP/1.0\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"User-Agent: %s\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str());\n\n\tmethod_ = Method::OPTIONS;\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspResponse::BuildAnnounceReq(const char *buf, const int buf_size,\n\t\t\t\t   const char *sdp)\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"ANNOUNCE %s RTSP/1.0\\r\\n\"\n\t\t \"Content-Type: application/sdp\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"User-Agent: %s\\r\\n\"\n\t\t \"Session: %s\\r\\n\"\n\t\t \"Content-Length: %d\\r\\n\"\n\t\t \"\\r\\n\"\n\t\t \"%s\",\n\t\t rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str(),\n\t\t this->GetSession().c_str(), static_cast<int>(strlen(sdp)),\n\t\t sdp);\n\n\tmethod_ = Method::ANNOUNCE;\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspResponse::BuildDescribeReq(const char *buf, const int buf_size)\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"DESCRIBE %s RTSP/1.0\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"Accept: application/sdp\\r\\n\"\n\t\t \"User-Agent: %s\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str());\n\n\tmethod_ = Method::DESCRIBE;\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspResponse::BuildSetupTcpReq(const char *buf, const int buf_size,\n\t\t\t\t   const int trackId)\n{\n\tint interleaved[2] = {0, 1};\n\tif (trackId == 1) {\n\t\tinterleaved[0] = 2;\n\t\tinterleaved[1] = 3;\n\t}\n\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(\n\t\tconst_cast<char *>(buf), buf_size,\n\t\t\"SETUP %s/track%d RTSP/1.0\\r\\n\"\n\t\t\"Transport: RTP/AVP/TCP;unicast;mode=record;interleaved=%d-%d\\r\\n\"\n\t\t\"CSeq: %u\\r\\n\"\n\t\t\"User-Agent: %s\\r\\n\"\n\t\t\"Session: %s\\r\\n\"\n\t\t\"\\r\\n\",\n\t\trtsp_url_.c_str(), trackId, interleaved[0], interleaved[1],\n\t\tthis->GetCSeq() + 1, user_agent_.c_str(),\n\t\tthis->GetSession().c_str());\n\n\tmethod_ = Method::SETUP;\n\treturn static_cast<int>(strlen(buf));\n}\n\nint RtspResponse::BuildRecordReq(const char *buf, const int buf_size)\n{\n\tmemset((void *)buf, 0, buf_size); //TODO\n\tsnprintf(const_cast<char *>(buf), buf_size,\n\t\t \"RECORD %s RTSP/1.0\\r\\n\"\n\t\t \"Range: npt=0.000-\\r\\n\"\n\t\t \"CSeq: %u\\r\\n\"\n\t\t \"User-Agent: %s\\r\\n\"\n\t\t \"Session: %s\\r\\n\"\n\t\t \"\\r\\n\",\n\t\t rtsp_url_.c_str(), this->GetCSeq() + 1, user_agent_.c_str(),\n\t\t this->GetSession().c_str());\n\n\tmethod_ = Method::RECORD;\n\treturn static_cast<int>(strlen(buf));\n}\n"
  },
  {
    "path": "rtsp-server/xop/RtspMessage.h",
    "content": "// PHZ\n// 2018-6-8\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#ifndef XOP_RTSP_MESSAGE_H\n#define XOP_RTSP_MESSAGE_H\n\n#include <utility>\n#include <unordered_map>\n#include <string>\n#include \"rtp.h\"\n#include \"media.h\"\n#include \"net/BufferReader.h\"\n\nnamespace xop {\n\nclass RtspRequest {\npublic:\n\tenum class Method {\n\t\tOPTIONS = 0,\n\t\tDESCRIBE,\n\t\tSETUP,\n\t\tPLAY,\n\t\tTEARDOWN,\n\t\tGET_PARAMETER,\n\t\tRTCP,\n\t\tNONE,\n\t};\n\n\tconst char *MethodToString[8] = {\"OPTIONS\", \"DESCRIBE\", \"SETUP\",\n\t\t\t\t\t \"PLAY\",    \"TEARDOWN\", \"GET_PARAMETER\",\n\t\t\t\t\t \"RTCP\",    \"NONE\"};\n\n\tenum class RtspRequestParseState {\n\t\tkParseRequestLine,\n\t\tkParseHeadersLine,\n\t\t//kParseBody,\n\t\tkGotAll,\n\t};\n\n\tbool ParseRequest(xop::BufferReader *buffer);\n\n\tbool GotAll() const { return state_ == RtspRequestParseState::kGotAll; }\n\n\tvoid Reset()\n\t{\n\t\tstate_ = RtspRequestParseState::kParseRequestLine;\n\t\trequest_line_param_.clear();\n\t\theader_line_param_.clear();\n\t}\n\n\tMethod GetMethod() const { return method_; }\n\n\tuint32_t GetCSeq() const;\n\n\tstd::string GetRtspUrl() const;\n\n\tstd::string GetRtspUrlSuffix() const;\n\n\tstd::string GetHost() const;\n\n\tstd::string GetAuthResponse() const;\n\n\tTransportMode GetTransportMode() const { return transport_; }\n\n\tMediaChannelId GetChannelId() const { return channel_id_; }\n\n\tuint8_t GetRtpChannel() const;\n\tuint8_t GetRtcpChannel() const;\n\tuint16_t GetRtpPort() const;\n\tuint16_t GetRtcpPort() const;\n\n\tint BuildOptionRes(const char *buf, int buf_size) const;\n\tint BuildDescribeRes(const char *buf, int buf_size,\n\t\t\t     const char *sdp) const;\n\tint BuildSetupMulticastRes(const char *buf, int buf_size,\n\t\t\t\t   const char *multicast_ip, uint16_t port,\n\t\t\t\t   uint32_t session_id) const;\n\tint BuildSetupTcpRes(const char *buf, int buf_size, uint16_t rtp_chn,\n\t\t\t     uint16_t rtcp_chn, uint32_t session_id) const;\n\tint BuildSetupUdpRes(const char *buf, int buf_size, uint16_t rtp_chn,\n\t\t\t     uint16_t rtcp_chn, uint32_t session_id) const;\n\tint BuildPlayRes(const char *buf, int buf_size, const char *rtp_info,\n\t\t\t uint32_t session_id) const;\n\tint BuildTeardownRes(const char *buf, int buf_size,\n\t\t\t     uint32_t session_id) const;\n\tint BuildGetParamterRes(const char *buf, int buf_size,\n\t\t\t\tuint32_t session_id) const;\n\tint BuildNotFoundRes(const char *buf, int buf_size) const;\n\tint BuildServerErrorRes(const char *buf, int buf_size) const;\n\tint BuildUnsupportedRes(const char *buf, int buf_size) const;\n\tint BuildUnauthorizedRes(const char *buf, int buf_size,\n\t\t\t\t const char *realm, const char *nonce) const;\n\nprivate:\n\tbool ParseRequestLine(const char *begin, const char *end);\n\tbool ParseHeadersLine(const char *begin, const char *end);\n\tbool ParseCSeq(std::string &message);\n\tstatic bool ParseAccept(const std::string &message);\n\tbool ParseTransport(std::string &message);\n\tstatic bool ParseSessionId(std::string &message);\n\tbool ParseMediaChannel(std::string &message);\n\tbool ParseAuthorization(std::string &message);\n\n\tMethod method_;\n\tMediaChannelId channel_id_;\n\tTransportMode transport_;\n\tstd::string auth_response_;\n\tstd::unordered_map<std::string, std::pair<std::string, uint32_t>>\n\t\trequest_line_param_;\n\tstd::unordered_map<std::string, std::pair<std::string, uint32_t>>\n\t\theader_line_param_;\n\n\tRtspRequestParseState state_ = RtspRequestParseState::kParseRequestLine;\n};\n\nclass RtspResponse {\npublic:\n\tenum class Method {\n\t\tOPTIONS = 0,\n\t\tDESCRIBE,\n\t\tANNOUNCE,\n\t\tSETUP,\n\t\tRECORD,\n\t\tRTCP,\n\t\tNONE,\n\t};\n\n\tbool ParseResponse(xop::BufferReader *buffer);\n\n\tMethod GetMethod() const { return method_; }\n\n\tuint32_t GetCSeq() const { return cseq_; }\n\n\tstd::string GetSession() const { return session_; }\n\n\tvoid SetUserAgent(const char *user_agent)\n\t{\n\t\tuser_agent_ = std::string(user_agent);\n\t}\n\n\tvoid SetRtspUrl(const char *url) { rtsp_url_ = std::string(url); }\n\n\tint BuildOptionReq(const char *buf, int buf_size);\n\tint BuildDescribeReq(const char *buf, int buf_size);\n\tint BuildAnnounceReq(const char *buf, int buf_size, const char *sdp);\n\tint BuildSetupTcpReq(const char *buf, int buf_size, int channel);\n\tint BuildRecordReq(const char *buf, int buf_size);\n\nprivate:\n\tMethod method_;\n\tuint32_t cseq_ = 0;\n\tstd::string user_agent_;\n\tstd::string rtsp_url_;\n\tstd::string session_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/RtspPusher.cpp",
    "content": "#include \"RtspPusher.h\"\n#include \"RtspConnection.h\"\n#include \"net/Logger.h\"\n#include \"net/TcpSocket.h\"\n#include \"net/Timestamp.h\"\n#include <memory>\n\nusing namespace xop;\n\nRtspPusher::RtspPusher(EventLoop *event_loop) : event_loop_(event_loop) {}\n\nRtspPusher::~RtspPusher()\n{\n\tthis->Close();\n}\n\nstd::shared_ptr<RtspPusher> RtspPusher::Create(EventLoop *loop)\n{\n\tstd::shared_ptr<RtspPusher> pusher(new RtspPusher(loop));\n\treturn pusher;\n}\n\nvoid RtspPusher::AddSession(MediaSession *session)\n{\n\tstd::lock_guard locker(mutex_);\n\tmedia_session_.reset(session);\n}\n\nvoid RtspPusher::RemoveSession(MediaSessionId session_id)\n{\n\tstd::lock_guard locker(mutex_);\n\tmedia_session_ = nullptr; //TODO\n}\n\nMediaSession::Ptr RtspPusher::LookMediaSession(MediaSessionId session_id)\n{\n\treturn media_session_; //TODO\n}\n\nint RtspPusher::OpenUrl(const std::string &url, const int msec)\n{\n\tstd::lock_guard lock(mutex_);\n\n\tstatic Timestamp timestamp;\n\tint timeout = msec;\n\tif (timeout <= 0) {\n\t\ttimeout = 10000;\n\t}\n\n\ttimestamp.reset();\n\n\tif (!this->ParseRtspUrl(url)) {\n\t\tLOG_ERROR(\"rtsp url(%s) was illegal.\\n\", url.c_str());\n\t\treturn -1;\n\t}\n\n\tif (rtsp_conn_ != nullptr) {\n\t\tstd::shared_ptr<RtspConnection> rtspConn = rtsp_conn_;\n\t\tSOCKET sockfd = rtspConn->GetSocket();\n\t\ttask_scheduler_->AddTriggerEvent(\n\t\t\t[sockfd, rtspConn]() { rtspConn->Disconnect(); });\n\t\trtsp_conn_ = nullptr;\n\t}\n\n\tTcpSocket tcpSocket;\n\ttcpSocket.Create();\n\tif (!tcpSocket.Connect(rtsp_url_info_.ip, rtsp_url_info_.port,\n\t\t\t       timeout)) {\n\t\ttcpSocket.Close();\n\t\treturn -1;\n\t}\n\n\ttask_scheduler_ = event_loop_->GetTaskScheduler();\n\trtsp_conn_ = std::make_shared<RtspConnection>(\n\t\ttcpSocket.GetSocket(), task_scheduler_, shared_from_this());\n\tevent_loop_->AddTriggerEvent([this]() {\n\t\trtsp_conn_->SendOptions(\n\t\t\tRtspConnection::ConnectionMode::RTSP_PUSHER);\n\t});\n\n\ttimeout -= static_cast<int>(timestamp.Elapsed());\n\tif (timeout < 0) {\n\t\ttimeout = 1000;\n\t}\n\n\tdo {\n\t\tTimer::Sleep(100);\n\t\ttimeout -= 100;\n\t} while (!rtsp_conn_->IsRecord() && timeout > 0);\n\n\tif (!rtsp_conn_->IsRecord()) {\n\t\tstd::shared_ptr<RtspConnection> rtspConn = rtsp_conn_;\n\t\tSOCKET sockfd = rtspConn->GetSocket();\n\t\ttask_scheduler_->AddTriggerEvent(\n\t\t\t[sockfd, rtspConn]() { rtspConn->Disconnect(); });\n\t\trtsp_conn_ = nullptr;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nvoid RtspPusher::Close()\n{\n\tstd::lock_guard lock(mutex_);\n\n\tif (rtsp_conn_ != nullptr) {\n\t\tstd::shared_ptr<RtspConnection> rtsp_conn = rtsp_conn_;\n\t\tSOCKET sockfd = rtsp_conn->GetSocket();\n\t\ttask_scheduler_->AddTriggerEvent(\n\t\t\t[sockfd, rtsp_conn]() { rtsp_conn->Disconnect(); });\n\t\trtsp_conn_ = nullptr;\n\t}\n}\n\nbool RtspPusher::IsConnected()\n{\n\tstd::lock_guard lock(mutex_);\n\n\tif (rtsp_conn_ != nullptr) {\n\t\treturn (!rtsp_conn_->IsClosed());\n\t}\n\treturn false;\n}\n\nbool RtspPusher::PushFrame(const MediaChannelId channelId, const AVFrame &frame)\n{\n\tstd::lock_guard locker(mutex_);\n\tif (!media_session_ || !rtsp_conn_) {\n\t\treturn false;\n\t}\n\n\treturn media_session_->HandleFrame(channelId, frame);\n}\n"
  },
  {
    "path": "rtsp-server/xop/RtspPusher.h",
    "content": "#ifndef XOP_RTSP_PUSHER_H\n#define XOP_RTSP_PUSHER_H\n\n#include <mutex>\n#include \"rtsp.h\"\n#include \"net/EventLoop.h\"\n\nnamespace xop {\n\nclass RtspConnection;\n\nclass RtspPusher : public Rtsp {\npublic:\n\tstatic std::shared_ptr<RtspPusher> Create(EventLoop *loop);\n\t~RtspPusher() override;\n\n\tvoid AddSession(MediaSession *session);\n\tvoid RemoveSession(MediaSessionId session_id);\n\n\tint OpenUrl(const std::string &url, int msec = 3000);\n\tvoid Close();\n\tbool IsConnected();\n\n\tbool PushFrame(MediaChannelId channelId, const AVFrame &frame);\n\nprivate:\n\tfriend class RtspConnection;\n\n\texplicit RtspPusher(EventLoop *event_loop);\n\tMediaSession::Ptr LookMediaSession(MediaSessionId session_id) override;\n\n\tEventLoop *event_loop_ = nullptr;\n\tstd::shared_ptr<TaskScheduler> task_scheduler_ = nullptr;\n\tstd::mutex mutex_;\n\tstd::shared_ptr<RtspConnection> rtsp_conn_;\n\tstd::shared_ptr<MediaSession> media_session_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/RtspServer.cpp",
    "content": "#include \"RtspServer.h\"\n#include \"RtspConnection.h\"\n\nusing namespace xop;\nusing namespace std;\n\nRtspServer::RtspServer(EventLoop *loop) : TcpServer(loop) {}\n\nRtspServer::~RtspServer() = default;\n\nstd::shared_ptr<RtspServer> RtspServer::Create(EventLoop *loop)\n{\n\tstd::shared_ptr<RtspServer> server(new RtspServer(loop));\n\treturn server;\n}\n\nMediaSessionId RtspServer::AddSession(MediaSession *session)\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (rtsp_suffix_map_.find(session->GetRtspUrlSuffix()) !=\n\t    rtsp_suffix_map_.end())\n\t\treturn 0;\n\n\tif (session->task_scheduler_.lock() != nullptr)\n\t\treturn 0;\n\n\tsession->task_scheduler_ = event_loop_->GetTaskScheduler();\n\tstd::shared_ptr<MediaSession> media_session(session);\n\tMediaSessionId sessionId = media_session->GetMediaSessionId();\n\trtsp_suffix_map_.emplace(media_session->GetRtspUrlSuffix(), sessionId);\n\tmedia_sessions_.emplace(sessionId, std::move(media_session));\n\n\treturn sessionId;\n}\n\nvoid RtspServer::RemoveSession(const MediaSessionId sessionId)\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (const auto iter = media_sessions_.find(sessionId);\n\t    iter != media_sessions_.end()) {\n\t\trtsp_suffix_map_.erase(iter->second->GetRtspUrlSuffix());\n\t\tmedia_sessions_.erase(sessionId);\n\t}\n}\n\nMediaSession::Ptr RtspServer::LookMediaSession(const std::string &suffix)\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (const auto iter = rtsp_suffix_map_.find(suffix);\n\t    iter != rtsp_suffix_map_.end()) {\n\t\tconst MediaSessionId id = iter->second;\n\t\treturn media_sessions_[id];\n\t}\n\n\treturn nullptr;\n}\n\nMediaSession::Ptr RtspServer::LookMediaSession(const MediaSessionId session_id)\n{\n\tstd::lock_guard locker(mutex_);\n\n\tif (const auto iter = media_sessions_.find(session_id);\n\t    iter != media_sessions_.end()) {\n\t\treturn iter->second;\n\t}\n\n\treturn nullptr;\n}\n\nbool RtspServer::PushFrame(const MediaSessionId session_id,\n\t\t\t   const MediaChannelId channel_id,\n\t\t\t   const AVFrame &frame)\n{\n\tstd::shared_ptr<MediaSession> sessionPtr;\n\n\t{\n\t\tstd::lock_guard locker(mutex_);\n\t\tif (const auto iter = media_sessions_.find(session_id);\n\t\t    iter != media_sessions_.end()) {\n\t\t\tsessionPtr = iter->second;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (sessionPtr != nullptr && sessionPtr->GetNumClient() != 0) {\n\t\treturn sessionPtr->HandleFrame(channel_id, frame);\n\t}\n\n\treturn false;\n}\n\nTcpConnection::Ptr RtspServer::OnConnect(SOCKET sockfd)\n{\n\treturn std::make_shared<RtspConnection>(\n\t\tsockfd, event_loop_->GetTaskScheduler(), shared_from_this());\n}\n"
  },
  {
    "path": "rtsp-server/xop/RtspServer.h",
    "content": "// PHZ\n// 2020-4-2\n\n#ifndef XOP_RTSP_SERVER_H\n#define XOP_RTSP_SERVER_H\n\n#include <memory>\n#include <string>\n#include <mutex>\n#include <unordered_map>\n#include \"net/TcpServer.h\"\n#include \"rtsp.h\"\n\nnamespace xop {\n\nclass RtspConnection;\n\nclass RtspServer : public Rtsp, public TcpServer {\npublic:\n\tstatic std::shared_ptr<RtspServer> Create(EventLoop *loop);\n\t~RtspServer() override;\n\n\tMediaSessionId AddSession(MediaSession *session);\n\tvoid RemoveSession(MediaSessionId sessionId);\n\n\tbool PushFrame(MediaSessionId session_id, MediaChannelId channel_id,\n\t               const AVFrame &frame);\n\nprivate:\n\tfriend class RtspConnection;\n\n\texplicit RtspServer(EventLoop *loop);\n\tMediaSession::Ptr LookMediaSession(const std::string &suffix) override;\n\tMediaSession::Ptr LookMediaSession(MediaSessionId session_id) override;\n\tTcpConnection::Ptr OnConnect(SOCKET sockfd) override;\n\n\tstd::mutex mutex_;\n\tstd::unordered_map<MediaSessionId, std::shared_ptr<MediaSession>>\n\t\tmedia_sessions_;\n\tstd::unordered_map<std::string, MediaSessionId> rtsp_suffix_map_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/VP8Source.cpp",
    "content": "// PHZ\n// 2021-8-26\n\n#if defined(WIN32) || defined(_WIN32)\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n#endif\n\n#include \"VP8Source.h\"\n#include <cstdio>\n#include <cstring>\n#include <chrono>\n#if defined(WIN32) || defined(_WIN32)\n\n#else\n#include <sys/time.h>\n#endif\n\nusing namespace xop;\nusing namespace std;\n\nVP8Source::VP8Source(const uint32_t framerate) : framerate_(framerate)\n{\n\tpayload_ = 96;\n\tclock_rate_ = 90000;\n}\n\nVP8Source *VP8Source::CreateNew(const uint32_t framerate)\n{\n\treturn new VP8Source(framerate);\n}\n\nVP8Source::~VP8Source() = default;\n\nstring VP8Source::GetMediaDescription(const uint16_t port)\n{\n\tchar buf[100];\n\tsnprintf(buf, sizeof(buf), \"m=video %hu RTP/AVP 96\", port);\n\treturn buf;\n}\n\nstring VP8Source::GetAttribute()\n{\n\treturn \"a=rtpmap:96 VP8/90000\";\n}\n\nbool VP8Source::HandleFrame(const MediaChannelId channel_id, AVFrame frame)\n{\n\tuint8_t *frame_buf = frame.buffer.get();\n\tsize_t frame_size = frame.size;\n\n\tif (frame.timestamp == 0) {\n\t\tframe.timestamp = GetTimestamp();\n\t}\n\n\t// X = R = N = 0; PartID = 0;\n\t// S = 1 if this is the first (or only) fragment of the frame\n\tuint8_t vp8_payload_descriptor = 0x10;\n\n\twhile (frame_size > 0) {\n\t\tsize_t payload_size = MAX_RTP_PAYLOAD_SIZE;\n\n\t\tRtpPacket rtp_pkt;\n\t\trtp_pkt.timestamp = frame.timestamp;\n\t\trtp_pkt.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t       RTP_VPX_HEAD_SIZE + MAX_RTP_PAYLOAD_SIZE;\n\t\trtp_pkt.last = 0;\n\n\t\tif (frame_size < MAX_RTP_PAYLOAD_SIZE) {\n\t\t\tpayload_size = frame_size;\n\t\t\trtp_pkt.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE +\n\t\t\t\t       RTP_VPX_HEAD_SIZE +\n\t\t\t\t       static_cast<uint16_t>(frame_size);\n\t\t\trtp_pkt.last = 1;\n\t\t}\n\n\t\trtp_pkt.data.get()[RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + 0] =\n\t\t\tvp8_payload_descriptor;\n\t\tmemcpy(rtp_pkt.data.get() + RTP_TCP_HEAD_SIZE +\n\t\t\t       RTP_HEADER_SIZE + RTP_VPX_HEAD_SIZE,\n\t\t       frame_buf, payload_size);\n\n\t\tif (send_frame_callback_) {\n\t\t\tif (!send_frame_callback_(channel_id, rtp_pkt))\n\t\t\t\treturn false;\n\t\t}\n\n\t\tframe_buf += payload_size;\n\t\tframe_size -= payload_size;\n\t\tvp8_payload_descriptor = 0x00;\n\t}\n\n\treturn true;\n}\n\nuint32_t VP8Source::GetTimestamp()\n{\n\tconst auto time_point = chrono::time_point_cast<chrono::microseconds>(\n\t\tchrono::steady_clock::now());\n\treturn static_cast<uint32_t>(\n\t\t(time_point.time_since_epoch().count() + 500) / 1000 * 90);\n}\n"
  },
  {
    "path": "rtsp-server/xop/VP8Source.h",
    "content": "// PHZ\n// 2021-8-26\n\n#ifndef XOP_VP8_SOURCE_H\n#define XOP_VP8_SOURCE_H\n\n#include \"MediaSource.h\"\n#include \"rtp.h\"\n\nnamespace xop {\n\nclass VP8Source : public MediaSource {\npublic:\n\tstatic VP8Source *CreateNew(uint32_t framerate = 25);\n\t~VP8Source() override;\n\n\tvoid Setframerate(const uint32_t framerate) { framerate_ = framerate; }\n\n\tuint32_t GetFramerate() const { return framerate_; }\n\n\tstd::string GetMediaDescription(uint16_t port = 0) override;\n\n\tstd::string GetAttribute() override;\n\n\tbool HandleFrame(MediaChannelId channelId, AVFrame frame) override;\n\n\tstatic uint32_t GetTimestamp();\n\nprivate:\n\texplicit VP8Source(uint32_t framerate);\n\n\tuint32_t framerate_ = 25;\n};\n\n}\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/media.h",
    "content": "﻿// PHZ\n// 2018-5-16\n\n#ifndef XOP_MEDIA_H\n#define XOP_MEDIA_H\n\n#include <memory>\n\nnamespace xop {\n\n/* RTSP服务支持的媒体类型 */\nenum class MediaType {\n\t//PCMU = 0,\n\tPCMA = 8,\n\tH264 = 96,\n\tAAC = 37,\n\tH265 = 265,\n\tNONE\n};\n\nenum class FrameType : uint8_t {\n\tVIDEO_FRAME_IDR = 0x01,\n\tVIDEO_FRAME_NOTIDR = 0x02,\n\tAUDIO_FRAME = 0x11,\n\tNONE = 0x00\n};\n\nstruct AVFrame {\n\texplicit AVFrame(const size_t size = 0)\n\t\t: buffer(new uint8_t[size], std::default_delete<uint8_t[]>()),\n\t\t  size(size),\n\t\t  timestamp(0)\n\t{\n\t}\n\n\tstd::shared_ptr<uint8_t> buffer; /* 帧数据 */\n\tsize_t size;                     /* 帧大小 */\n\tuint32_t timestamp;              /* 时间戳 */\n};\n\nenum class MediaChannelId : uint8_t {\n\tchannel_0 = 0,\n\tchannel_1 = 1,\n\tchannel_2 = 2,\n\tchannel_3 = 3,\n\tchannel_4 = 4,\n\tchannel_5 = 5,\n\tchannel_6 = 6,\n\tchannel_7 = 7,\n\tchannel_8 = 8,\n\tchannel_9 = 9\n};\n\ntypedef uint32_t MediaSessionId;\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/rtp.h",
    "content": "﻿// PHZ\n// 2021-9-2\n\n#ifndef XOP_RTP_H\n#define XOP_RTP_H\n\n#include <memory>\n#include <cstdint>\n#include \"media.h\"\n\n#define RTP_HEADER_SIZE 12\n#define MAX_RTP_PAYLOAD_SIZE 1420 //1460  1500-20-12-8\n#define RTP_VERSION 2\n#define RTP_TCP_HEAD_SIZE 4\n#define RTP_VPX_HEAD_SIZE 1\n\n#define RTP_HEADER_BIG_ENDIAN 0\nnamespace xop {\n\nenum class TransportMode {\n\tNONE = 0,\n\tRTP_OVER_TCP = 1,\n\tRTP_OVER_UDP = 2,\n\tRTP_OVER_MULTICAST = 3,\n};\n\ntypedef struct _RTP_header {\n#if RTP_HEADER_BIG_ENDIAN\n\t/* 大端序 */\n\tunsigned char version : 2;\n\tunsigned char padding : 1;\n\tunsigned char extension : 1;\n\tunsigned char csrc : 4;\n\tunsigned char marker : 1;\n\tunsigned char payload : 7;\n#else\n\t/* 小端序 */\n\tunsigned char csrc : 4;\n\tunsigned char extension : 1;\n\tunsigned char padding : 1;\n\tunsigned char version : 2;\n\tunsigned char payload : 7;\n\tunsigned char marker : 1;\n#endif\n\n\tunsigned short seq;\n\tunsigned int ts;\n\tunsigned int ssrc;\n} RtpHeader;\n\nstruct MediaChannelInfo {\n\tRtpHeader rtp_header;\n\n\t// tcp\n\tuint8_t rtp_channel;\n\tuint8_t rtcp_channel;\n\n\t// udp\n\tuint16_t rtp_port;\n\tuint16_t rtcp_port;\n\tuint16_t packet_seq;\n\tuint32_t clock_rate;\n\n\t// rtcp\n\tuint64_t packet_count;\n\tuint64_t octet_count;\n\tuint64_t last_rtcp_ntp_time;\n\n\tbool is_setup;\n\tbool is_play;\n\tbool is_record;\n};\n\nstruct RtpPacket {\n\tRtpPacket()\n\t\t: data(new uint8_t[1600], std::default_delete<uint8_t[]>()),\n\t\t  size(0),\n\t\t  timestamp(0),\n\t\t  type(FrameType::NONE),\n\t\t  last(0)\n\t{\n\t}\n\n\tstd::shared_ptr<uint8_t> data;\n\tuint16_t size;\n\tuint32_t timestamp;\n\tFrameType type;\n\tuint8_t last;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp-server/xop/rtsp.h",
    "content": "// PHZ\n// 2018-6-8\n// Scott Xu\n// 2020-12-5 Add IPv6 Support.\n\n#ifndef XOP_RTSP_H\n#define XOP_RTSP_H\n\n#include <cstdio>\n#include <string>\n#include \"MediaSession.h\"\n#include \"net/Logger.h\"\n\nnamespace xop {\n\nstruct RtspUrlInfo {\n\tstd::string url;\n\tstd::string ip;\n\tuint16_t port;\n\tstd::string suffix;\n};\n\nclass Rtsp : public std::enable_shared_from_this<Rtsp> {\npublic:\n\tRtsp() = default;\n\tvirtual ~Rtsp() = default;\n\n\tvirtual void SetAuthConfig(const std::string realm,\n\t\t\t\t   const std::string username,\n\t\t\t\t   const std::string password)\n\t{\n\t\trealm_ = realm;\n\t\tusername_ = username;\n\t\tpassword_ = password;\n\t\thas_auth_info_ = true;\n\n\t\tif (realm_.empty() || username.empty()) {\n\t\t\thas_auth_info_ = false;\n\t\t}\n\t}\n\n\tvirtual void SetVersion(std::string version) // SDP Session Name\n\t{\n\t\tversion_ = std::move(version);\n\t}\n\n\tvirtual std::string GetVersion() { return version_; }\n\n\tvirtual std::string GetRtspUrl() { return rtsp_url_info_.url; }\n\n\tbool ParseRtspUrl(const std::string &url)\n\t{\n\t\tchar ip[100] = {0};\n\t\tchar suffix[100] = {0};\n\t\tuint16_t port = 0;\n#if defined(WIN32) || defined(_WIN32)\n\t\tif (sscanf_s(url.c_str() + 7, \"[%[^]]]:%hu/%s\", ip, 100, &port,\n\t\t\t     suffix, 100) == 3) //IPv6\n#else\n\t\tif (sscanf(url.c_str() + 7, \"[%[^]]]:%hu/%s\", ip, &port,\n\t\t\t   suffix) == 3)\n#endif\n\t\t{\n\t\t\trtsp_url_info_.port = port;\n\t\t}\n#if defined(WIN32) || defined(_WIN32)\n\t\telse if (sscanf_s(url.c_str() + 7, \"[%[^]]]/%s\", ip, 100,\n\t\t\t\t  suffix, 100) == 2)\n#else\n\t\telse if (sscanf(url.c_str() + 7, \"[%[^]]]/%s\", ip, suffix) == 2)\n#endif\n\t\t{\n\t\t\trtsp_url_info_.port = 554;\n\t\t}\n#if defined(WIN32) || defined(_WIN32)\n\t\telse if (sscanf_s(url.c_str() + 7, \"%[^:]:%hu/%s\", ip, 100,\n\t\t\t\t  &port, suffix, 100) == 3) //IPv4, domain\n#else\n\t\telse if (sscanf(url.c_str() + 7, \"%[^:]:%hu/%s\", ip, &port,\n\t\t\t\tsuffix) == 3)\n#endif\n\t\t{\n\t\t\trtsp_url_info_.port = port;\n\t\t}\n#if defined(WIN32) || defined(_WIN32)\n\t\telse if (sscanf_s(url.c_str() + 7, \"%[^/]/%s\", ip, 100, suffix,\n\t\t\t\t  100) == 2)\n#else\n\t\telse if (sscanf(url.c_str() + 7, \"%[^/]/%s\", ip, suffix) == 2)\n#endif\n\t\t{\n\t\t\trtsp_url_info_.port = 554;\n\t\t} else {\n\t\t\tLOG_ERROR(\"%s was illegal.\\n\", url.c_str());\n\t\t\treturn false;\n\t\t}\n\n\t\trtsp_url_info_.ip = ip;\n\t\trtsp_url_info_.suffix = suffix;\n\t\trtsp_url_info_.url = url;\n\t\treturn true;\n\t}\n\nprotected:\n\tfriend class RtspConnection;\n\tvirtual MediaSession::Ptr LookMediaSession([[maybe_unused]] const std::string &suffix)\n\t{\n\t\treturn nullptr;\n\t}\n\n\tvirtual MediaSession::Ptr LookMediaSession([[maybe_unused]] MediaSessionId sessionId)\n\t{\n\t\treturn nullptr;\n\t}\n\n\tbool has_auth_info_ = false;\n\tstd::string realm_;\n\tstd::string username_;\n\tstd::string password_;\n\tstd::string version_;\n\tRtspUrlInfo rtsp_url_info_;\n};\n\n}\n\n#endif\n"
  },
  {
    "path": "rtsp_main.cpp",
    "content": "#include <obs-module.h>\n#include <obs-frontend-api.h>\n#include <util/config-file.h>\n#include <QMainWindow>\n#include <QAction>\n#include <mutex>\n#include <net/Logger.h>\n#include \"helper.h\"\n#include \"rtsp_output_helper.h\"\n#include \"rtsp_output.h\"\n#include \"ui/rtsp_properties.hpp\"\n\nOBS_DECLARE_MODULE()\nOBS_MODULE_USE_DEFAULT_LOCALE(\"obs-rtspserver\", \"en-US\")\n\nvoid obs_frontend_event(enum obs_frontend_event event, void *ptr);\nvoid rtsp_output_auto_start(RtspOutputHelper *rtspOutputHelper);\nvoid rtsp_output_stop(RtspOutputHelper *rtspOutputHelper);\nvoid rtsp_output_save_settings(RtspOutputHelper *rtspOutputHelper);\nvoid rtsp_output_save_hotkey_settings(RtspOutputHelper *rtspOutputHelper);\nvoid server_log_write_callback(xop::Priority priority, std::string info);\n\nconst char *obs_module_name(void)\n{\n\treturn obs_module_text(\"RtspServer\");\n}\n\nconst char *obs_module_description(void)\n{\n\treturn obs_module_text(\"RstpServer.Description\");\n}\n\nbool obs_module_load(void)\n{\n\txop::Logger::Instance().SetWriteCallback(server_log_write_callback);\n\trtsp_output_register();\n\n\tRtspOutputHelper *rtspOutputHelper;\n\t{\n\t\tauto *settings = rtsp_output_read_data();\n\t\tauto *config = rtsp_properties_open_config();\n\t\tconst char *str = nullptr;\n\t\tstr = config_get_string(config, HOTKEY_CONFIG_SECTIION,\n\t\t\t\t\t\"RtspOutput\");\n\t\tobs_data_t *hotkey = nullptr;\n\t\tif (str) hotkey = obs_data_create_from_json(str);\n\t\trtspOutputHelper =\n\t\t\tRtspOutputHelper::CreateRtspOutput(settings, hotkey);\n\t\tobs_data_release(hotkey);\n\t\tconfig_close(config);\n\t\tobs_data_release(settings);\n\t}\n\n\tconst auto mainWindow = static_cast<QMainWindow *>(obs_frontend_get_main_window());\n\tconst auto action = static_cast<QAction *>(obs_frontend_add_tools_menu_qaction(\n\t\tobs_module_text(\"RtspServer\")));\n\n\tobs_frontend_push_ui_translation(obs_module_get_string);\n\tconst auto rtspProperties = new RtspProperties(rtspOutputHelper->GetOutputName(), mainWindow);\n\tobs_frontend_pop_ui_translation();\n\n\tQAction::connect(action, &QAction::triggered, rtspProperties, &QDialog::exec);\n\n\tobs_frontend_add_event_callback(obs_frontend_event, rtspOutputHelper);\n\n\treturn true;\n}\n\nvoid obs_module_unload(void)\n{\n\tobs_frontend_remove_event_callback(obs_frontend_event, nullptr);\n}\n\nvoid obs_frontend_event(enum obs_frontend_event event, void *ptr)\n{\n\tconst auto rtspOutputHelper = static_cast<RtspOutputHelper *>(ptr);\n\tswitch (event) {\n\tcase OBS_FRONTEND_EVENT_FINISHED_LOADING:\n\t\trtsp_output_auto_start(rtspOutputHelper);\n\t\tbreak;\n\tcase OBS_FRONTEND_EVENT_EXIT:\n\t\trtsp_output_stop(rtspOutputHelper);\n\t\trtsp_output_save_settings(rtspOutputHelper);\n\t\trtsp_output_save_hotkey_settings(rtspOutputHelper);\n\t\tdelete rtspOutputHelper;\n\t\tbreak;\n\tdefault: ;\n\t}\n}\n\nvoid rtsp_output_auto_start(RtspOutputHelper *rtspOutputHelper)\n{\n\tauto *config = rtsp_properties_open_config();\n\tauto autoStart = false;\n\tif (config) {\n\t\tautoStart =\n\t\t\tconfig_get_bool(config, CONFIG_SECTIION, \"AutoStart\");\n\t\tconfig_close(config);\n\t}\n\tif (autoStart)\n\t\trtspOutputHelper->Start();\n}\n\nvoid rtsp_output_stop(RtspOutputHelper *rtspOutputHelper)\n{\n\trtspOutputHelper->Stop();\n}\n\nvoid rtsp_output_save_hotkey_settings(RtspOutputHelper *rtspOutputHelper)\n{\n\tauto *data = rtspOutputHelper->HotkeysSave();\n\tauto *str = obs_data_get_json(data);\n\tauto *config = rtsp_properties_open_config();\n\tconfig_set_string(config, HOTKEY_CONFIG_SECTIION, \"RtspOutput\", str);\n\tconfig_save(config);\n\tconfig_close(config);\n}\n\nvoid rtsp_output_save_settings(RtspOutputHelper *rtspOutputHelper)\n{\n\tauto *data = rtspOutputHelper->GetSettings();\n\trtsp_output_save_data(data);\n}\n\nvoid server_log_write_callback(xop::Priority priority, std::string info)\n{\n\tswitch (priority) {\n\tcase xop::LOG_DEBUG:\n\t\tblog(LOG_DEBUG, \"[rtsp-server] %s\", info.c_str());\n\t\tbreak;\n\tcase xop::LOG_STATE:\n\t\tblog(LOG_INFO, \"[rtsp-server] %s\", info.c_str());\n\t\tbreak;\n\tcase xop::LOG_INFO:\n\t\tblog(LOG_INFO, \"[rtsp-server] %s\", info.c_str());\n\t\tbreak;\n\tcase xop::LOG_WARNING:\n\t\tblog(LOG_WARNING, \"[rtsp-server] %s\", info.c_str());\n\t\tbreak;\n\tcase xop::LOG_ERROR:\n\t\tblog(LOG_ERROR, \"[rtsp-server] %s\", info.c_str());\n\t\tbreak;\n\tdefault: ;\n\t}\n}\n"
  },
  {
    "path": "rtsp_output.cpp",
    "content": "#include <thread>\n#include <memory>\n#include <array>\n#include <string>\n#include <obs-module.h>\n#include <obs-encoder.h>\n#include <util/threading.h>\n#include <xop/RtspServer.h>\n#include <xop/H264Source.h>\n#include <xop/H265Source.h>\n#include <xop/AACSource.h>\n#include \"threadsafe_queue.h\"\n#include \"rtsp_output.h\"\n#include \"helper.h\"\n\n#define USEC_IN_SEC 1000000\n\n#define ERROR_BEGIN_DATA_CAPTURE 1\n#define ERROR_INIT_ENCODERS 2\n#define ERROR_START_RTSP_SERVER 3\n#define ERROR_START_MULTICAST 4\n#define ERROR_ENCODE OBS_OUTPUT_ENCODE_ERROR\n\n#define OBS_RTSPSERVER_QUEUE_SIZE_LIMIT 2\n\nstruct queue_frame {\n\tqueue_frame(size_t size = 0)\n\t\t: av_frame(size), channe_id(xop::MediaChannelId::channel_0)\n\t{\n\t}\n\tstruct xop::AVFrame av_frame;\n\txop::MediaChannelId channe_id;\n};\n\nstruct rtsp_out_data {\n\tobs_output_t *output = nullptr;\n\n\tvolatile bool active;\n\tvolatile bool starting;\n\tvolatile bool stopping;\n\tvolatile uint64_t stop_ts;\n\n\t//volatile uint32_t num_clients = 0;\n\tstd::array<uint32_t, OBS_OUTPUT_MULTI_TRACK> audio_timestamp_clocks;\n\tstd::array<xop::MediaChannelId, OBS_OUTPUT_MULTI_TRACK> channel_ids;\n\tvolatile uint64_t total_bytes_sent = 0;\n\tvolatile uint32_t enabled_audio_channels_count = 0;\n\tvolatile bool output_audio = false;\n\n\tstd::unique_ptr<xop::EventLoop> event_loop;\n\tstd::shared_ptr<xop::RtspServer> server;\n\txop::MediaSessionId session_id = 0;\n\tstd::unique_ptr<threadsafe_queue<queue_frame>> frame_queue;\n\tstd::unique_ptr<std::thread> frame_push_thread;\n\n\tobs_hotkey_pair_id start_stop_hotkey;\n};\n\nstatic const char *rtsp_output_getname(void *unused)\n{\n\tUNUSED_PARAMETER(unused);\n\treturn obs_module_text(\"RtspOutput\");\n}\n\nstatic inline bool active(const rtsp_out_data *out_data)\n{\n\treturn os_atomic_load_bool(&out_data->active);\n}\n\nstatic inline bool starting(const rtsp_out_data *out_data)\n{\n\treturn os_atomic_load_bool(&out_data->starting);\n}\n\nstatic inline bool stopping(const rtsp_out_data *out_data)\n{\n\treturn os_atomic_load_bool(&out_data->stopping);\n}\n\nstatic void add_prestart_signal(const rtsp_out_data *out_data)\n{\n\tconst auto handler = obs_output_get_signal_handler(out_data->output);\n\tsignal_handler_add(handler, \"void pre_start()\");\n}\n\nstatic void send_prestart_signal(const rtsp_out_data *out_data)\n{\n\tconst auto handler = obs_output_get_signal_handler(out_data->output);\n\tsignal_handler_signal(handler, \"pre_start\", nullptr);\n}\n\nstatic bool rtsp_output_start_hotkey(void *data, const obs_hotkey_pair_id id,\n                                     obs_hotkey_t *hotkey, const bool pressed)\n{\n\tUNUSED_PARAMETER(id);\n\tUNUSED_PARAMETER(hotkey);\n\n\tconst auto out_data = static_cast<rtsp_out_data *>(data);\n\n\tif (!pressed)\n\t\treturn false;\n\tif (stopping(out_data) || starting(out_data) || active(out_data))\n\t\treturn false;\n\n\treturn obs_output_start(out_data->output);\n}\n\nstatic bool rtsp_output_stop_hotkey(void *data, const obs_hotkey_pair_id id,\n                                    obs_hotkey_t *hotkey, const bool pressed)\n{\n\tUNUSED_PARAMETER(id);\n\tUNUSED_PARAMETER(hotkey);\n\n\tconst auto *out_data = static_cast<rtsp_out_data *>(data);\n\n\tif (!pressed)\n\t\treturn false;\n\tif (stopping(out_data) || starting(out_data) || !active(out_data))\n\t\treturn false;\n\n\tobs_output_stop(out_data->output);\n\n\treturn true;\n}\n\nstatic void rtsp_output_destroy(void *data)\n{\n\t//rtsp_out_data *out_data = (rtsp_out_data *)data;\n\tbfree(data);\n}\n\nstatic void rtsp_output_update(void *data, obs_data_t *settings);\nstatic void *rtsp_output_create(obs_data_t *settings, obs_output_t *output)\n{\n\tconst auto data = static_cast<rtsp_out_data *>(\n\t\tbzalloc(sizeof(struct rtsp_out_data)));\n\n\tdata->output = output;\n\n\tdata->event_loop = std::make_unique<xop::EventLoop>();\n\tdata->server = xop::RtspServer::Create(data->event_loop.get());\n\n\tadd_prestart_signal(data);\n\n\tdata->start_stop_hotkey = obs_hotkey_pair_register_output(\n\t\toutput, \"RtspOutput.Start\",\n\t\tobs_module_text(\"RtspOutput.Hotkey.StartOutput\"),\n\t\t\"RtspOutput.Stop\",\n\t\tobs_module_text(\"RtspOutput.Hotkey.StopOutput\"),\n\t\trtsp_output_start_hotkey, rtsp_output_stop_hotkey, data, data);\n\n\tUNUSED_PARAMETER(settings);\n\treturn data;\n}\n\nstatic void rtsp_push_frame(void *param);\nstatic void set_output_error(const rtsp_out_data *out_data, int code, ...)\n{\n\tconst char *message;\n\tconst char *lookup_string;\n\tswitch (code) {\n\tcase ERROR_BEGIN_DATA_CAPTURE:\n\t\tmessage = \"can't begin data capture\";\n\t\tlookup_string = \"RtspOutput.Error.BeginDataCapture\";\n\t\tbreak;\n\tcase ERROR_INIT_ENCODERS:\n\t\tmessage = \"initialize encoders error\";\n\t\tlookup_string = \"RtspOutput.Error.InitEncoders\";\n\t\tbreak;\n\tcase ERROR_START_RTSP_SERVER:\n\t\tmessage = \"starting RTSP server failed on port '%d'\";\n\t\tlookup_string = \"RtspOutput.Error.StartRtspServer\";\n\t\tbreak;\n\tcase ERROR_START_MULTICAST:\n\t\tmessage = \"starting multicast failed\";\n\t\tlookup_string = \"RtspOutput.Error.StartMulticast\";\n\t\tbreak;\n\tcase ERROR_ENCODE:\n\t\tmessage = \"encode error\";\n\t\tlookup_string = \"RtspOutput.Error.Encode\";\n\t\tbreak;\n\tdefault:\n\t\tmessage = \"unknown error\";\n\t\tlookup_string = \"RtspOutput.Error.Unknown\";\n\t\tbreak;\n\t}\n\n\t{\n\t\tchar buffer[500] = {0};\n\t\tva_list args;\n\t\tva_start(args, code);\n#if defined(WIN32) || defined(_WIN32)\n\t\tvsprintf_s(buffer, obs_module_text(lookup_string), args);\n#else\n\t\tvsnprintf(buffer, sizeof(buffer),\n\t\t\t  obs_module_text(lookup_string), args);\n#endif\n\t\tva_end(args);\n\t\tobs_output_set_last_error(out_data->output, buffer);\n\t}\n\n\t{\n\t\tva_list args;\n\t\tva_start(args, code);\n\t\tblogva(LOG_WARNING, message, args);\n\t\tva_end(args);\n\t}\n}\n\nstatic bool rtsp_output_add_video_channel(void *data,\n\t\t\t\t\t  xop::MediaSession *session)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(data);\n\tconst auto video_encoder =\n\t\tobs_output_get_video_encoder(out_data->output);\n\tif (video_encoder == nullptr)\n\t\treturn false;\n\tconst auto video = obs_encoder_video(video_encoder);\n\tconst auto video_frame_rate = video_output_get_frame_rate(video);\n\tvector<uint8_t> extra_data;\n\t{\n\t\tuint8_t *p_extra_data = nullptr;\n\t\tsize_t extra_data_size = 0;\n\t\tif (!obs_encoder_get_extra_data(video_encoder, &p_extra_data,\n\t\t\t\t\t\t&extra_data_size))\n\t\t\textra_data_size = 0;\n\t\textra_data = vector<uint8_t>(p_extra_data,\n\t\t\t\t\t     p_extra_data + extra_data_size);\n\t}\n\tswitch (get_encoder_codec(video_encoder)) {\n\tcase encoder_codec::H264: {\n\t\tsession->AddSource(\n\t\t\txop::MediaChannelId::channel_0,\n\t\t\txop::H264Source::CreateNew(\n\t\t\t\textra_data,\n\t\t\t\tstatic_cast<uint32_t>(video_frame_rate)));\n\t} break;\n\tcase encoder_codec::HEVC: {\n\t\tsession->AddSource(\n\t\t\txop::MediaChannelId::channel_0,\n\t\t\txop::H265Source::CreateNew(\n\t\t\t\textra_data, vector<uint8_t>(),\n\t\t\t\tstatic_cast<uint32_t>(video_frame_rate)));\n\t} break;\n\tdefault:\n\t\tbreak;\n\t}\n\treturn true;\n}\n\nstatic bool rtsp_output_add_audio_channel(void *data,\n\t\t\t\t\t  xop::MediaSession *session,\n\t\t\t\t\t  const size_t idx,\n\t\t\t\t\t  const xop::MediaChannelId channel_id)\n{\n\tauto *out_data = static_cast<rtsp_out_data *>(data);\n\tconst auto audio_encoder =\n\t\tobs_output_get_audio_encoder(out_data->output, idx);\n\tif (audio_encoder == nullptr)\n\t\treturn true;\n\tconst auto audio = obs_encoder_audio(audio_encoder);\n\tconst auto audio_channels = audio_output_get_channels(audio);\n\tconst auto audio_sample_rate =\n\t\tobs_encoder_get_sample_rate(audio_encoder);\n\tsession->AddSource(\n\t\tchannel_id,\n\t\txop::AACSource::CreateNew(audio_sample_rate,\n\t\t\t\t\t  static_cast<uint8_t>(audio_channels),\n\t\t\t\t\t  false));\n\tout_data->audio_timestamp_clocks[idx] = audio_sample_rate;\n\treturn true;\n}\n\nstatic bool rtsp_output_start(void *data)\n{\n\tconst auto out_data = static_cast<rtsp_out_data *>(data);\n\n\tif (starting(out_data) || stopping(out_data))\n\t\treturn false;\n\n\tsend_prestart_signal(out_data);\n\n\tconst auto settings = obs_output_get_settings(out_data->output);\n\trtsp_output_update(data, settings);\n\tconst auto port =\n\t\tstatic_cast<uint16_t>(obs_data_get_int(settings, \"port\"));\n\n\tuint32_t enabled_audio_channels_count = 0;\n\tfor (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) {\n\t\tif (obs_output_get_audio_encoder(out_data->output, index) ==\n\t\t    nullptr) continue;\n\t\tout_data->channel_ids[index] = static_cast<xop::MediaChannelId>(\n\t\t\t++enabled_audio_channels_count);\n\t}\n\tout_data->enabled_audio_channels_count = enabled_audio_channels_count;\n\n\tif (!obs_output_can_begin_data_capture(out_data->output, 0)) {\n\t\tset_output_error(out_data, ERROR_BEGIN_DATA_CAPTURE);\n\t\treturn false;\n\t}\n\n\tif (!obs_output_initialize_encoders(out_data->output, 0)) {\n\t\tset_output_error(out_data, ERROR_INIT_ENCODERS);\n\t\treturn false;\n\t}\n\n\tif (!out_data->server->Start(\"0.0.0.0\", port) ||\n\t    !out_data->server->Start(\"::0\", port)) {\n\t\tset_output_error(out_data, ERROR_START_RTSP_SERVER, port);\n\t\tout_data->server->Stop();\n\t\treturn false;\n\t}\n\n\tobs_output_begin_data_capture(out_data->output, 0);\n\n\tos_atomic_set_bool(&out_data->starting, true);\n\n\treturn true;\n}\n\nstatic void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code);\nstatic void rtsp_output_rtsp_start(void *data)\n{\n\tconst auto out_data = static_cast<rtsp_out_data *>(data);\n\n\tconst auto settings = obs_output_get_settings(out_data->output);\n\tconst auto port =\n\t\tstatic_cast<uint16_t>(obs_data_get_int(settings, \"port\"));\n\tconst auto url_suffix = obs_data_get_string(settings, \"url_suffix\");\n\tout_data->output_audio = obs_data_get_bool(settings, \"output_audio\");\n\n\txop::MediaSession *session = xop::MediaSession::CreateNew(\n\t\turl_suffix, out_data->enabled_audio_channels_count + 1);\n\n\tif (!rtsp_output_add_video_channel(data, session)) {\n\t\trtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS);\n\t\treturn;\n\t}\n\n\tif (out_data->output_audio)\n\tfor (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) {\n\t\tif (!rtsp_output_add_audio_channel(\n\t\t\t    data, session, index,\n\t\t\t    out_data->channel_ids[index])) {\n\t\t\trtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tout_data->frame_queue = std::make_unique<threadsafe_queue<queue_frame>>(\n\t\tOBS_RTSPSERVER_QUEUE_SIZE_LIMIT);\n\n\tsession->AddNotifyConnectedCallback(\n\t\t[](const xop::MediaSessionId session_id,\n\t\t   const std::string &peer_ip, const uint16_t peer_port) {\n\t\t\tblog(LOG_INFO, \"Rtsp client %d(%s:%d) is connected.\",\n\t\t\t     session_id, peer_ip.c_str(), peer_port);\n\t\t});\n\n\tsession->AddNotifyDisconnectedCallback(\n\t\t[](const xop::MediaSessionId session_id,\n\t\t   const std::string &peer_ip, const uint16_t peer_port) {\n\t\t\tblog(LOG_INFO, \"Rtsp client %d(%s:%d) is disconnected.\",\n\t\t\t     session_id, peer_ip.c_str(), peer_port);\n\t\t});\n\n\tout_data->session_id = out_data->server->AddSession(session);\n\n\tif (obs_data_get_bool(settings, \"multicast\")) {\n\t\tif (!session->StartMulticast()) {\n\t\t\trtsp_output_actual_stop(out_data,\n\t\t\t\t\t\tERROR_START_MULTICAST);\n\t\t\treturn;\n\t\t}\n\t\tblog(LOG_INFO,\n\t\t     \"------------------------------------------------\");\n\t\tblog(LOG_INFO, \"Rtsp multicast info:\");\n\t\tblog(LOG_INFO, \"\\tipv6 address:        \\t%s\",\n\t\t     session->GetMulticastIp(true).c_str());\n\t\tblog(LOG_INFO, \"\\tipv4 address:        \\t%s\",\n\t\t     session->GetMulticastIp(false).c_str());\n\t\tblog(LOG_INFO, \"\\tchannel 0 port (video): \\t%d\",\n\t\t     session->GetMulticastPort(static_cast<xop::MediaChannelId>(0)));\n\t\tfor (size_t index = 1; index < OBS_OUTPUT_MULTI_TRACK + 1 ; index++) {\n\t\t\tauto channel_port = session->GetMulticastPort(static_cast<xop::MediaChannelId>(index));\n\t\t\tif (channel_port == 0) break;\n\t\t\tblog(LOG_INFO, \"\\tchannel %zu port (audio): \\t%d\", index, channel_port);\n\t\t}\n\t\tblog(LOG_INFO,\n\t\t     \"------------------------------------------------\");\n\t}\n\n\tout_data->total_bytes_sent = 0;\n\n\tout_data->frame_push_thread =\n\t\tstd::make_unique<std::thread>(rtsp_push_frame, out_data);\n\n\tos_atomic_set_bool(&out_data->active, true);\n\tos_atomic_set_bool(&out_data->starting, false);\n\n\tblog(LOG_INFO, \"Starting rtsp server on port '%d'.\", port);\n}\n\nstatic void rtsp_output_stop(void *data, const uint64_t ts)\n{\n\tauto *out_data = static_cast<rtsp_out_data *>(data);\n\n\tif (starting(out_data) || stopping(out_data))\n\t\treturn;\n\n\tout_data->stop_ts = ts / 1000ULL;\n\t//obs_output_pause(out_data->output, false);\n\tos_atomic_set_bool(&out_data->stopping, true);\n}\n\nstatic void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code)\n{\n\tos_atomic_set_bool(&out_data->active, false);\n\n\tif (out_data->frame_queue)\n\t\tout_data->frame_queue->termination();\n\n\tif (out_data->frame_push_thread) {\n\t\tout_data->frame_push_thread->join();\n\t\tout_data->frame_push_thread.reset();\n\t}\n\n\tif (out_data->session_id) {\n\t\tout_data->server->RemoveSession(out_data->session_id);\n\t\tout_data->session_id = 0;\n\t}\n\tout_data->server->Stop();\n\t//out_data->num_clients = 0;\n\n\tif (out_data->frame_queue)\n\t\tout_data->frame_queue.reset();\n\n\tif (code < 0) {\n\t\tset_output_error(out_data, code);\n\t\tobs_output_signal_stop(out_data->output, code);\n\t} else if (code > 0) {\n\t\tobs_output_end_data_capture(out_data->output);\n\t\tset_output_error(out_data, code);\n\t\tobs_output_signal_stop(out_data->output, OBS_OUTPUT_ERROR);\n\t} else {\n\t\tobs_output_end_data_capture(out_data->output);\n\t}\n\n\tos_atomic_set_bool(&out_data->starting, false);\n\tos_atomic_set_bool(&out_data->stopping, false);\n\n\tblog(LOG_INFO, \"Rtsp server stopped.\");\n}\n\nstatic uint32_t get_timestamp(const uint64_t timestamp_clock,\n                              const struct encoder_packet *packet)\n{\n\t// Convert the incoming dts time to the correct clock time for the timestamp.\n\t// We use a int64 to ensure the roll over is handled correctly.\n\t// We do the [USEC_IN_SEC / 2] trick to make sure the result of the division rounds to the nearest int.\n\tconst uint64_t timestamp = packet->dts_usec * timestamp_clock;\n\treturn static_cast<uint32_t>((timestamp + USEC_IN_SEC / 2) /\n\t\t\t\t     USEC_IN_SEC);\n}\n\nstatic void rtsp_push_frame(void *param)\n{\n\tauto *out_data = static_cast<rtsp_out_data *>(param);\n\n\tblog(LOG_INFO, \"Starting rtsp frame push thread.\");\n\twhile (true) {\n\t\tstd::shared_ptr<queue_frame> queue_frame =\n\t\t\tout_data->frame_queue->wait_and_pop();\n\t\tif (queue_frame == nullptr)\n\t\t\tbreak;\n\t\tout_data->total_bytes_sent += queue_frame->av_frame.size;\n\t\tout_data->server->PushFrame(out_data->session_id,\n\t\t\t\t\t    queue_frame->channe_id,\n\t\t\t\t\t    queue_frame->av_frame);\n\t}\n\tblog(LOG_INFO, \"Rtsp frame push thread stopped.\");\n}\n\nstatic void rtsp_output_video(void *param, struct encoder_packet *packet)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(param);\n\n\tstruct queue_frame queue_frame(packet->size);\n\txop::AVFrame *frame = &queue_frame.av_frame;\n\tqueue_frame.channe_id = xop::MediaChannelId::channel_0;\n\n\tframe->timestamp = get_timestamp(90000, packet);\n\n\tmemcpy(frame->buffer.get(), packet->data, packet->size);\n\n\tout_data->frame_queue->move_push(std::move(queue_frame));\n}\n\nstatic void rtsp_output_audio(void *param, struct encoder_packet *packet)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(param);\n\n\tqueue_frame queue_frame(packet->size);\n\txop::AVFrame *frame = &queue_frame.av_frame;\n\tqueue_frame.channe_id = out_data->channel_ids[packet->track_idx];\n\n\tframe->timestamp = get_timestamp(\n\t\tout_data->audio_timestamp_clocks[packet->track_idx], packet);\n\n\tmemcpy(frame->buffer.get(), packet->data, packet->size);\n\n\tout_data->frame_queue->move_push(std::move(queue_frame));\n}\n\nstatic void rtsp_output_data(void *data, struct encoder_packet *packet)\n{\n\tauto *out_data = static_cast<rtsp_out_data *>(data);\n\n\tif (!active(out_data) && !starting(out_data))\n\t\treturn;\n\n\tif (starting(out_data)) {\n\t\trtsp_output_rtsp_start(out_data);\n\t\treturn;\n\t}\n\n\tif (!packet) {\n\t\trtsp_output_actual_stop(out_data, OBS_OUTPUT_ENCODE_ERROR);\n\t\treturn;\n\t}\n\n\tif (stopping(out_data) &&\n\t    packet->sys_dts_usec >= static_cast<int64_t>(out_data->stop_ts)) {\n\t\trtsp_output_actual_stop(out_data, OBS_OUTPUT_SUCCESS);\n\t\treturn;\n\t}\n\n\t//if (out_data->num_clients > 0) {\n\tif (packet->type == OBS_ENCODER_VIDEO)\n\t\trtsp_output_video(data, packet);\n\telse if (packet->type == OBS_ENCODER_AUDIO && out_data->output_audio)\n\t\trtsp_output_audio(data, packet);\n\t//} else if (!stopping(out_data)) {\n\t//obs_output_pause(out_data->output, true);\n\t//}\n}\n\nstatic void rtsp_output_defaults(obs_data_t *defaults)\n{\n\tobs_data_set_default_bool(defaults, \"multicast\", false);\n#if defined(__APPLE__) || defined(__MACH__)\n\t// On osx the application will run using a non-priviliged user.\n\t// Opening ports below 1024 is not possible.\n\tobs_data_set_default_int(defaults, \"port\", 8554);\n#else\n\tobs_data_set_default_int(defaults, \"port\", 554);\n#endif\n\tobs_data_set_default_string(defaults, \"url_suffix\", \"live\");\n\tobs_data_set_default_bool(defaults, \"output_audio\", true);\n\tobs_data_set_default_bool(defaults, \"authentication\", false);\n\tobs_data_set_default_string(defaults, \"authentication_realm\", \"\");\n\tobs_data_set_default_string(defaults, \"authentication_username\", \"\");\n\tobs_data_set_default_string(defaults, \"authentication_password\", \"\");\n}\n\nstatic void rtsp_output_update(void *data, obs_data_t *settings)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(data);\n\tconst auto auth_enabled = obs_data_get_bool(settings, \"authentication\");\n\tconst auto auth_realm =\n\t\tobs_data_get_string(settings, \"authentication_realm\");\n\tconst auto auth_username =\n\t\tobs_data_get_string(settings, \"authentication_username\");\n\tconst auto auth_password =\n\t\tobs_data_get_string(settings, \"authentication_password\");\n\n\tif (auth_enabled && auth_realm && *auth_realm != '\\0' &&\n\t    auth_username && *auth_username != '\\0')\n\t\tout_data->server->SetAuthConfig(auth_realm, auth_username,\n\t\t\t\t\t\tauth_password);\n\telse {\n\t\tobs_data_set_bool(settings, \"authentication\", false);\n\t\tout_data->server->SetAuthConfig(\"\", \"\", \"\");\n\t}\n}\n\nstatic obs_properties_t *rtsp_output_properties(void *data)\n{\n\tUNUSED_PARAMETER(data);\n\n\tobs_properties_t *props = obs_properties_create();\n\tobs_properties_set_flags(props, OBS_PROPERTIES_DEFER_UPDATE);\n\n\tobs_properties_add_bool(\n\t\tprops, \"multicast\",\n\t\tobs_module_text(\"RtspOutput.Properties.Multicast\"));\n\tobs_properties_add_int(props, \"port\",\n\t\t\t       obs_module_text(\"RtspOutput.Properties.Port\"), 1,\n\t\t\t       65535, 1);\n\n\tobs_properties_add_text(\n\t\tprops, \"url_suffix\",\n\t\tobs_module_text(\"RtspOutput.Properties.UrlSuffix\"),\n\t\tOBS_TEXT_DEFAULT);\n\n\tobs_properties_add_bool(\n\t\tprops, \"output_audio\",\n\t\tobs_module_text(\"RtspOutput.Properties.OutputAudio\"));\n\n\tobs_properties_t *auth_group = obs_properties_create();\n\tobs_properties_add_text(\n\t\tauth_group, \"authentication_realm\",\n\t\tobs_module_text(\"RtspOutput.Properties.Authentication.Realm\"),\n\t\tOBS_TEXT_DEFAULT);\n\tobs_properties_add_text(\n\t\tauth_group, \"authentication_username\",\n\t\tobs_module_text(\n\t\t\t\"RtspOutput.Properties.Authentication.Username\"),\n\t\tOBS_TEXT_DEFAULT);\n\tobs_properties_add_text(\n\t\tauth_group, \"authentication_password\",\n\t\tobs_module_text(\n\t\t\t\"RtspOutput.Properties.Authentication.Password\"),\n\t\tOBS_TEXT_PASSWORD);\n\tobs_properties_add_group(\n\t\tprops, \"authentication\",\n\t\tobs_module_text(\"RtspOutput.Properties.Authentication\"),\n\t\tOBS_GROUP_CHECKABLE, auth_group);\n\treturn props;\n}\n\nstatic uint64_t rtsp_output_total_bytes_sent(void *data)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(data);\n\treturn out_data->total_bytes_sent;\n}\n\nstatic int rtsp_output_get_dropped_frames(void *data)\n{\n\tconst auto *out_data = static_cast<rtsp_out_data *>(data);\n\tif (!active(out_data) || out_data->frame_queue == nullptr) {\n\t\treturn 0;\n\t}\n\tauto dropped_count = out_data->frame_queue->dropped_count();\n\twhile (dropped_count > INT32_MAX)\n\t\tdropped_count -= INT32_MAX;\n\treturn static_cast<int>(dropped_count);\n}\n\nvoid rtsp_output_register()\n{\n\tstruct obs_output_info output_info = {};\n\toutput_info.id = \"rtsp_output\";\n\toutput_info.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED |\n\t\t\t    OBS_OUTPUT_MULTI_TRACK;\n\toutput_info.encoded_video_codecs = \"h264;hevc\";\n\toutput_info.encoded_audio_codecs = \"aac\";\n\toutput_info.get_name = rtsp_output_getname;\n\toutput_info.create = rtsp_output_create;\n\toutput_info.destroy = rtsp_output_destroy;\n\toutput_info.start = rtsp_output_start;\n\toutput_info.stop = rtsp_output_stop;\n\toutput_info.encoded_packet = rtsp_output_data;\n\toutput_info.get_defaults = rtsp_output_defaults;\n\toutput_info.update = rtsp_output_update;\n\toutput_info.get_properties = rtsp_output_properties;\n\toutput_info.get_total_bytes = rtsp_output_total_bytes_sent;\n\toutput_info.get_dropped_frames = rtsp_output_get_dropped_frames;\n\n\tobs_register_output(&output_info);\n}\n"
  },
  {
    "path": "rtsp_output.h",
    "content": "#pragma once\n\n[[maybe_unused]] static const char *rtsp_output_getname(void *unused);\nvoid rtsp_output_register();\n"
  },
  {
    "path": "rtsp_output_helper.cpp",
    "content": "#include <vector>\n#include <cstdio>\n#include <obs-module.h>\n#include <obs-frontend-api.h>\n#include <util/config-file.h>\n#include <util/dstr.h>\n#include \"rtsp_output_helper.h\"\n#include \"helper.h\"\n\nusing namespace std;\nRtspOutputHelper::RtspOutputHelper(const string outputName)\n\t: RtspOutputHelper(obs_get_output_by_name(outputName.c_str()))\n{\n}\n\nRtspOutputHelper::RtspOutputHelper(obs_output_t *obsOutput)\n\t: obsOutput(obsOutput)\n{\n}\n\nRtspOutputHelper::~RtspOutputHelper()\n{\n\tobs_output_release(obsOutput);\n\tobs_encoder_release(videoEncoder);\n\tfor (const auto audioEncoder : audioEncoders)\n\t\tobs_encoder_release(audioEncoder);\n}\n\nRtspOutputHelper *RtspOutputHelper::CreateRtspOutput(obs_data_t *settings,\n\t\t\t\t\t\t     obs_data_t *hotkey)\n{\n\tconst auto rtspOutput = new RtspOutputHelper(\n\t\tobs_output_create(\"rtsp_output\", obs_module_text(\"RtspOutput\"),\n\t\t\t\t  settings, hotkey));\n\trtspOutput->SignalConnect(\n\t\t\"pre_start\", OnPreStartSignal, rtspOutput);\n\treturn rtspOutput;\n}\n\nvoid RtspOutputHelper::UpdateSettings(obs_data_t *settings) const\n{\n\tobs_output_update(obsOutput, settings);\n}\n\nobs_data_t *RtspOutputHelper::GetSettings() const\n{\n\treturn obs_output_get_settings(obsOutput);\n}\n\nvoid RtspOutputHelper::UpdateEncoder()\n{\n\tGetBaseConfig();\n\tCreateVideoEncoder();\n\tCreateAudioEncoder();\n}\n\nbool RtspOutputHelper::Start() const\n{\n\treturn obs_output_start(obsOutput);\n}\n\nvoid RtspOutputHelper::Stop() const\n{\n\tobs_output_stop(obsOutput);\n}\n\nstring RtspOutputHelper::GetLastError() const\n{\n\treturn obs_output_get_last_error(obsOutput);\n}\n\nobs_data_t *RtspOutputHelper::HotkeysSave() const\n{\n\treturn obs_hotkeys_save_output(obsOutput);\n}\n\nvoid RtspOutputHelper::SignalConnect(const char *signal,\n\t\t\t\t     signal_callback_t callback, void *data) const\n{\n\tconst auto handler = obs_output_get_signal_handler(obsOutput);\n\tsignal_handler_connect(handler, signal, callback, data);\n}\n\nvoid RtspOutputHelper::SignalDisconnect(const char *signal,\n\t\t\t\t\tsignal_callback_t callback, void *data) const\n{\n\tconst auto handler = obs_output_get_signal_handler(obsOutput);\n\tsignal_handler_disconnect(handler, signal, callback, data);\n}\n\nstring RtspOutputHelper::GetOutputName() const\n{\n\treturn string(obs_output_get_name(obsOutput));\n}\n\nuint64_t RtspOutputHelper::GetTotalBytes() const\n{\n\treturn obs_output_get_total_bytes(obsOutput);\n}\n\nint RtspOutputHelper::GetTotalFrames() const\n{\n\treturn obs_output_get_total_frames(obsOutput);\n}\n\nint RtspOutputHelper::GetFramesDropped() const\n{\n\treturn obs_output_get_frames_dropped(obsOutput);\n}\n\nbool RtspOutputHelper::IsActive() const\n{\n\treturn obs_output_active(obsOutput);\n}\n\nvoid RtspOutputHelper::CreateVideoEncoder()\n{\n\tobs_encoder_t *encoder;\n\tif (outputSettings.adv_out)\n\t\t//encoder = obs_get_encoder_by_name(\"streaming_h264\"); //OBS 27.2.4 Or Older\n\t\tencoder = obs_get_encoder_by_name(\"advanced_video_stream\");\n\telse\n\t\t//encoder = obs_get_encoder_by_name(\"simple_h264_stream\"); //OBS 27.2.4 Or Older\n\t\tencoder = obs_get_encoder_by_name(\"simple_video_stream\");\n\tobs_encoder_release(videoEncoder);\n\tvideoEncoder = obs_video_encoder_create(\n\t\tobs_encoder_get_id(encoder), \"rtsp_output_video\",\n\t\tobs_encoder_get_settings(encoder), nullptr);\n\tobs_encoder_release(encoder);\n\tif (outputSettings.adv_out)\n\t\tobs_encoder_set_scaled_size(videoEncoder,\n\t\t\t\t\t    outputSettings.rescale_cx,\n\t\t\t\t\t    outputSettings.rescale_cy);\n\tobs_encoder_set_video(videoEncoder, obs_get_video());\n\t{\n\t\tauto video = obs_output_video(obsOutput);\n\t\tif (video == nullptr)\n\t\t\tvideo = obs_get_video();\n\t\tobs_encoder_set_video(videoEncoder, video);\n\t}\n\tobs_output_set_video_encoder(obsOutput, videoEncoder);\n}\n\nvoid RtspOutputHelper::CreateAudioEncoder()\n{\n\tobs_encoder_t *encoder;\n\tif (outputSettings.adv_out) {\n\t\t/*if ((encoder = obs_get_encoder_by_name(\"adv_stream_aac\")) ==\n\t\t    nullptr)\n\t\t\tencoder = obs_get_encoder_by_name(\n\t\t\t\t\"avc_aac_stream\");*/ //OBS 26.0.2 Or Older\n\t\t/*if ((encoder = obs_get_encoder_by_name(\"adv_stream_audio\")) ==\n\t\t    nullptr) //OBS 30.0.0 Or Older\n\t\tencoder = obs_get_encoder_by_name(\"adv_stream_aac\");*/\n\t\tencoder = obs_get_encoder_by_name(\"adv_stream_audio\");\n\t}\n\telse\n\t\tencoder = obs_get_encoder_by_name(\"simple_aac\");\n\n\tfor (const auto audioEncoder : audioEncoders)\n\t\tobs_encoder_release(audioEncoder);\n\taudioEncoders.clear();\n\n\tconst auto config = rtsp_properties_open_config();\n\n\tauto tracks =\n\t\tstatic_cast<uint8_t>(config_get_uint(config, CONFIG_SECTIION, \"AudioTracks\"));\n\t{\n\t\tauto outputData = GetSettings();\n\t\tif (tracks == 0) {\n\t\t\tobs_data_set_bool(outputData, \"output_audio\", false);\n\t\t\ttracks = 0x1;\n\t\t} else obs_data_set_bool(outputData, \"output_audio\", true);\n\t\tUpdateSettings(outputData);\n\t\tobs_data_release(outputData);\n\t}\n\tauto trackIndex = 0;\n\tfor (auto idx = 0; idx < OBS_OUTPUT_MULTI_TRACK; idx++) {\n\t\tif ((tracks & (1 << idx)) == 0) continue;\n\t\tauto audioEncoder = obs_audio_encoder_create(\n\t\t\tobs_encoder_get_id(encoder),\n\t\t\tstring(\"rtsp_output_audio_track\")\n\t\t\t\t.append(to_string(idx + 1))\n\t\t\t\t.c_str(),\n\t\t\tobs_encoder_get_settings(encoder), idx, nullptr);\n\t\t{\n\t\t\tauto audio = obs_output_audio(obsOutput);\n\t\t\tif (audio == nullptr)\n\t\t\t\taudio = obs_get_audio();\n\t\t\tobs_encoder_set_audio(audioEncoder, audio);\n\t\t}\n\t\taudioEncoders.push_back(audioEncoder);\n\t\tobs_output_set_audio_encoder(obsOutput, audioEncoder,\n\t\t\t\t\t     trackIndex++);\n\t}\n\tconfig_close(config);\n\tobs_encoder_release(encoder);\n}\n\nvoid RtspOutputHelper::GetBaseConfig()\n{\n\tconfig_t *basicConfig = obs_frontend_get_profile_config();\n\tconst char *mode = config_get_string(basicConfig, \"Output\", \"Mode\");\n\toutputSettings.adv_out = astrcmpi(mode, \"Advanced\") == 0;\n\n\toutputSettings.rescale_cx = 0;\n\toutputSettings.rescale_cy = 0;\n\n\tif (outputSettings.adv_out) {\n\t\tconst bool rescale =\n\t\t\tconfig_get_bool(basicConfig, \"AdvOut\", \"Rescale\");\n\n\t\tif (const char *rescaleRes =\n\t\t\tconfig_get_string(basicConfig, \"AdvOut\", \"RescaleRes\"); rescale && rescaleRes && *rescaleRes) {\n\t\t\tif (sscanf(rescaleRes, \"%ux%u\",\n\t\t\t\t   &outputSettings.rescale_cx,\n\t\t\t\t   &outputSettings.rescale_cy) != 2) {\n\t\t\t\toutputSettings.rescale_cx = 0;\n\t\t\t\toutputSettings.rescale_cy = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid RtspOutputHelper::OnPreStartSignal(void *data, calldata_t *cd)\n{\n\tUNUSED_PARAMETER(cd);\n\tconst auto helper = static_cast<RtspOutputHelper *>(data);\n\thelper->UpdateEncoder();\n}\n"
  },
  {
    "path": "rtsp_output_helper.h",
    "content": "#ifndef RTSP_OUTPUT_HELPER_H\n#define RTSP_OUTPUT_HELPER_H\n\n#include <mutex>\n#include <vector>\n#include <obs-module.h>\n\nstruct rtsp_output_settings {\n\tbool adv_out = false;\n\tuint32_t rescale_cx = 0;\n\tuint32_t rescale_cy = 0;\n};\n\nclass RtspOutputHelper {\npublic:\n\tRtspOutputHelper(std::string outputName);\n\t~RtspOutputHelper();\n\tstatic RtspOutputHelper *CreateRtspOutput(obs_data_t *settings, obs_data_t *hotkey);\n\tvoid UpdateSettings(obs_data_t *settings) const;\n\tobs_data_t *GetSettings() const;\n\tvoid UpdateEncoder();\n\tbool Start() const;\n\tvoid Stop() const;\n\tstd::string GetLastError() const;\n\tobs_data_t *HotkeysSave() const;\n\tvoid SignalConnect(const char *signal, signal_callback_t callback,\n\t\t\t   void *data) const;\n\tvoid SignalDisconnect(const char *signal, signal_callback_t callback,\n\t\t\t      void *data) const;\n\tstd::string GetOutputName() const;\n\tuint64_t GetTotalBytes() const;\n\tint GetTotalFrames() const;\n\tint GetFramesDropped() const;\n\tbool IsActive() const;\n\nprivate:\n\tRtspOutputHelper(obs_output_t *obsOutput);\n\tvoid CreateVideoEncoder();\n\tvoid CreateAudioEncoder();\n\tvoid GetBaseConfig();\n\tstatic void OnPreStartSignal(void *data, calldata_t *cd);\n\tobs_output_t *obsOutput;\n\tstruct rtsp_output_settings outputSettings;\n\tobs_encoder_t *videoEncoder = nullptr;\n\tstd::vector<obs_encoder_t *> audioEncoders;\n};\n\n#endif // RTSP_OUTPUT_HELPER_H\n"
  },
  {
    "path": "rtspoutput.rc.in",
    "content": "1 VERSIONINFO\nFILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK}\nBEGIN\n  BLOCK \"StringFileInfo\"\n  BEGIN\n    BLOCK \"040904B0\"\n    BEGIN\n      VALUE \"CompanyName\", \"OBS\"\n      VALUE \"FileDescription\", \"OBS RTSP Server Plugin\"\n      VALUE \"FileVersion\", \"${PROJECT_VERSION}\"\n      VALUE \"InternalName\", \"obs-rtspserver\"\n      VALUE \"OriginalFilename\", \"obs-rtspserver.dll\"\n      VALUE \"ProductName\", \"OBS Studio\"\n      VALUE \"ProductVersion\", \"${OBS_PLUGUIN_VERSION}\"\n      VALUE \"Comments\", \"RTSP server for OBS\"\n    END\n  END\n\n  BLOCK \"VarFileInfo\"\n  BEGIN\n    VALUE \"Translation\", 0x0409, 0x04B0\n  END\nEND\n"
  },
  {
    "path": "threadsafe_queue.h",
    "content": "\n#ifndef __THREADSAFE_QUEUE_H_\n#define __THREADSAFE_QUEUE_H_\n\n\n#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <memory>\n#include <atomic>\n\nusing namespace std;\ntemplate<typename T> class threadsafe_queue\n{\npublic:\n\tthreadsafe_queue(size_t size_limit)\n\t\t: size_limit(size_limit), m_termination(false)\n\t{\n\t}\n\t~threadsafe_queue() = default;\n\n\t/**\n\t * 1. When termination is not called, one element is dequeued every time the \n\t *    queue is called until the queue is empty. This method blocks the thread.\n         * 2. After termination is called, this method will never block. If it is \n         *    already in a blocked state, contact the blocked state.\n         * 3. When true is returned, the value is valid. When false is returned, value \n         *    is invalid. Returns false when termination is called and the queue is empty.\n\t **/\n\n\t//return nullptr if the queue is empty\n\tstd::shared_ptr<T> wait_and_pop()\n\t{\n\t\tunique_lock<mutex> lk(mut);\n\t\tdata_cond.wait(lk, [this] {\n\t\t\treturn !data_queue.empty() ||\n\t\t\t       m_termination.load(memory_order_acquire);\n\t\t});\n\n\t\t//dequeue if not empty\n\t\tif (!data_queue.empty())\n\t\t{\n\t\t\tshared_ptr<T> res = data_queue.front();\n\t\t\tdata_queue.pop();\n\t\t\treturn res;\n\t\t}\n\n\t\t//If the queue is empty, return nullptr\n\t\treturn nullptr;\n\t}\n\n\t//return false if the queue is empty\n\tbool wait_and_pop(T &&value)\n\t{\n\t\tshared_ptr<T> res = wait_and_pop();\n\t\tif (res == nullptr)\n\t\t\treturn false;\n\t\tvalue = std::move(res);\n\t\treturn true;\n\t}\n\n\t//return nullptr if the queue is empty\n\tstd::shared_ptr<T> try_pop()\n\t{\n\t\tlock_guard<mutex> lk(mut);\n\n\t\t//dequeue if not empty\n\t\tif (!data_queue.empty())\n\t\t{\n\t\t\tshared_ptr<T> res = data_queue.front();\n\t\t\tdata_queue.pop();\n\t\t\treturn res;\n\t\t}\n\n\t\t//If the queue is empty, return nullptr\n\t\treturn nullptr;\n\t}\n\n\t//return false if the queue is empty\n\tbool try_pop(T &&value)\n\t{\n\t\tshared_ptr<T> res = try_pop();\n\t\tif (res == nullptr)\n\t\t\treturn false;\n\t\tvalue = std::move(res);\n\t\treturn true;\n\t}\n\n\t//insert queue, move\n\tvoid move_push(T &&new_value)\n\t{\n\t\tif (m_termination.load(memory_order_acquire))\n\t\t\treturn;\n\t\tshared_ptr<T> data(make_shared<T>(std::move(new_value)));\n\t\tunique_lock<mutex> lk(mut);\n\t\tdata_queue.push(data);\n\t\tif (data_queue.size() > size_limit) {\n\t\t\tdata_queue.pop();\n\t\t\tm_dropped_count.fetch_add(1, memory_order_relaxed);\n\t\t}\n\t\tdata_cond.notify_one();\n\t}\n\n\t//insert queue\n\tvoid push(T new_value)\n\t{\n\t\tmove_push(new_value);\n\t}\n\n\tbool empty()\n\t{\n\t\tunique_lock<mutex> lk(mut);\n\t\treturn data_queue.empty();\n\t}\n\n\tsize_t size()\n\t{\n\t\tunique_lock<mutex> lk(mut);\n\t\treturn data_queue.size();\n\t}\n\n\tsize_t dropped_count() const\n\t{\n\t\treturn m_dropped_count.load(memory_order_relaxed);\n\t}\n\n\t//Set this queue to terminated state.\n\t//In the exit state, the enqueue is ignored, and the dequeue can be performed.\n\t//When the queue is empty, wait_and_pop will not block.\n\tvoid termination()\n\t{\n\t\tunique_lock<mutex> lk(mut);\n\t\tm_termination.store(true, memory_order_release);\n\t\tdata_cond.notify_all();\n\t}\n\n\t//Get whether this queue is terminated\n\tbool is_termination() const\n\t{\n\t\treturn m_termination.load(memory_order_acquire);\n\t}\n\nprivate:\n\tmutex mut;\n\tqueue<shared_ptr<T>> data_queue;\n\tconst size_t size_limit;\n\tcondition_variable data_cond;\n\tatomic_bool m_termination;\n\tatomic_size_t m_dropped_count;\n};\n\n#endif\n"
  },
  {
    "path": "ui/rtsp_properties.cpp",
    "content": "#include <obs-frontend-api.h>\n#include <util/config-file.h>\n#include <QMainWindow>\n#include <QClipboard>\n#include <QString>\n#include <QTimer>\n#include <QCloseEvent>\n#include <utility>\n#include \"rtsp_properties.hpp\"\n#include \"ui_rtsp_properties.h\"\n#include \"../helper.h\"\n\nRtspProperties::RtspProperties(std::string rtspOutputName, QWidget *parent)\n\t: QDialog(parent),\n\t  ui(new Ui::RtspProperties),\n\t  statusTimer(new QTimer(this)),\n\t  rtspOutputHelper(new RtspOutputHelper(std::move(rtspOutputName))),\n\t  settings(rtspOutputHelper->GetSettings())\n{\n\tui->setupUi(this);\n\n\tconnect(ui->pushButtonStart, &QPushButton::clicked, this,\n\t\t&RtspProperties::onPushButtonStartClicked);\n\tconnect(ui->pushButtonStop, &QPushButton::clicked, this,\n\t\t&RtspProperties::onPushButtonStopClicked);\n\tconnect(ui->pushButtonAddressCopy, &QPushButton::clicked, this,\n\t\t&RtspProperties::onPushButtonAddressCopyClicked);\n\tconnect(ui->checkBoxEnableMulticast, &QCheckBox::clicked, this,\n\t\t&RtspProperties::onCheckBoxEnableMulticastClicked);\n\tconnect(ui->spinBoxPort,\n\t\tstatic_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),\n\t\tthis, &RtspProperties::onSpinBoxPortValueChanged);\n\tconnect(ui->lineEditUrlSuffix, &QLineEdit::textChanged, this,\n\t\t&RtspProperties::onLineEditUrlSuffixValueChanged);\n\tconnect(ui->checkBoxEnableAuthentication, &QCheckBox::clicked, this,\n\t\t&RtspProperties::onCheckBoxEnableAuthenticationClicked);\n\tconnect(ui->lineEditRealm, &QLineEdit::textChanged, this,\n\t\t&RtspProperties::onLineEditRealmTextChanged);\n\tconnect(ui->lineEditUsername, &QLineEdit::textChanged, this,\n\t\t&RtspProperties::onLineEditUsernameTextChanged);\n\tconnect(ui->lineEditPassword, &QLineEdit::textChanged, this,\n\t\t&RtspProperties::onLineEditPasswordTextChanged);\n\n\tconnect(statusTimer, &QTimer::timeout, this,\n\t\t&RtspProperties::onStatusTimerTimeout);\n\n\tconnect(this, &RtspProperties::setButtonStatus, this,\n\t\t&RtspProperties::onButtonStatusChanging);\n\tconnect(this, &RtspProperties::setStatusTimerStatus, this,\n\t\t&RtspProperties::onStatusTimerStatusChanging);\n\tconnect(this, &RtspProperties::setLabelMessageStatus, this,\n\t\t&RtspProperties::onLabelMessageStatusChanging);\n\n\t//ui->lineEditUrlSuffix->setValidator(new QRegExpValidator(QRegExp(\"^([-A-Za-z0-9+&@#%=~_|]+)(\\\\/[-A-Za-z0-9+&@#%=~_|]+)*$\"), this));\n\n#ifdef VERSION_STRING\n\tui->labelVersion->setText(VERSION_STRING);\n#endif\n\n\tonButtonStatusChanging(!rtspOutputHelper->IsActive(),\n\t                       rtspOutputHelper->IsActive());\n\trtspOutputHelper->SignalConnect(\"start\", OnOutputStart, this);\n\trtspOutputHelper->SignalConnect(\"stop\", OnOutputStop, this);\n\n\t{\n\t\tconst auto config = rtsp_properties_open_config();\n\t\tLoadConfig(config);\n\t\tconfig_close(config);\n\t}\n}\n\nRtspProperties::~RtspProperties()\n{\n\trtspOutputHelper->SignalDisconnect(\"start\", OnOutputStart, this);\n\trtspOutputHelper->SignalDisconnect(\"stop\", OnOutputStop, this);\n\tobs_data_release(settings);\n\tdelete ui;\n\tdelete rtspOutputHelper;\n}\n\nint RtspProperties::exec()\n{\n\tif (const auto host = this->parentWidget(); host) {\n\t\tconst auto hostRect = host->geometry();\n\t\tthis->move(hostRect.center() - this->rect().center());\n\t}\n\treturn QDialog::exec();\n}\n\nvoid RtspProperties::onPushButtonStartClicked()\n{\n\t{\n\t\tconst auto config = rtsp_properties_open_config();\n\t\tSaveConfig(config);\n\t\tconfig_close(config);\n\t}\n\tsetLabelMessageStatus(!rtspOutputHelper->Start());\n}\n\nvoid RtspProperties::onPushButtonStopClicked()\n{\n\trtspOutputHelper->Stop();\n\tsetButtonStatus(false, false);\n}\n\nvoid RtspProperties::onPushButtonAddressCopyClicked() const\n{\n\tQString url = \"rtsp://localhost\";\n\tif (ui->spinBoxPort->value() != 554) {\n\t\turl.append(\":\");\n\t\turl.append(ui->spinBoxPort->text());\n\t}\n\turl.append(\"/\");\n\turl.append(ui->lineEditUrlSuffix->text());\n\tQApplication::clipboard()->setText(url);\n}\n\nvoid RtspProperties::onCheckBoxEnableMulticastClicked(const int checked) const\n{\n\tobs_data_set_bool(settings, \"multicast\", checked);\n}\n\nvoid RtspProperties::onSpinBoxPortValueChanged(const int value) const\n{\n\tobs_data_set_int(settings, \"port\", value);\n}\n\nvoid RtspProperties::onLineEditUrlSuffixValueChanged(const QString &value) const\n{\n\t//auto rx = QRegExp(\"^[-A-Za-z0-9+&@#%=~_|]+(/[-A-Za-z0-9+&@#%=~_|]+)*$\");\n\t//if (!rx.exactMatch(value))\n\t\t//return;\n\tobs_data_set_string(settings, \"url_suffix\",\n\t\t\t    value.toStdString().c_str());\n}\n\nvoid RtspProperties::onCheckBoxEnableAuthenticationClicked(const bool checked) const\n{\n\tobs_data_set_bool(settings, \"authentication\", checked);\n}\n\nvoid RtspProperties::onLineEditRealmTextChanged(const QString &value) const\n{\n\tobs_data_set_string(settings, \"authentication_realm\",\n\t\t\t    value.toStdString().c_str());\n}\n\nvoid RtspProperties::onLineEditUsernameTextChanged(const QString &value) const\n{\n\tobs_data_set_string(settings, \"authentication_username\",\n\t\t\t    value.toStdString().c_str());\n}\n\nvoid RtspProperties::onLineEditPasswordTextChanged(const QString &value) const\n{\n\tobs_data_set_string(settings, \"authentication_password\",\n\t\t\t    value.toStdString().c_str());\n}\n\nvoid RtspProperties::onStatusTimerTimeout()\n{\n\tconst auto totalBytes = rtspOutputHelper->GetTotalBytes();\n\tconst auto totalFrames = rtspOutputHelper->GetTotalFrames();\n\tconst auto framesDropped = rtspOutputHelper->GetFramesDropped();\n\tconst auto bitps = (totalBytes - lastTotalBytes) * 8;\n\tlastTotalBytes = totalBytes;\n\tui->labelTotalData->setText(\n\t\trtsp_properties_get_data_volume_display(totalBytes).c_str());\n\tui->labelBitrate->setText(QString(\"%1 kb/s\").arg(\n\t\tbitps / 1000.0, 0, 'f', 0));\n\tui->labelFramesDropped->setText(\n\t\tQString(\"%1 / %2 (%3%)\")\n\t\t.arg(framesDropped)\n\t\t.arg(totalFrames)\n\t\t.arg(totalFrames == 0 ? 0\n\t\t\t\t     : framesDropped * 100.0 / totalFrames,\n\t\t\t     0, 'f', 1));\n}\n\nvoid RtspProperties::onButtonStatusChanging(const bool outputStarted,\n                                            const bool outputStopped) const\n{\n\tui->checkBoxEnableMulticast->setEnabled(outputStarted);\n\tui->spinBoxPort->setEnabled(outputStarted);\n\tui->lineEditUrlSuffix->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack1->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack2->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack3->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack4->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack5->setEnabled(outputStarted);\n\tui->checkBoxAudioTrack6->setEnabled(outputStarted);\n\tui->pushButtonStart->setEnabled(outputStarted);\n\tui->pushButtonStop->setEnabled(outputStopped);\n}\n\nvoid RtspProperties::onStatusTimerStatusChanging(const bool start)\n{\n\tif (start) {\n\t\tlastTotalBytes = 0;\n\t\tstatusTimer->start(1000);\n\t} else {\n\t\tstatusTimer->stop();\n\t\tui->labelTotalData->setText(\"0.0 MB\");\n\t\tui->labelBitrate->setText(\"0 kb/s\");\n\t\tui->labelFramesDropped->setText(\"0 / 0 (0.0%)\");\n\t}\n}\n\nvoid RtspProperties::onLabelMessageStatusChanging(const bool showError) const\n{\n\tif (showError)\n\t\tui->labelMessage->setText(\n\t\t\tQString(rtspOutputHelper->GetLastError().c_str()));\n\telse\n\t\tui->labelMessage->setText(\"\");\n}\n\nvoid RtspProperties::showEvent(QShowEvent *event)\n{\n\tUNUSED_PARAMETER(event);\n\tui->checkBoxEnableMulticast->blockSignals(true);\n\tui->checkBoxEnableMulticast->setChecked(\n\t\tobs_data_get_bool(settings, \"multicast\"));\n\tui->checkBoxEnableMulticast->blockSignals(false);\n\n\tui->spinBoxPort->blockSignals(true);\n\tui->spinBoxPort->setValue(obs_data_get_int(settings, \"port\"));\n\tui->spinBoxPort->blockSignals(false);\n\n\tui->lineEditUrlSuffix->blockSignals(true);\n\tui->lineEditUrlSuffix->setText(\n\t\tstd::string(obs_data_get_string(settings, \"url_suffix\")).c_str());\n\tui->lineEditUrlSuffix->blockSignals(false);\n\n\tconst auto realm = std::string(\n\t\tobs_data_get_string(settings, \"authentication_realm\"));\n\tconst auto username = std::string(\n\t\tobs_data_get_string(settings, \"authentication_username\"));\n\tconst auto password = std::string(\n\t\tobs_data_get_string(settings, \"authentication_password\"));\n\n\tauto enbledAuth = false;\n\tif (!realm.empty() && !username.empty())\n\t\tenbledAuth = obs_data_get_bool(settings, \"authentication\");\n\n\tui->checkBoxEnableAuthentication->setChecked(enbledAuth);\n\n\tui->lineEditRealm->blockSignals(true);\n\tui->lineEditRealm->setText(realm.c_str());\n\tui->lineEditRealm->blockSignals(false);\n\n\tui->lineEditUsername->blockSignals(true);\n\tui->lineEditUsername->setText(username.c_str());\n\tui->lineEditUsername->blockSignals(false);\n\n\tui->lineEditPassword->blockSignals(true);\n\tui->lineEditPassword->setText(password.c_str());\n\tui->lineEditPassword->blockSignals(false);\n}\n\nvoid RtspProperties::closeEvent(QCloseEvent *event)\n{\n\tUNUSED_PARAMETER(event);\n\tif (this->isHidden())\n\t\treturn;\n\t{\n\t\tconst auto config = rtsp_properties_open_config();\n\t\tSaveConfig(config);\n\t\tconfig_close(config);\n\t}\n\trtspOutputHelper->UpdateSettings(settings);\n}\n\nvoid RtspProperties::OnOutputStart(void *data, calldata_t *cd)\n{\n\tUNUSED_PARAMETER(cd);\n\tauto page = static_cast<RtspProperties *>(data);\n\tpage->setButtonStatus(false, true);\n\tpage->setStatusTimerStatus(true);\n}\n\nvoid RtspProperties::OnOutputStop(void *data, calldata_t *cd)\n{\n\tconst auto page = static_cast<RtspProperties *>(data);\n\tif (const auto code = calldata_int(cd, \"code\");\n\t    code != OBS_OUTPUT_SUCCESS)\n\t\tpage->setLabelMessageStatus(true);\n\tpage->setButtonStatus(true, false);\n\tpage->setStatusTimerStatus(false);\n}\n\nvoid RtspProperties::LoadConfig(config_t *config) const\n{\n\tui->checkBoxAuto->setChecked(\n\t\tconfig_get_bool(config, CONFIG_SECTIION, \"AutoStart\"));\n\n\t{\n\t\tauto tracks =\n\t\t\tconfig_get_uint(config, CONFIG_SECTIION, \"AudioTracks\");\n\t\tui->checkBoxAudioTrack1->setChecked(tracks & (1 << 0));\n\t\tui->checkBoxAudioTrack2->setChecked(tracks & (1 << 1));\n\t\tui->checkBoxAudioTrack3->setChecked(tracks & (1 << 2));\n\t\tui->checkBoxAudioTrack4->setChecked(tracks & (1 << 3));\n\t\tui->checkBoxAudioTrack5->setChecked(tracks & (1 << 4));\n\t\tui->checkBoxAudioTrack6->setChecked(tracks & (1 << 5));\n\t}\n}\n\nvoid RtspProperties::SaveConfig(config_t *config) const\n{\n\tif (!config)\n\t\treturn;\n\tconfig_set_bool(config, CONFIG_SECTIION, \"AutoStart\",\n\t\t\tui->checkBoxAuto->isChecked());\n\n\t{\n\t\tuint64_t tracks = 0;\n\t\ttracks |= ui->checkBoxAudioTrack1->isChecked() ? (1 << 0) : 0;\n\t\ttracks |= ui->checkBoxAudioTrack2->isChecked() ? (1 << 1) : 0;\n\t\ttracks |= ui->checkBoxAudioTrack3->isChecked() ? (1 << 2) : 0;\n\t\ttracks |= ui->checkBoxAudioTrack4->isChecked() ? (1 << 3) : 0;\n\t\ttracks |= ui->checkBoxAudioTrack5->isChecked() ? (1 << 4) : 0;\n\t\ttracks |= ui->checkBoxAudioTrack6->isChecked() ? (1 << 5) : 0;\n\t\tconfig_set_uint(config, CONFIG_SECTIION, \"AudioTracks\", tracks);\n\t}\n\tconfig_save(config);\n}\n"
  },
  {
    "path": "ui/rtsp_properties.hpp",
    "content": "#ifndef RTSP_PROPERTIES_H\n#define RTSP_PROPERTIES_H\n\n#include <QDialog>\n#include <obs-module.h>\n#include <obs-frontend-api.h>\n#include \"../rtsp_output_helper.h\"\n\nnamespace Ui {\nclass RtspProperties;\n}\n\nclass RtspProperties : public QDialog {\n\tQ_OBJECT\n\npublic:\n\texplicit RtspProperties(std::string rtspOutputName, QWidget *parent = 0);\n\t~RtspProperties();\n\npublic Q_SLOTS:\n\tvirtual int exec();\n\nprivate Q_SLOTS:\n\tvoid onPushButtonStartClicked();\n\tvoid onPushButtonStopClicked();\n\tvoid onPushButtonAddressCopyClicked() const;\n\tvoid onCheckBoxEnableMulticastClicked(int checked) const;\n\tvoid onSpinBoxPortValueChanged(int value) const;\n\tvoid onLineEditUrlSuffixValueChanged(const QString &value) const;\n\tvoid onCheckBoxEnableAuthenticationClicked(bool checked) const;\n\tvoid onLineEditRealmTextChanged(const QString &value) const;\n\tvoid onLineEditUsernameTextChanged(const QString &value) const;\n\tvoid onLineEditPasswordTextChanged(const QString &value) const;\n\n\tvoid onStatusTimerTimeout();\n\n\tvoid onButtonStatusChanging(bool outputStarted, bool outputStopped) const;\n\tvoid onStatusTimerStatusChanging(bool start);\n\tvoid onLabelMessageStatusChanging(bool showError) const;\n\nQ_SIGNALS:\n\tvoid setButtonStatus(bool outputStarted, bool outputStopped);\n\tvoid setStatusTimerStatus(bool start);\n\tvoid setLabelMessageStatus(bool showError);\n\nprivate:\n\tUi::RtspProperties *ui;\n\tQTimer *statusTimer;\n\n\tsignal_handler_t *signalHandler;\n\tRtspOutputHelper *rtspOutputHelper;\n\n\tuint64_t lastTotalBytes;\n\n\tobs_data_t *settings;\n\n\tvoid showEvent(QShowEvent *event);\n\tvoid closeEvent(QCloseEvent *event);\n\n\tstatic void OnOutputStart(void *data, calldata_t *cd);\n\tstatic void OnOutputStop(void *data, calldata_t *cd);\n\n\tvoid LoadConfig(config_t *config) const;\n\tvoid SaveConfig(config_t *config) const;\n};\n\n#endif // RTSP_PROPERTIES_H\n"
  },
  {
    "path": "ui/rtsp_properties.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>RtspProperties</class>\n <widget class=\"QDialog\" name=\"RtspProperties\">\n  <property name=\"windowTitle\">\n   <string>RtspServer.Properties</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"rtspProperties_layout\">\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBoxOption\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"title\">\n      <string>RtspServer.Properties.Options</string>\n     </property>\n     <layout class=\"QFormLayout\" name=\"groupBoxOption_layout\">\n      <property name=\"fieldGrowthPolicy\">\n       <enum>QFormLayout::AllNonFixedFieldsGrow</enum>\n      </property>\n      <property name=\"labelAlignment\">\n       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n      </property>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QCheckBox\" name=\"checkBoxAuto\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Options.AutoStart</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelOutputOptions\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Options.Output</string>\n        </property>\n        <property name=\"buddy\">\n         <cstring>labelOutputOptionsTip</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"1\">\n       <widget class=\"QLabel\" name=\"labelOutputOptionsTip\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Options.Output.Tip</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelEnabledAudioTracks\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Options.EnabledAudioTracks</string>\n        </property>\n        <property name=\"buddy\">\n         <cstring>widgetEnabledAudioTracks</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"1\">\n       <widget class=\"QWidget\" name=\"widgetEnabledAudioTracks\" native=\"true\">\n        <layout class=\"QHBoxLayout\" name=\"widgetEnabledAudioTracks_layout\">\n         <property name=\"leftMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>0</number>\n         </property>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack1\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>1</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack2\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>2</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack3\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>3</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack4\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>4</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack5\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>5</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QCheckBox\" name=\"checkBoxAudioTrack6\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>6</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <spacer name=\"widgetEnabledAudioSpacer\">\n           <property name=\"orientation\">\n            <enum>Qt::Horizontal</enum>\n           </property>\n           <property name=\"sizeHint\" stdset=\"0\">\n            <size>\n             <width>0</width>\n             <height>0</height>\n            </size>\n           </property>\n          </spacer>\n         </item>\n        </layout>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBoxTarget\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"title\">\n      <string>RtspServer.Properties.Target</string>\n     </property>\n     <layout class=\"QFormLayout\" name=\"groupBoxTarget_layout\">\n      <property name=\"fieldGrowthPolicy\">\n       <enum>QFormLayout::AllNonFixedFieldsGrow</enum>\n      </property>\n      <property name=\"labelAlignment\">\n       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n      </property>\n      <item row=\"2\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelAddress\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"maximumSize\">\n         <size>\n          <width>80</width>\n          <height>16777215</height>\n         </size>\n        </property>\n        <property name=\"layoutDirection\">\n         <enum>Qt::RightToLeft</enum>\n        </property>\n        <property name=\"text\">\n         <string>RtspServer.Properties.Target.Address</string>\n        </property>\n        <property name=\"alignment\">\n         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n        </property>\n        <property name=\"buddy\">\n         <cstring>widgetAddress</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"2\" column=\"1\">\n       <widget class=\"QWidget\" name=\"widgetAddress\" native=\"true\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <layout class=\"QHBoxLayout\" name=\"widgetAddress_layout\">\n         <property name=\"leftMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>0</number>\n         </property>\n         <item>\n          <widget class=\"QLabel\" name=\"labelAddressPrefix\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>rtsp://localhost:</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QSpinBox\" name=\"spinBoxPort\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimum\">\n            <number>1</number>\n           </property>\n           <property name=\"maximum\">\n            <number>65535</number>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLabel\" name=\"labelAddressSplit\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>/</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLineEdit\" name=\"lineEditUrlSuffix\"/>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"pushButtonAddressCopy\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>RtspServer.Properties.Target.Address.Copy</string>\n           </property>\n          </widget>\n         </item>\n        </layout>\n        <zorder>pushButtonAddressCopy</zorder>\n        <zorder>labelAddressSplit</zorder>\n        <zorder>spinBoxPort</zorder>\n        <zorder>labelAddressPrefix</zorder>\n        <zorder>lineEditUrlSuffix</zorder>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QCheckBox\" name=\"checkBoxEnableMulticast\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Target.Multicast</string>\n        </property>\n       </widget>\n      </item>\n     </layout>\n     <zorder>widgetAddress</zorder>\n     <zorder>labelAddress</zorder>\n     <zorder>checkBoxEnableMulticast</zorder>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBoxAuthentication\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"title\">\n      <string>RtspServer.Properties.Authentication</string>\n     </property>\n     <layout class=\"QFormLayout\" name=\"groupBoxAuthentication_layout\">\n      <property name=\"fieldGrowthPolicy\">\n       <enum>QFormLayout::AllNonFixedFieldsGrow</enum>\n      </property>\n      <property name=\"labelAlignment\">\n       <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>\n      </property>\n      <item row=\"0\" column=\"1\">\n       <widget class=\"QCheckBox\" name=\"checkBoxEnableAuthentication\">\n        <property name=\"text\">\n         <string>RtspServer.Properties.Authentication.Enabled</string>\n        </property>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditRealm\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"clearButtonEnabled\">\n         <bool>true</bool>\n        </property>\n       </widget>\n      </item>\n      <item row=\"1\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelRealm\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"text\">\n         <string>RtspServer.Properties.Authentication.Realm</string>\n        </property>\n        <property name=\"buddy\">\n         <cstring>lineEditRealm</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelUsername\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"layoutDirection\">\n         <enum>Qt::LeftToRight</enum>\n        </property>\n        <property name=\"text\">\n         <string>RtspServer.Properties.Authentication.Username</string>\n        </property>\n        <property name=\"buddy\">\n         <cstring>lineEditUsername</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"3\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditUsername\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"clearButtonEnabled\">\n         <bool>true</bool>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"0\">\n       <widget class=\"QLabel\" name=\"labelPassword\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"layoutDirection\">\n         <enum>Qt::RightToLeft</enum>\n        </property>\n        <property name=\"text\">\n         <string>RtspServer.Properties.Authentication.Password</string>\n        </property>\n        <property name=\"buddy\">\n         <cstring>lineEditPassword</cstring>\n        </property>\n       </widget>\n      </item>\n      <item row=\"5\" column=\"1\">\n       <widget class=\"QLineEdit\" name=\"lineEditPassword\">\n        <property name=\"enabled\">\n         <bool>false</bool>\n        </property>\n        <property name=\"echoMode\">\n         <enum>QLineEdit::PasswordEchoOnEdit</enum>\n        </property>\n        <property name=\"placeholderText\">\n         <string>RtspServer.Properties.Authentication.PasswordPlaceholder</string>\n        </property>\n        <property name=\"clearButtonEnabled\">\n         <bool>true</bool>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGroupBox\" name=\"groupBoxStatus\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Fixed\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <property name=\"title\">\n      <string>RtspServer.Properties.Status</string>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"status_layout\">\n      <item>\n       <widget class=\"QWidget\" name=\"widgetStatus\" native=\"true\">\n        <layout class=\"QGridLayout\" name=\"widgetStatus_layout\">\n         <property name=\"leftMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>0</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>0</number>\n         </property>\n         <item row=\"1\" column=\"3\">\n          <widget class=\"QLabel\" name=\"labelBitrate\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimumSize\">\n            <size>\n             <width>60</width>\n             <height>0</height>\n            </size>\n           </property>\n           <property name=\"text\">\n            <string>0kb/s</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"1\" column=\"0\">\n          <widget class=\"QLabel\" name=\"labelTotalDataTitle\">\n           <property name=\"text\">\n            <string>RtspServer.Properties.Status.TotalDataSent</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"1\" column=\"2\">\n          <widget class=\"QLabel\" name=\"labelBitrateTitle\">\n           <property name=\"text\">\n            <string>RtspServer.Properties.Status.Bitrate</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"1\" column=\"1\">\n          <widget class=\"QLabel\" name=\"labelTotalData\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimumSize\">\n            <size>\n             <width>60</width>\n             <height>0</height>\n            </size>\n           </property>\n           <property name=\"text\">\n            <string>0.0 MB</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"2\" column=\"0\">\n          <widget class=\"QLabel\" name=\"labelFramesDroppedTitle\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>RtspServer.Properties.Status.DroppedFrames</string>\n           </property>\n          </widget>\n         </item>\n         <item row=\"2\" column=\"1\">\n          <widget class=\"QLabel\" name=\"labelFramesDropped\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimumSize\">\n            <size>\n             <width>60</width>\n             <height>0</height>\n            </size>\n           </property>\n           <property name=\"text\">\n            <string>0 / 0 (0.0%)</string>\n           </property>\n          </widget>\n         </item>\n        </layout>\n        <zorder>labelBitrate</zorder>\n        <zorder>labelBitrateTitle</zorder>\n        <zorder>labelTotalData</zorder>\n        <zorder>labelTotalDataTitle</zorder>\n        <zorder>labelFramesDroppedTitle</zorder>\n        <zorder>labelFramesDropped</zorder>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QLabel\" name=\"labelMessage\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Ignored\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"minimumSize\">\n         <size>\n          <width>0</width>\n          <height>12</height>\n         </size>\n        </property>\n        <property name=\"styleSheet\">\n         <string notr=\"true\">QLabel { color : red; }</string>\n        </property>\n        <property name=\"margin\">\n         <number>0</number>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <spacer name=\"RtspPropertiesverSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>0</width>\n       <height>0</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayoutBottom\">\n     <property name=\"leftMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"topMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"rightMargin\">\n      <number>0</number>\n     </property>\n     <property name=\"bottomMargin\">\n      <number>0</number>\n     </property>\n     <item>\n      <widget class=\"QLabel\" name=\"labelVersionTitle\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Preferred\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <property name=\"text\">\n        <string>RtspServer.Properties.Version</string>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignBottom|Qt::AlignRight|Qt::AlignTrailing</set>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QLabel\" name=\"labelVersion\">\n       <property name=\"text\">\n        <string/>\n       </property>\n       <property name=\"alignment\">\n        <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QWidget\" name=\"widgetStartOrStop\" native=\"true\">\n       <property name=\"sizePolicy\">\n        <sizepolicy hsizetype=\"Fixed\" vsizetype=\"Fixed\">\n         <horstretch>0</horstretch>\n         <verstretch>0</verstretch>\n        </sizepolicy>\n       </property>\n       <layout class=\"QHBoxLayout\" name=\"widgetStartOrStop_layout\">\n        <property name=\"leftMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"topMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"rightMargin\">\n         <number>0</number>\n        </property>\n        <property name=\"bottomMargin\">\n         <number>0</number>\n        </property>\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonStart\">\n          <property name=\"minimumSize\">\n           <size>\n            <width>75</width>\n            <height>23</height>\n           </size>\n          </property>\n          <property name=\"text\">\n           <string>RtspServer.Properties.StartOutput</string>\n          </property>\n          <property name=\"default\">\n           <bool>true</bool>\n          </property>\n          <property name=\"flat\">\n           <bool>false</bool>\n          </property>\n         </widget>\n        </item>\n        <item>\n         <widget class=\"QPushButton\" name=\"pushButtonStop\">\n          <property name=\"minimumSize\">\n           <size>\n            <width>75</width>\n            <height>23</height>\n           </size>\n          </property>\n          <property name=\"text\">\n           <string>RtspServer.Properties.StopOutput</string>\n          </property>\n          <property name=\"iconSize\">\n           <size>\n            <width>16</width>\n            <height>16</height>\n           </size>\n          </property>\n          <property name=\"autoDefault\">\n           <bool>true</bool>\n          </property>\n          <property name=\"default\">\n           <bool>true</bool>\n          </property>\n          <property name=\"flat\">\n           <bool>false</bool>\n          </property>\n         </widget>\n        </item>\n       </layout>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n  <zorder>groupBoxTarget</zorder>\n  <zorder>groupBoxOption</zorder>\n  <zorder>groupBoxAuthentication</zorder>\n  <zorder>groupBoxStatus</zorder>\n  <zorder>RtspPropertiesverSpacer</zorder>\n </widget>\n <tabstops>\n  <tabstop>checkBoxAuto</tabstop>\n  <tabstop>checkBoxAudioTrack1</tabstop>\n  <tabstop>checkBoxAudioTrack2</tabstop>\n  <tabstop>checkBoxAudioTrack3</tabstop>\n  <tabstop>checkBoxAudioTrack4</tabstop>\n  <tabstop>checkBoxAudioTrack5</tabstop>\n  <tabstop>checkBoxAudioTrack6</tabstop>\n  <tabstop>checkBoxEnableMulticast</tabstop>\n  <tabstop>spinBoxPort</tabstop>\n  <tabstop>lineEditUrlSuffix</tabstop>\n  <tabstop>pushButtonAddressCopy</tabstop>\n  <tabstop>checkBoxEnableAuthentication</tabstop>\n  <tabstop>lineEditRealm</tabstop>\n  <tabstop>lineEditUsername</tabstop>\n  <tabstop>lineEditPassword</tabstop>\n  <tabstop>pushButtonStart</tabstop>\n  <tabstop>pushButtonStop</tabstop>\n </tabstops>\n <resources/>\n <connections>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>labelUsername</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>552</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>93</x>\n     <y>263</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>labelPassword</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>552</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>105</x>\n     <y>294</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>lineEditUsername</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>552</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>552</x>\n     <y>263</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>lineEditPassword</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>552</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>552</x>\n     <y>294</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>lineEditRealm</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>520</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>660</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n  <connection>\n   <sender>checkBoxEnableAuthentication</sender>\n   <signal>toggled(bool)</signal>\n   <receiver>labelRealm</receiver>\n   <slot>setEnabled(bool)</slot>\n   <hints>\n    <hint type=\"sourcelabel\">\n     <x>520</x>\n     <y>207</y>\n    </hint>\n    <hint type=\"destinationlabel\">\n     <x>112</x>\n     <y>232</y>\n    </hint>\n   </hints>\n  </connection>\n </connections>\n</ui>\n"
  }
]