diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 74d496d..610342d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,17 +14,17 @@ on: env: PRODUCT_NAME: 'obs-rtspserver' - DEPS_VERSION_MAC: '2023-01-06' - DEPS_HASH_MAC: '811c022a178230d3b9ad1eeb8ae9e1de0e6017b69364f50b183e36246509e0e8' + DEPS_VERSION_MAC: '2023-11-03' + DEPS_HASH_MAC: '90c2fc069847ec2768dcc867c1c63b112c615ed845a907dc44acab7a97181974' #QT_VERSION_MAC: '2022-02-13' - QT_HASH_MAC: 'dfa3c48ee46765029d06773ba08d84b3f11360547171c8e736019c77fabff80d' - DEPS_VERSION_WIN: '2023-03-06' - DEPS_X64_HASH_WIN: '0498193e483826f0736a3871b71240b66d40656f181a24aec54017a724790d15' - DEPS_X86_HASH_WIN: 'dd5d5ead658916207350a2554715cc1944e048a7ad1d6b6a60607c44d457d13b' - QT_X64_HASH_WIN: 'd4a1fa152011222adfbdcc23f6324f001f6308184559a1655974651941218c7b' - QT_X86_HASH_WIN: '22343a90567ab7329a4d7228db8303f2b384a895e6d5ad7ca3086247d8c58caf' + QT_HASH_MAC: 'ba4a7152848da0053f63427a2a2cb0a199af3992997c0db08564df6f48c9db98' + DEPS_VERSION_WIN: '2023-11-03' + DEPS_X64_HASH_WIN: 'd0825a6fb65822c993a3059edfba70d72d2e632ef74893588cf12b1f0d329ce6' + DEPS_X86_HASH_WIN: 'b69c864275189464483c240ef3f25ea16fba3050b136fe6c3db6e9ee63036683' + QT_X64_HASH_WIN: 'bc57dedf76b47119a6dce0435a2f21b35b08c8f2948b1cb34a157320f77732d1' + QT_X86_HASH_WIN: '50129f9836ef987c23db2e0535085fa2d52474ef0de44bc11c9df6cfa602b785' #QT_VERSION_WIN: '5.15.2' - NSIS_VERSION_WIN: '3.08' + NSIS_VERSION_WIN: '3.09' jobs: get_obs_info: name: '01 - Get obs-studio last release info' @@ -42,11 +42,11 @@ jobs: steps: - name: Get latest release info id: latest_release - uses: kaliber5/action-get-release@v1 + uses: cardinalby/git-get-release-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - token: ${{ secrets.GITHUB_TOKEN }} - owner: 'obsproject' - repo: 'obs-studio' + repo: 'obsproject/obs-studio' latest: true get_plugin_info: @@ -56,7 +56,7 @@ jobs: git_tag_name: ${{ steps.tag_name.outputs.tag }} steps: - name: 'Get plugin git tag' - uses: devops-actions/action-get-tag@v1.0.1 + uses: devops-actions/action-get-tag@v1.0.2 id: tag_name clang_check: @@ -65,7 +65,7 @@ jobs: needs: [get_plugin_info] steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' @@ -82,7 +82,7 @@ jobs: windows_build: name: '03 - Windows (Latest)' - runs-on: [windows-2019] + runs-on: [windows-2022] needs: [get_obs_info, get_plugin_info, clang_check] strategy: matrix: @@ -96,14 +96,14 @@ jobs: uses: microsoft/setup-msbuild@v1 - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' @@ -128,14 +128,14 @@ jobs: run: CI/windows/04_package_plugin.ps1 -BuildArch "${{ matrix.arch }}-bit" - name: 'Upload build Artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-${{ matrix.arch }}' path: '${{ github.workspace }}/plugin/*-windows-*.zip' windows_package: name: '04 - Windows Installer' - runs-on: [windows-2019] + runs-on: [windows-2022] needs: [get_plugin_info, windows_build] env: #CMAKE_GENERATOR: "Visual Studio 17 2022" @@ -143,20 +143,20 @@ jobs: OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Download 64-bit artifact' - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-64' path: 'plugin' - name: 'Download 32-bit artifact' - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-32' path: 'plugin' @@ -173,7 +173,7 @@ jobs: CI/windows/04_package_plugin.ps1 -BuildArch 64-bit -BuildInstaller -CombinedArchs - name: 'Upload build Artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-windows-release' path: '${{ github.workspace }}/plugin/${{ env.PRODUCT_NAME }}-*-windows-all*.*' @@ -184,7 +184,8 @@ jobs: needs: [get_obs_info, get_plugin_info, clang_check] strategy: matrix: - ubuntu: ['ubuntu-20.04', 'ubuntu-22.04'] + #ubuntu: ['ubuntu-20.04', 'ubuntu-22.04'] + ubuntu: ['ubuntu-22.04'] env: OBS_VERSION: "${{ needs.get_obs_info.outputs.latest_tag_name }}" defaults: @@ -192,14 +193,14 @@ jobs: shell: bash steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' @@ -224,7 +225,7 @@ jobs: run: source CI/linux/04_package_plugin.sh - name: 'Upload build Artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-linux-${{ matrix.ubuntu }}' path: '${{ github.workspace }}/plugin/build/${{ env.PRODUCT_NAME }}-*-linux.*' @@ -245,14 +246,14 @@ jobs: shell: bash steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'plugin' ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' submodules: 'recursive' - name: 'Checkout OBS v${{ needs.get_obs_info.outputs.latest_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: obsproject/obs-studio path: 'obs-studio' @@ -289,7 +290,7 @@ jobs: run: source CI/macos/04_package_plugin.sh --architecture "${{ matrix.arch }}" - name: 'Upload build Artifact' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: '${{ env.PRODUCT_NAME }}-macos-${{ matrix.arch }}' path: '${{ github.workspace }}/plugin/build/*-macos-${{ matrix.arch }}.*' @@ -303,21 +304,22 @@ jobs: steps: - name: 'Checkout plugin ${{ needs.get_plugin_info.outputs.git_tag_name }}' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: '${{ needs.get_plugin_info.outputs.git_tag_name }}' fetch-depth: 0 - name: 'Get last release info' id: get_last_release - uses: kaliber5/action-get-release@v1 + uses: cardinalby/git-get-release-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - token: ${{ secrets.GITHUB_TOKEN }} latest: true - name: 'Create changelog text' id: create_changelog_text - uses: dlavrenuek/conventional-changelog-action@v1.2.2 + uses: dlavrenuek/conventional-changelog-action@v1.2.3 with: from: "${{ steps.get_last_release.outputs.tag_name }}" to: "${{ needs.get_plugin_info.outputs.git_tag_name }}" @@ -349,7 +351,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: 'Download release artifacts' - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: 'Upload Windows .zip artifact to release' uses: shogo82148/actions-upload-release-asset@v1 @@ -369,14 +371,14 @@ jobs: asset_content_type: application/x-msdownload github_token: ${{ secrets.GITHUB_TOKEN }} - - name: 'Upload linux qt5 .tar.gz artifact to release' - uses: shogo82148/actions-upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz - asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.tar.gz - asset_content_type: application/x-gzip - github_token: ${{ secrets.GITHUB_TOKEN }} + #- name: 'Upload linux qt5 .tar.gz artifact to release' + # uses: shogo82148/actions-upload-release-asset@v1 + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.tar.gz + # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.tar.gz + # asset_content_type: application/x-gzip + # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .tar.gz artifact to release' uses: shogo82148/actions-upload-release-asset@v1 @@ -387,14 +389,14 @@ jobs: asset_content_type: application/x-gzip github_token: ${{ secrets.GITHUB_TOKEN }} - - name: 'Upload linux qt5 .deb artifact to release' - uses: shogo82148/actions-upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb - asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.deb - asset_content_type: application/vnd.debian.binary-package - github_token: ${{ secrets.GITHUB_TOKEN }} + # - name: 'Upload linux qt5 .deb artifact to release' + # uses: shogo82148/actions-upload-release-asset@v1 + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.deb + # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.deb + # asset_content_type: application/vnd.debian.binary-package + # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .deb artifact to release' uses: shogo82148/actions-upload-release-asset@v1 @@ -405,20 +407,20 @@ jobs: asset_content_type: application/vnd.debian.binary-package github_token: ${{ secrets.GITHUB_TOKEN }} - - name: 'Upload linux qt5 .rpm artifact to release' - uses: shogo82148/actions-upload-release-asset@v1 - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm - asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.rpm - asset_content_type: application/vnd.debian.binary-package - github_token: ${{ secrets.GITHUB_TOKEN }} + # - name: 'Upload linux qt5 .rpm artifact to release' + # uses: shogo82148/actions-upload-release-asset@v1 + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm + # asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt5.rpm + # asset_content_type: application/vnd.debian.binary-package + # github_token: ${{ secrets.GITHUB_TOKEN }} - name: 'Upload linux qt6 .rpm artifact to release' uses: shogo82148/actions-upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-20.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm + asset_path: ${{ github.workspace }}/${{ env.PRODUCT_NAME }}-linux-ubuntu-22.04/obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux.rpm asset_name: obs-rtspserver-${{ needs.get_plugin_info.outputs.git_tag_name }}-linux-qt6.rpm asset_content_type: application/vnd.debian.binary-package github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/CI/macos/04_package_plugin.sh b/CI/macos/04_package_plugin.sh index e90ec65..14b9011 100644 --- a/CI/macos/04_package_plugin.sh +++ b/CI/macos/04_package_plugin.sh @@ -33,13 +33,13 @@ package_obs_plugin() { if ! type packagesbuild &>/dev/null; then status "Setting up dependency Packages.app" step "Download..." - check_and_fetch "http://s.sudre.free.fr/Software/files/Packages.dmg" "6afdd25386295974dad8f078b8f1e41cabebd08e72d970bf92f707c7e48b16c9" + check_and_fetch "http://s.sudre.free.fr/Software/files/Packages.dmg" "9d9a73a64317ea6697a380014d2e5c8c8188b59d5fb8ce8872e56cec06cd78e8" step "Mount disk image..." hdiutil attach -noverify Packages.dmg step "Install Packages.app" PACKAGES_VOLUME=$(hdiutil info -plist | grep "/Volumes/Packages" | sed 's/\/Volumes\/\([^<]*\)<\/string>/\1/' | sed -e 's/^[[:space:]]*//') - sudo installer -pkg "/Volumes/${PACKAGES_VOLUME}/packages/Packages.pkg" -target / + sudo installer -pkg "/Volumes/${PACKAGES_VOLUME}/Install Packages.pkg" -target / hdiutil detach "/Volumes/${PACKAGES_VOLUME}" fi diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index aceaa98..05e549b 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(Optional)" RtspServer.Properties.Status="Status" RtspServer.Properties.Status.TotalDataSent="Gesamtdatenausgabe: " RtspServer.Properties.Status.Bitrate="Bitrate: " +RtspServer.Properties.Status.DroppedFrames="Ausgelassene Frames: " RtspServer.Properties.Version="Version: " RtspOutput="RTSP Ausgabe" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 0aa441d..e2ece21 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(Optional)" RtspServer.Properties.Status="Status" RtspServer.Properties.Status.TotalDataSent="Total Data Output: " RtspServer.Properties.Status.Bitrate="Bitrate: " +RtspServer.Properties.Status.DroppedFrames="Dropped Frames: " RtspServer.Properties.Version="Version: " RtspOutput="RTSP Output" @@ -35,6 +36,7 @@ RtspOutput.Hotkey.StopOutput="Stop Output" RtspOutput.Properties.Multicast="Enabled Multicast" RtspOutput.Properties.Port="Port" RtspOutput.Properties.UrlSuffix="URL Suffix" +RtspOutput.Properties.OutputAudio="Enable Audio Output" RtspOutput.Properties.Authentication="Authentication" RtspOutput.Properties.Authentication.Realm="Realm" RtspOutput.Properties.Authentication.Username="Username" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index 7715631..fa70507 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(Opcional)" RtspServer.Properties.Status="Estatus" RtspServer.Properties.Status.TotalDataSent="Salida de datos total: " RtspServer.Properties.Status.Bitrate="Tasa de bits: " +RtspServer.Properties.Status.DroppedFrames="Fotogramas perdidos: " RtspServer.Properties.Version="Versión: " RtspOutput="Salida RTSP" diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index eb2fa7b..0c11e5b 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(Facultatif)" RtspServer.Properties.Status="Statut" RtspServer.Properties.Status.TotalDataSent="Sortie totale des données: " RtspServer.Properties.Status.Bitrate="Débit binaire: " +RtspServer.Properties.Status.DroppedFrames="Images perdues: " RtspServer.Properties.Version="Version: " RtspOutput="Sortie RTSP" diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index 496bd57..65e3ab3 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(オプション)" RtspServer.Properties.Status="統計" RtspServer.Properties.Status.TotalDataSent="出力データの合計: " RtspServer.Properties.Status.Bitrate="ビットレート: " +RtspServer.Properties.Status.DroppedFrames="ドロップしたフレーム: " RtspServer.Properties.Version="Version: " RtspOutput="RTSP出力" diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini index af47cc2..18a78bf 100644 --- a/data/locale/ko-KR.ini +++ b/data/locale/ko-KR.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(옵션)" RtspServer.Properties.Status="상태" RtspServer.Properties.Status.TotalDataSent="총 데이터 출력: " RtspServer.Properties.Status.Bitrate="비트 레이트: " +RtspServer.Properties.Status.DroppedFrames="손실된 프레임: " RtspServer.Properties.Version="버전: " RtspOutput="RTSP 출력" diff --git a/data/locale/nl-NL.ini b/data/locale/nl-NL.ini index bb87272..d1629df 100644 --- a/data/locale/nl-NL.ini +++ b/data/locale/nl-NL.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(Optioneel)" RtspServer.Properties.Status="Toestand" RtspServer.Properties.Status.TotalDataSent="Totale gegevensoutput: " RtspServer.Properties.Status.Bitrate="Bitsnelheid: " +RtspServer.Properties.Status.DroppedFrames="Gedropte Frames: " RtspServer.Properties.Version="Versie: " RtspOutput="RTSP Uitvoer" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index dcbfa8c..ea782ae 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -21,6 +21,7 @@ RtspServer.Properties.Authentication.PasswordPlaceholder="(可选)" RtspServer.Properties.Status="状态" RtspServer.Properties.Status.TotalDataSent="总数据输出:" RtspServer.Properties.Status.Bitrate="比特率:" +RtspServer.Properties.Status.DroppedFrames="丢弃的帧:" RtspServer.Properties.Version="版本:" RtspOutput="RTSP 输出" diff --git a/data/locale/zh-TW.ini b/data/locale/zh-TW.ini index e897719..34a0be4 100644 --- a/data/locale/zh-TW.ini +++ b/data/locale/zh-TW.ini @@ -19,8 +19,9 @@ RtspServer.Properties.Authentication.Username="用戶名:" RtspServer.Properties.Authentication.Password="密碼:" RtspServer.Properties.Authentication.PasswordPlaceholder="(可選)" RtspServer.Properties.Status="狀態" -RtspServer.Properties.Status.TotalDataSent="總數據輸出:" -RtspServer.Properties.Status.Bitrate="位元速率:" +RtspServer.Properties.Status.TotalDataSent="總輸出資料:" +RtspServer.Properties.Status.Bitrate="位元率:" +RtspServer.Properties.Status.DroppedFrames="丟棄的影格:" RtspServer.Properties.Version="版本:" RtspOutput="RTSP 輸出" diff --git a/external/Findlibobs.cmake b/external/Findlibobs.cmake index 49f04a1..a10ef81 100644 --- a/external/Findlibobs.cmake +++ b/external/Findlibobs.cmake @@ -6,6 +6,18 @@ # LIBOBS_INCLUDE_DIRS # LIBOBS_LIBRARIES +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(OS_MACOS ON) + set(OS_POSIX ON) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") + set(OS_POSIX ON) + string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) + set(OS_${_SYSTEM_NAME_U} ON) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(OS_WINDOWS ON) + set(OS_POSIX OFF) +endif() + find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_OBS QUIET obs libobs) @@ -45,8 +57,15 @@ function(find_obs_lib base_name repo_build_path lib_name) set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") endif() + if(OS_MACOS) + set(_find_library_names + ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib) + else() + set(_find_library_names + ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}) + endif() find_library(${base_name_u}_LIB - NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} + NAMES ${_find_library_names} HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR diff --git a/external/Findobs-frontend-api.cmake b/external/Findobs-frontend-api.cmake index 656f3ba..ce92b95 100644 --- a/external/Findobs-frontend-api.cmake +++ b/external/Findobs-frontend-api.cmake @@ -6,6 +6,18 @@ # OBS_FRONTEND_API_INCLUDE_DIRS # OBS_FRONTEND_API_LIBRARIES +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(OS_MACOS ON) + set(OS_POSIX ON) +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|OpenBSD") + set(OS_POSIX ON) + string(TOUPPER "${CMAKE_SYSTEM_NAME}" _SYSTEM_NAME_U) + set(OS_${_SYSTEM_NAME_U} ON) +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(OS_WINDOWS ON) + set(OS_POSIX OFF) +endif() + find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(_OBS QUIET obs obs-frontend-api) @@ -45,8 +57,15 @@ function(find_obs_lib base_name repo_build_path lib_name) set(_build_type_${repo_build_path}${_lib_suffix} "${_build_type_base}${_lib_suffix}/${repo_build_path}") endif() + if(OS_MACOS) + set(_find_library_names + ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} ${lib_name}.dylib lib${lib_name}.dylib) + else() + set(_find_library_names + ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name}) + endif() find_library(${base_name_u}_LIB - NAMES ${_${base_name_u}_LIBRARIES} ${lib_name} lib${lib_name} + NAMES ${_find_library_names} HINTS ENV OBS_SOURCE_DIR${_lib_suffix} ENV OBS_SOURCE_DIR diff --git a/helper.h b/helper.h index 90a755d..e1837d5 100644 --- a/helper.h +++ b/helper.h @@ -5,15 +5,14 @@ #include #include -#ifndef RTSP_HELPER_H -#define RTSP_HELPER_H +#pragma once #define CONFIG_SECTIION "RstpOutput" #define HOTKEY_CONFIG_SECTIION "Hotkeys" enum encoder_codec { UNKNOW = 0, H264 = 1, HEVC = 2, AV1 = 3, AAC = 4 }; -static bool make_config_dir() +[[maybe_unused]] static bool make_config_dir() { auto path = obs_module_config_path(""); auto ret = os_mkdirs(path); @@ -21,7 +20,7 @@ static bool make_config_dir() return ret == MKDIR_SUCCESS || ret == MKDIR_EXISTS; } -static obs_data_t *rtsp_output_read_data() +[[maybe_unused]] static obs_data_t *rtsp_output_read_data() { obs_data_t *data; auto path = obs_module_config_path("rtsp_output.json"); @@ -30,7 +29,7 @@ static obs_data_t *rtsp_output_read_data() return data; } -static bool rtsp_output_save_data(obs_data_t *data) +[[maybe_unused]] static bool rtsp_output_save_data(obs_data_t *data) { if (!make_config_dir()) return false; @@ -40,7 +39,7 @@ static bool rtsp_output_save_data(obs_data_t *data) return ret; } -static config_t *rtsp_properties_open_config() +[[maybe_unused]] static config_t *rtsp_properties_open_config() { if (!make_config_dir()) return nullptr; @@ -60,7 +59,7 @@ static config_t *rtsp_properties_open_config() return config; } -static std::string string_format(char const *format, ...) +[[maybe_unused]] static std::string string_format(char const *format, ...) { va_list argp; va_start(argp, format); @@ -73,7 +72,7 @@ static std::string string_format(char const *format, ...) return std::string(buf.data(), buf.data() + size - 1); } -static std::string rtsp_properties_get_data_volume_display(uint64_t total_bytes) +[[maybe_unused]] static std::string rtsp_properties_get_data_volume_display(uint64_t total_bytes) { const uint64_t kb = 1024; const uint64_t mb = kb * 1024; @@ -96,7 +95,7 @@ static std::string rtsp_properties_get_data_volume_display(uint64_t total_bytes) return string_format("%.1f TB", double(total_bytes) / tb); } -static encoder_codec get_encoder_codec(const obs_encoder_t *encoder) +[[maybe_unused]] static encoder_codec get_encoder_codec(const obs_encoder_t *encoder) { const char *const codec = obs_encoder_get_codec(encoder); if (strcmp(codec, "h264") == 0) { @@ -113,5 +112,3 @@ static encoder_codec get_encoder_codec(const obs_encoder_t *encoder) } return UNKNOW; } - -#endif // RTSP_HELPER_H diff --git a/rtsp-server/net/Logger.cpp b/rtsp-server/net/Logger.cpp index 43630f7..8fc0f93 100644 --- a/rtsp-server/net/Logger.cpp +++ b/rtsp-server/net/Logger.cpp @@ -59,12 +59,14 @@ void Logger::Log(const Priority priority, const char *__file, { std::unique_lock lock(mutex_); - char buf[2048] = {0}; - sprintf(buf, "[%s][%s:%s:%d] ", Priority_To_String[priority], __file, + char buf[2048]; + auto buf_ptr = buf; + auto buf_end = buf + sizeof(buf); + buf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, "[%s][%s:%s:%d] ", Priority_To_String[priority], __file, __func, __line); va_list args; va_start(args, fmt); - vsprintf(buf + strlen(buf), fmt, args); + vsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args); va_end(args); this->Write(std::string(buf)); @@ -75,11 +77,13 @@ void Logger::Log2(const Priority priority, const char *fmt, ...) { std::unique_lock lock(mutex_); - char buf[4096] = {0}; - sprintf(buf, "[%s] ", Priority_To_String[priority]); + char buf[4096]; + auto buf_ptr = buf; + auto buf_end = buf + sizeof(buf); + buf_ptr += snprintf(buf_ptr, buf_end - buf_ptr, "[%s] ", Priority_To_String[priority]); va_list args; va_start(args, fmt); - vsprintf(buf + strlen(buf), fmt, args); + vsnprintf(buf_ptr, buf_end - buf_ptr, fmt, args); va_end(args); this->Write(std::string(buf)); diff --git a/rtsp-server/xop/AACSource.cpp b/rtsp-server/xop/AACSource.cpp index 8dbe48b..c557d20 100644 --- a/rtsp-server/xop/AACSource.cpp +++ b/rtsp-server/xop/AACSource.cpp @@ -42,8 +42,8 @@ AACSource::~AACSource() = default; string AACSource::GetMediaDescription(const uint16_t port) { - char buf[100] = {0}; - sprintf(buf, "m=audio %hu RTP/AVP 97", port); // \r\nb=AS:64 + char buf[100]; + snprintf(buf, sizeof(buf), "m=audio %hu RTP/AVP 97", port); // \r\nb=AS:64 return buf; } @@ -76,14 +76,14 @@ string AACSource::GetAttribute() // RFC 3640 strlen(fmtp_fmt); auto buf = vector(buf_size); const size_t rtpmap_format_size = - sprintf(buf.data(), rtpmap_fmt, samplerate_, channels_); + snprintf(buf.data(), buf_size, rtpmap_fmt, samplerate_, channels_); const array audioSpecificConfig = { static_cast((profile + 1) << 3 | samplingFrequencyIndex >> 1), static_cast(samplingFrequencyIndex << 7 | channels_ << 3)}; - sprintf(buf.data() + rtpmap_format_size, fmtp_fmt, + snprintf(buf.data() + rtpmap_format_size, buf_size - rtpmap_format_size, fmtp_fmt, audioSpecificConfig[0], audioSpecificConfig[1]); return buf.data(); diff --git a/rtsp-server/xop/CngMd5.cpp b/rtsp-server/xop/CngMd5.cpp index 9e8fec0..12bacce 100644 --- a/rtsp-server/xop/CngMd5.cpp +++ b/rtsp-server/xop/CngMd5.cpp @@ -16,7 +16,7 @@ CngMd5::CngMd5() : Md5() DWORD cbData = 0; NTSTATUS status; if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider( - &hAlgorithm_, BCRYPT_MD5_ALGORITHM, NULL, 0))) { + &hAlgorithm_, BCRYPT_MD5_ALGORITHM, nullptr, 0))) { LOG_ERROR( "**** Error 0x%x returned by BCryptOpenAlgorithmProvider", status); @@ -28,6 +28,7 @@ CngMd5::CngMd5() : Md5() 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptGetProperty", status); + CngMd5::~CngMd5(); return; } if (!NT_SUCCESS(status = BCryptGetProperty( @@ -35,16 +36,23 @@ CngMd5::CngMd5() : Md5() (PBYTE)&cbHash_, sizeof(DWORD), &cbData, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptGetProperty", status); + CngMd5::~CngMd5(); return; } + if (cbHash_ > MD5_HASH_LENGTH) { + LOG_ERROR("**** The generated hash value is too long"); + CngMd5::~CngMd5(); + } #endif } CngMd5::~CngMd5() { #if defined(WIN32) || defined(_WIN32) - if (hAlgorithm_) + if (hAlgorithm_) { BCryptCloseAlgorithmProvider(hAlgorithm_, 0); + hAlgorithm_ = nullptr; + } #endif } @@ -52,31 +60,40 @@ void CngMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize, unsigned char *outHash) { #if defined(WIN32) || defined(_WIN32) - if (cbHash_ > MD5_HASH_LENGTH) { - LOG_ERROR("**** The generated hash value is too long"); - goto Cleanup; - } + if (hAlgorithm_ == nullptr) return; + const auto pbHashObject = static_cast( HeapAlloc(GetProcessHeap(), 0, cbHashObject_)); + //create a hash + BCRYPT_HASH_HANDLE hHash = nullptr; + + auto cleanup = [&] () { + if (hHash) + BCryptDestroyHash(hHash); + if (pbHashObject) + HeapFree(GetProcessHeap(), 0, pbHashObject); + }; + if (nullptr == pbHashObject) { LOG_ERROR("**** memory allocation failed"); - goto Cleanup; + cleanup(); + return; } - //create a hash - BCRYPT_HASH_HANDLE hHash = nullptr; NTSTATUS status; if (!NT_SUCCESS(status = BCryptCreateHash(hAlgorithm_, &hHash, pbHashObject, cbHashObject_, - NULL, 0, 0))) { + nullptr, 0, 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptCreateHash", status); - goto Cleanup; + cleanup(); + return; } if (!NT_SUCCESS(status = BCryptHashData(hHash, const_cast(data), static_cast(dataSize), 0))) { LOG_ERROR("**** Error 0x%x returned by BCryptHashData", status); - goto Cleanup; + cleanup(); + return; } //close the hash @@ -85,11 +102,6 @@ void CngMd5::GetMd5Hash(const unsigned char *data, const size_t dataSize, LOG_ERROR("**** Error 0x%x returned by BCryptFinishHash", status); } - -Cleanup: - if (hHash) - BCryptDestroyHash(hHash); - if (pbHashObject) - HeapFree(GetProcessHeap(), 0, pbHashObject); + cleanup(); #endif } diff --git a/rtsp-server/xop/G711ASource.cpp b/rtsp-server/xop/G711ASource.cpp index 2b420e4..7fd3123 100644 --- a/rtsp-server/xop/G711ASource.cpp +++ b/rtsp-server/xop/G711ASource.cpp @@ -35,8 +35,8 @@ G711ASource::~G711ASource() = default; string G711ASource::GetMediaDescription(const uint16_t port) { - char buf[100] = {0}; - sprintf(buf, "m=audio %hu RTP/AVP 8", port); + char buf[100]; + snprintf(buf, sizeof(buf), "m=audio %hu RTP/AVP 8", port); return buf; } diff --git a/rtsp-server/xop/H264Source.cpp b/rtsp-server/xop/H264Source.cpp index b7ddfe5..9f15dcb 100644 --- a/rtsp-server/xop/H264Source.cpp +++ b/rtsp-server/xop/H264Source.cpp @@ -23,14 +23,13 @@ #include "Base64Encode.h" #include "Nal.h" #include "H264NalUnit.h" -#include "H265Source.h" using namespace xop; using namespace std; H264Source::H264Source(vector sps, vector pps, const uint32_t framerate) - : framerate_(framerate), sps_(move(sps)), pps_(move(pps)) + : framerate_(framerate), sps_(std::move(sps)), pps_(std::move(pps)) { payload_ = 96; media_type_ = MediaType::H264; @@ -57,15 +56,15 @@ H264Source *H264Source::CreateNew(vector extraData, H264Source *H264Source::CreateNew(vector sps, vector pps, const uint32_t framerate) { - return new H264Source(move(sps), move(pps), framerate); + return new H264Source(std::move(sps), std::move(pps), framerate); } H264Source::~H264Source() = default; string H264Source::GetMediaDescription(const uint16_t port) { - char buf[100] = {0}; - sprintf(buf, "m=video %hu RTP/AVP 96", port); // \r\nb=AS:2000 + char buf[100]; + snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); // \r\nb=AS:2000 return buf; } @@ -89,7 +88,7 @@ string H264Source::GetAttribute() pps_base64.length(); auto buf = vector(buf_size); - sprintf(buf.data(), fmtp, profile_level_id, sps_base64.c_str(), + snprintf(buf.data(), buf_size, fmtp, profile_level_id, sps_base64.c_str(), pps_base64.c_str()); sdp.append(buf.data()); @@ -124,24 +123,24 @@ bool H264Source::HandleFrame(const MediaChannelId channelId, if (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index) size_count -= nal[end_index--]->GetSize() + 2; if (end_index > nal_index) { - //Single-Time Aggregation Packet (STAP-A) - /* 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | RTP Header | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | NALU 1 Data | - * : : - * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | | NALU 2 Size | NALU 2 HDR | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | NALU 2 Data | - * : : - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + //Single-Time Aggregation Packet (STAP-A) + /* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RTP Header | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NALU 1 Data | + * : : + * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | NALU 2 Size | NALU 2 HDR | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NALU 2 Data | + * : : + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size_count); rtp_packet.last = 1; @@ -177,18 +176,18 @@ bool H264Source::HandleFrame(const MediaChannelId channelId, if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { - //Single NAL Unit Packets - /* 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * |F|NRI| type | | - * +-+-+-+-+-+-+-+-+ | - * | | - * | Bytes 2..n of a Single NAL unit | - * | | - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + //Single NAL Unit Packets + /* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |F|NRI| type | | + * +-+-+-+-+-+-+-+-+ | + * | | + * | Bytes 2..n of a Single NAL unit | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ const auto nal_unit = nal[nal_index++]; if (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) { const auto size = nal_unit->CopyData( @@ -202,8 +201,8 @@ bool H264Source::HandleFrame(const MediaChannelId channelId, rtp_packet)) return false; } else { - //Fragmentation Units (FU-A) - /* 0 1 2 3 + //Fragmentation Units (FU-A) + /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | FU indicator | FU header | | diff --git a/rtsp-server/xop/H265Source.cpp b/rtsp-server/xop/H265Source.cpp index 0cca74d..7a928e7 100644 --- a/rtsp-server/xop/H265Source.cpp +++ b/rtsp-server/xop/H265Source.cpp @@ -30,10 +30,10 @@ H265Source::H265Source(vector vps, vector sps, vector pps, vector sei, const uint32_t framerate) : framerate_(framerate), - vps_(move(vps)), - sps_(move(sps)), - pps_(move(pps)), - sei_(move(sei)) + vps_(std::move(vps)), + sps_(std::move(sps)), + pps_(std::move(pps)), + sei_(std::move(sei)) { payload_ = 96; media_type_ = MediaType::H265; @@ -58,7 +58,7 @@ H265Source *H265Source::CreateNew(vector extraData, if (pps_nal_unit != nullptr) pps = pps_nal_unit->GetData(); - return new H265Source(vps, sps, pps, move(sei), framerate); + return new H265Source(vps, sps, pps, std::move(sei), framerate); } H265Source::~H265Source() = default; @@ -67,14 +67,14 @@ H265Source *H265Source::CreateNew(vector vps, vector sps, vector pps, vector sei, const uint32_t framerate) { - return new H265Source(move(vps), move(sps), move(pps), move(sei), + return new H265Source(std::move(vps), std::move(sps), std::move(pps), std::move(sei), framerate); } string H265Source::GetMediaDescription(const uint16_t port) { - char buf[100] = {0}; - sprintf(buf, "m=video %hu RTP/AVP 96", port); + char buf[100]; + snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); return buf; } @@ -86,7 +86,7 @@ string H265Source::GetAttribute() if (!vps_.empty() && !sps_.empty() && !pps_.empty()) { const auto fmtp = "a=fmtp:96 profile-space=%u;tier-flag=%u;" - "profile-id=%u;level-id=%u;interop-constraints=%012I64X;" + "profile-id=%u;level-id=%u;interop-constraints=%012llX;" "sprop-vps=%s;sprop-pps=%s;sprop-sps=%s;%s"; string vps_base64, pps_base64, sps_base64, sei; @@ -117,7 +117,7 @@ string H265Source::GetAttribute() sps_base64.length() + sei.length(); auto buf = vector(buf_size); - sprintf(buf.data(), fmtp, profile_space, tier_flag, profile_id, + snprintf(buf.data(), buf_size, fmtp, profile_space, tier_flag, profile_id, level_id, interop_constraints, vps_base64.c_str(), pps_base64.c_str(), sps_base64.c_str(), sei.c_str()); buf[strlen(buf.data()) - 1] = '\0'; @@ -154,28 +154,28 @@ bool H265Source::HandleFrame(const MediaChannelId channelId, if (size_count > MAX_RTP_PAYLOAD_SIZE && end_index > nal_index) size_count -= nal[end_index--]->GetSize() + 2; if (end_index > nal_index) { - //Aggregation Packets - /* 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | RTP Header | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | PayloadHdr (Type=48) | NALU 1 Size | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | NALU 1 HDR | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 1 Data | - * | . . . | - * | | - * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | . . . | NALU 2 Size | NALU 2 HDR | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | NALU 2 HDR | | - * +-+-+-+-+-+-+-+-+ NALU 2 Data | - * | . . . | - * | | - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + //Aggregation Packets + /* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | RTP Header | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PayloadHdr (Type=48) | NALU 1 Size | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NALU 1 HDR | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ NALU 1 Data | + * | . . . | + * | | + * + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . . . | NALU 2 Size | NALU 2 HDR | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | NALU 2 HDR | | + * +-+-+-+-+-+-+-+-+ NALU 2 Data | + * | . . . | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ rtp_packet.size = RTP_TCP_HEAD_SIZE + RTP_HEADER_SIZE + static_cast(size_count); rtp_packet.last = 1; @@ -219,19 +219,19 @@ bool H265Source::HandleFrame(const MediaChannelId channelId, if (!send_frame_callback_(channelId, rtp_packet)) return false; } else { - //Single NAL Unit Packets - /* 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - * | PayloadHdr | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | - * | | - * | | - * | NAL unit payload data | - * | | - * | | - * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ + //Single NAL Unit Packets + /* 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | PayloadHdr | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | + * | | + * | NAL unit payload data | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ const auto nal_unit = nal[nal_index++]; if (nal_unit->GetSize() <= MAX_RTP_PAYLOAD_SIZE) { const auto size = nal_unit->CopyData( @@ -245,8 +245,8 @@ bool H265Source::HandleFrame(const MediaChannelId channelId, rtp_packet)) return false; } else { - //Fragmentation Units - /* 0 1 2 3 + //Fragmentation Units + /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PayloadHdr (Type=49) | FU header | | @@ -335,7 +335,6 @@ uint32_t H265Source::GetTimestamp() return ts; #else */ //auto time_point = chrono::time_point_cast(chrono::system_clock::now()); - //auto time_point = chrono::time_point_cast(chrono::steady_clock::now()); const auto time_point = chrono::time_point_cast( chrono::steady_clock::now()); return static_cast( diff --git a/rtsp-server/xop/VP8Source.cpp b/rtsp-server/xop/VP8Source.cpp index 07d2a64..9fac799 100644 --- a/rtsp-server/xop/VP8Source.cpp +++ b/rtsp-server/xop/VP8Source.cpp @@ -35,8 +35,8 @@ VP8Source::~VP8Source() = default; string VP8Source::GetMediaDescription(const uint16_t port) { - char buf[100] = {0}; - sprintf(buf, "m=video %hu RTP/AVP 96", port); + char buf[100]; + snprintf(buf, sizeof(buf), "m=video %hu RTP/AVP 96", port); return buf; } diff --git a/rtsp_output.cpp b/rtsp_output.cpp index 851e1ea..fe77c5f 100644 --- a/rtsp_output.cpp +++ b/rtsp_output.cpp @@ -21,9 +21,13 @@ #define ERROR_START_MULTICAST 4 #define ERROR_ENCODE OBS_OUTPUT_ENCODE_ERROR +#define OBS_RTSPSERVER_QUEUE_SIZE_LIMIT 2 struct queue_frame { - queue_frame(size_t size = 0) : av_frame(size), channe_id(xop::MediaChannelId::channel_0) {} + queue_frame(size_t size = 0) + : av_frame(size), channe_id(xop::MediaChannelId::channel_0) + { + } struct xop::AVFrame av_frame; xop::MediaChannelId channe_id; }; @@ -32,14 +36,16 @@ struct rtsp_out_data { obs_output_t *output = nullptr; volatile bool active; + volatile bool starting; volatile bool stopping; - uint64_t stop_ts; + volatile uint64_t stop_ts; - uint32_t num_clients = 0; + //volatile uint32_t num_clients = 0; std::array audio_timestamp_clocks; - std::array enabled_channels; std::array channel_ids; - uint64_t total_bytes_sent = 0; + volatile uint64_t total_bytes_sent = 0; + volatile uint32_t enabled_audio_channels_count = 0; + volatile bool output_audio = false; std::unique_ptr event_loop; std::shared_ptr server; @@ -56,25 +62,30 @@ static const char *rtsp_output_getname(void *unused) return obs_module_text("RtspOutput"); } -static inline bool stopping(rtsp_out_data *out_data) +static inline bool active(const rtsp_out_data *out_data) { - return os_atomic_load_bool(&out_data->stopping); + return os_atomic_load_bool(&out_data->active); } -static inline bool active(rtsp_out_data *out_data) +static inline bool starting(const rtsp_out_data *out_data) { - return os_atomic_load_bool(&out_data->active); + return os_atomic_load_bool(&out_data->starting); +} + +static inline bool stopping(const rtsp_out_data *out_data) +{ + return os_atomic_load_bool(&out_data->stopping); } -static void add_prestart_signal(rtsp_out_data *out_data) +static void add_prestart_signal(const rtsp_out_data *out_data) { - auto handler = obs_output_get_signal_handler(out_data->output); + const auto handler = obs_output_get_signal_handler(out_data->output); signal_handler_add(handler, "void pre_start()"); } -static void send_prestart_signal(rtsp_out_data *out_data) +static void send_prestart_signal(const rtsp_out_data *out_data) { - auto handler = obs_output_get_signal_handler(out_data->output); + const auto handler = obs_output_get_signal_handler(out_data->output); signal_handler_signal(handler, "pre_start", nullptr); } @@ -88,7 +99,7 @@ static bool rtsp_output_start_hotkey(void *data, const obs_hotkey_pair_id id, if (!pressed) return false; - if (!stopping(out_data) && active(out_data)) + if (stopping(out_data) || starting(out_data) || active(out_data)) return false; return obs_output_start(out_data->output); @@ -100,11 +111,11 @@ static bool rtsp_output_stop_hotkey(void *data, const obs_hotkey_pair_id id, UNUSED_PARAMETER(id); UNUSED_PARAMETER(hotkey); - auto *out_data = static_cast(data); + const auto *out_data = static_cast(data); if (!pressed) return false; - if (stopping(out_data) && active(out_data)) + if (stopping(out_data) || starting(out_data) || !active(out_data)) return false; obs_output_stop(out_data->output); @@ -121,8 +132,8 @@ static void rtsp_output_destroy(void *data) static void rtsp_output_update(void *data, obs_data_t *settings); static void *rtsp_output_create(obs_data_t *settings, obs_output_t *output) { - auto data = - static_cast(bzalloc(sizeof(struct rtsp_out_data))); + const auto data = static_cast( + bzalloc(sizeof(struct rtsp_out_data))); data->output = output; @@ -199,13 +210,11 @@ static void set_output_error(const rtsp_out_data *out_data, int code, ...) static bool rtsp_output_add_video_channel(void *data, xop::MediaSession *session) { - auto *out_data = static_cast(data); + const auto *out_data = static_cast(data); const auto video_encoder = obs_output_get_video_encoder(out_data->output); - if (video_encoder == nullptr) { - set_output_error(out_data, ERROR_INIT_ENCODERS); + if (video_encoder == nullptr) return false; - } const auto video = obs_encoder_video(video_encoder); const auto video_frame_rate = video_output_get_frame_rate(video); vector extra_data; @@ -225,17 +234,14 @@ static bool rtsp_output_add_video_channel(void *data, xop::H264Source::CreateNew( extra_data, static_cast(video_frame_rate))); - } - break; + } break; case encoder_codec::HEVC: { session->AddSource( xop::MediaChannelId::channel_0, xop::H265Source::CreateNew( - extra_data, - vector(), + extra_data, vector(), static_cast(video_frame_rate))); - } - break; + } break; default: break; } @@ -250,18 +256,12 @@ static bool rtsp_output_add_audio_channel(void *data, auto *out_data = static_cast(data); const auto audio_encoder = obs_output_get_audio_encoder(out_data->output, idx); - if (audio_encoder == nullptr) { - return false; - } + if (audio_encoder == nullptr) + return true; const auto audio = obs_encoder_audio(audio_encoder); const auto audio_channels = audio_output_get_channels(audio); const auto audio_sample_rate = obs_encoder_get_sample_rate(audio_encoder); - uint8_t *extra_data = nullptr; - size_t extra_data_size = 0; - if (obs_encoder_get_extra_data(audio_encoder, &extra_data, - &extra_data_size)) { - } session->AddSource( channel_id, xop::AACSource::CreateNew(audio_sample_rate, @@ -273,40 +273,37 @@ static bool rtsp_output_add_audio_channel(void *data, static bool rtsp_output_start(void *data) { - auto out_data = static_cast(data); + const auto out_data = static_cast(data); + + if (starting(out_data) || stopping(out_data)) + return false; send_prestart_signal(out_data); - auto enabled_channels_count = 0; - for (size_t index = 0; index < out_data->enabled_channels.size(); - index++) { + const auto settings = obs_output_get_settings(out_data->output); + rtsp_output_update(data, settings); + const auto port = + static_cast(obs_data_get_int(settings, "port")); + + uint32_t enabled_audio_channels_count = 0; + for (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) { if (obs_output_get_audio_encoder(out_data->output, index) == - nullptr) { - out_data->enabled_channels[index] = false; - continue; - } - out_data->enabled_channels[index] = true; + nullptr) continue; out_data->channel_ids[index] = static_cast( - ++enabled_channels_count); + ++enabled_audio_channels_count); } + out_data->enabled_audio_channels_count = enabled_audio_channels_count; - const auto av_flags = enabled_channels_count > 0 ? 0 : OBS_OUTPUT_VIDEO; - if (!obs_output_can_begin_data_capture(out_data->output, av_flags)) { + if (!obs_output_can_begin_data_capture(out_data->output, 0)) { set_output_error(out_data, ERROR_BEGIN_DATA_CAPTURE); return false; } - if (!obs_output_initialize_encoders(out_data->output, av_flags)) { + if (!obs_output_initialize_encoders(out_data->output, 0)) { set_output_error(out_data, ERROR_INIT_ENCODERS); return false; } - const auto settings = obs_output_get_settings(out_data->output); - rtsp_output_update(data, settings); - const auto port = - static_cast(obs_data_get_int(settings, "port")); - const auto url_suffix = obs_data_get_string(settings, "url_suffix"); - if (!out_data->server->Start("0.0.0.0", port) || !out_data->server->Start("::0", port)) { set_output_error(out_data, ERROR_START_RTSP_SERVER, port); @@ -314,86 +311,103 @@ static bool rtsp_output_start(void *data) return false; } - os_atomic_set_bool(&out_data->stopping, false); + obs_output_begin_data_capture(out_data->output, 0); + + os_atomic_set_bool(&out_data->starting, true); + + return true; +} + +static void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code); +static void rtsp_output_rtsp_start(void *data) +{ + const auto out_data = static_cast(data); + + const auto settings = obs_output_get_settings(out_data->output); + const auto port = + static_cast(obs_data_get_int(settings, "port")); + const auto url_suffix = obs_data_get_string(settings, "url_suffix"); + out_data->output_audio = obs_data_get_bool(settings, "output_audio"); xop::MediaSession *session = xop::MediaSession::CreateNew( - url_suffix, enabled_channels_count + 1); + url_suffix, out_data->enabled_audio_channels_count + 1); if (!rtsp_output_add_video_channel(data, session)) { - return false; + rtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS); + return; } - for (size_t index = 0; index < out_data->enabled_channels.size(); - index++) { - if (!out_data->enabled_channels[index]) - continue; + if (out_data->output_audio) + for (size_t index = 0; index < OBS_OUTPUT_MULTI_TRACK; index++) { if (!rtsp_output_add_audio_channel( data, session, index, out_data->channel_ids[index])) { - return false; + rtsp_output_actual_stop(out_data, ERROR_INIT_ENCODERS); + return; } } - out_data->frame_queue = - std::make_unique>(); + out_data->frame_queue = std::make_unique>( + OBS_RTSPSERVER_QUEUE_SIZE_LIMIT); session->AddNotifyConnectedCallback( [](const xop::MediaSessionId session_id, - const std::string &peer_ip, - const uint16_t peer_port) { + const std::string &peer_ip, const uint16_t peer_port) { blog(LOG_INFO, "Rtsp client %d(%s:%d) is connected.", session_id, peer_ip.c_str(), peer_port); }); session->AddNotifyDisconnectedCallback( [](const xop::MediaSessionId session_id, - const std::string &peer_ip, - const uint16_t peer_port) { + const std::string &peer_ip, const uint16_t peer_port) { blog(LOG_INFO, "Rtsp client %d(%s:%d) is disconnected.", session_id, peer_ip.c_str(), peer_port); }); out_data->session_id = out_data->server->AddSession(session); - if (auto multicast = obs_data_get_bool(settings, "multicast")) { - if (multicast = session->StartMulticast(); multicast) { - blog(LOG_INFO, - "------------------------------------------------"); - blog(LOG_INFO, "rtsp multicast info:"); - blog(LOG_INFO, "\tipv6 address: \t%s", - session->GetMulticastIp(true).c_str()); - blog(LOG_INFO, "\tipv4 address: \t%s", - session->GetMulticastIp(false).c_str()); - for (auto i = 0; i < enabled_channels_count + 1; i++) { - blog(LOG_INFO, "\tchannel %d port: \t%d", i, - session->GetMulticastPort( - static_cast( - i))); - } - blog(LOG_INFO, - "------------------------------------------------"); - } else { - set_output_error(out_data, ERROR_START_MULTICAST); - return false; + if (obs_data_get_bool(settings, "multicast")) { + if (!session->StartMulticast()) { + rtsp_output_actual_stop(out_data, + ERROR_START_MULTICAST); + return; + } + blog(LOG_INFO, + "------------------------------------------------"); + blog(LOG_INFO, "Rtsp multicast info:"); + blog(LOG_INFO, "\tipv6 address: \t%s", + session->GetMulticastIp(true).c_str()); + blog(LOG_INFO, "\tipv4 address: \t%s", + session->GetMulticastIp(false).c_str()); + blog(LOG_INFO, "\tchannel 0 port (video): \t%d", + session->GetMulticastPort(static_cast(0))); + for (size_t index = 1; index < OBS_OUTPUT_MULTI_TRACK + 1 ; index++) { + auto channel_port = session->GetMulticastPort(static_cast(index)); + if (channel_port == 0) break; + blog(LOG_INFO, "\tchannel %zu port (audio): \t%d", index, channel_port); } + blog(LOG_INFO, + "------------------------------------------------"); } + out_data->total_bytes_sent = 0; + out_data->frame_push_thread = std::make_unique(rtsp_push_frame, out_data); - out_data->total_bytes_sent = 0; - os_atomic_set_bool(&out_data->active, true); - obs_output_begin_data_capture(out_data->output, av_flags); - - blog(LOG_INFO, "starting rstp server on port '%d'", port); + os_atomic_set_bool(&out_data->starting, false); - return true; + blog(LOG_INFO, "Starting rtsp server on port '%d'.", port); } -static void rtsp_output_stop(void *data, uint64_t ts) +static void rtsp_output_stop(void *data, const uint64_t ts) { auto *out_data = static_cast(data); + + if (starting(out_data) || stopping(out_data)) + return; + out_data->stop_ts = ts / 1000ULL; //obs_output_pause(out_data->output, false); os_atomic_set_bool(&out_data->stopping, true); @@ -403,13 +417,6 @@ static void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code) { os_atomic_set_bool(&out_data->active, false); - if (code) { - set_output_error(out_data, code); - obs_output_signal_stop(out_data->output, code); - } else { - obs_output_end_data_capture(out_data->output); - } - if (out_data->frame_queue) out_data->frame_queue->termination(); @@ -423,16 +430,30 @@ static void rtsp_output_actual_stop(rtsp_out_data *out_data, const int code) out_data->session_id = 0; } out_data->server->Stop(); - out_data->num_clients = 0; + //out_data->num_clients = 0; if (out_data->frame_queue) out_data->frame_queue.reset(); - blog(LOG_INFO, "rstp server stopped"); + if (code < 0) { + set_output_error(out_data, code); + obs_output_signal_stop(out_data->output, code); + } else if (code > 0) { + obs_output_end_data_capture(out_data->output); + set_output_error(out_data, code); + obs_output_signal_stop(out_data->output, OBS_OUTPUT_ERROR); + } else { + obs_output_end_data_capture(out_data->output); + } + + os_atomic_set_bool(&out_data->starting, false); + os_atomic_set_bool(&out_data->stopping, false); + + blog(LOG_INFO, "Rtsp server stopped."); } -static uint32_t get_timestamp(uint64_t timestamp_clock, - struct encoder_packet *packet) +static uint32_t get_timestamp(const uint64_t timestamp_clock, + const struct encoder_packet *packet) { // Convert the incoming dts time to the correct clock time for the timestamp. // We use a int64 to ensure the roll over is handled correctly. @@ -446,7 +467,7 @@ static void rtsp_push_frame(void *param) { auto *out_data = static_cast(param); - blog(LOG_INFO, "starting rtsp frame push thread"); + blog(LOG_INFO, "Starting rtsp frame push thread."); while (true) { std::shared_ptr queue_frame = out_data->frame_queue->wait_and_pop(); @@ -457,7 +478,7 @@ static void rtsp_push_frame(void *param) queue_frame->channe_id, queue_frame->av_frame); } - blog(LOG_INFO, "rtsp frame push thread stopped"); + blog(LOG_INFO, "Rtsp frame push thread stopped."); } static void rtsp_output_video(void *param, struct encoder_packet *packet) @@ -472,7 +493,7 @@ static void rtsp_output_video(void *param, struct encoder_packet *packet) memcpy(frame->buffer.get(), packet->data, packet->size); - out_data->frame_queue->push(queue_frame); + out_data->frame_queue->move_push(std::move(queue_frame)); } static void rtsp_output_audio(void *param, struct encoder_packet *packet) @@ -488,15 +509,20 @@ static void rtsp_output_audio(void *param, struct encoder_packet *packet) memcpy(frame->buffer.get(), packet->data, packet->size); - out_data->frame_queue->push(queue_frame); + out_data->frame_queue->move_push(std::move(queue_frame)); } static void rtsp_output_data(void *data, struct encoder_packet *packet) { auto *out_data = static_cast(data); - if (!active(out_data)) + if (!active(out_data) && !starting(out_data)) + return; + + if (starting(out_data)) { + rtsp_output_rtsp_start(out_data); return; + } if (!packet) { rtsp_output_actual_stop(out_data, OBS_OUTPUT_ENCODE_ERROR); @@ -512,7 +538,7 @@ static void rtsp_output_data(void *data, struct encoder_packet *packet) //if (out_data->num_clients > 0) { if (packet->type == OBS_ENCODER_VIDEO) rtsp_output_video(data, packet); - else if (packet->type == OBS_ENCODER_AUDIO) + else if (packet->type == OBS_ENCODER_AUDIO && out_data->output_audio) rtsp_output_audio(data, packet); //} else if (!stopping(out_data)) { //obs_output_pause(out_data->output, true); @@ -530,6 +556,7 @@ static void rtsp_output_defaults(obs_data_t *defaults) obs_data_set_default_int(defaults, "port", 554); #endif obs_data_set_default_string(defaults, "url_suffix", "live"); + obs_data_set_default_bool(defaults, "output_audio", true); obs_data_set_default_bool(defaults, "authentication", false); obs_data_set_default_string(defaults, "authentication_realm", ""); obs_data_set_default_string(defaults, "authentication_username", ""); @@ -576,6 +603,10 @@ static obs_properties_t *rtsp_output_properties(void *data) obs_module_text("RtspOutput.Properties.UrlSuffix"), OBS_TEXT_DEFAULT); + obs_properties_add_bool( + props, "output_audio", + obs_module_text("RtspOutput.Properties.OutputAudio")); + obs_properties_t *auth_group = obs_properties_create(); obs_properties_add_text( auth_group, "authentication_realm", @@ -604,6 +635,18 @@ static uint64_t rtsp_output_total_bytes_sent(void *data) return out_data->total_bytes_sent; } +static int rtsp_output_get_dropped_frames(void *data) +{ + const auto *out_data = static_cast(data); + if (!active(out_data) || out_data->frame_queue == nullptr) { + return 0; + } + auto dropped_count = out_data->frame_queue->dropped_count(); + while (dropped_count > INT32_MAX) + dropped_count -= INT32_MAX; + return static_cast(dropped_count); +} + void rtsp_output_register() { struct obs_output_info output_info = {}; @@ -622,6 +665,7 @@ void rtsp_output_register() output_info.update = rtsp_output_update; output_info.get_properties = rtsp_output_properties; output_info.get_total_bytes = rtsp_output_total_bytes_sent; + output_info.get_dropped_frames = rtsp_output_get_dropped_frames; obs_register_output(&output_info); } diff --git a/rtsp_output.h b/rtsp_output.h index 1ca1b13..03412e5 100644 --- a/rtsp_output.h +++ b/rtsp_output.h @@ -1,7 +1,4 @@ -#ifndef RTSP_OUTPUT_H -#define RTSP_OUTPUT_H +#pragma once -static const char *rtsp_output_getname(void *unused); +[[maybe_unused]] static const char *rtsp_output_getname(void *unused); void rtsp_output_register(); - -#endif // RTSP_OUTPUT_H diff --git a/rtsp_output_helper.cpp b/rtsp_output_helper.cpp index 6dc6031..f05449c 100644 --- a/rtsp_output_helper.cpp +++ b/rtsp_output_helper.cpp @@ -98,6 +98,16 @@ uint64_t RtspOutputHelper::GetTotalBytes() const return obs_output_get_total_bytes(obsOutput); } +int RtspOutputHelper::GetTotalFrames() const +{ + return obs_output_get_total_frames(obsOutput); +} + +int RtspOutputHelper::GetFramesDropped() const +{ + return obs_output_get_frames_dropped(obsOutput); +} + bool RtspOutputHelper::IsActive() const { return obs_output_active(obsOutput); @@ -121,20 +131,29 @@ void RtspOutputHelper::CreateVideoEncoder() obs_encoder_set_scaled_size(videoEncoder, outputSettings.rescale_cx, outputSettings.rescale_cy); - obs_encoder_set_video(videoEncoder, obs_output_video(obsOutput)); + obs_encoder_set_video(videoEncoder, obs_get_video()); + { + auto video = obs_output_video(obsOutput); + if (video == nullptr) + video = obs_get_video(); + obs_encoder_set_video(videoEncoder, video); + } obs_output_set_video_encoder(obsOutput, videoEncoder); } void RtspOutputHelper::CreateAudioEncoder() { obs_encoder_t *encoder; - if (outputSettings.adv_out) + if (outputSettings.adv_out) { /*if ((encoder = obs_get_encoder_by_name("adv_stream_aac")) == nullptr) encoder = obs_get_encoder_by_name( "avc_aac_stream");*/ //OBS 26.0.2 Or Older - encoder = obs_get_encoder_by_name("adv_stream_aac"); - + /*if ((encoder = obs_get_encoder_by_name("adv_stream_audio")) == + nullptr) //OBS 30.0.0 Or Older + encoder = obs_get_encoder_by_name("adv_stream_aac");*/ + encoder = obs_get_encoder_by_name("adv_stream_audio"); + } else encoder = obs_get_encoder_by_name("simple_aac"); @@ -144,21 +163,32 @@ void RtspOutputHelper::CreateAudioEncoder() const auto config = rtsp_properties_open_config(); + auto tracks = + static_cast(config_get_uint(config, CONFIG_SECTIION, "AudioTracks")); + { + auto outputData = GetSettings(); + if (tracks == 0) { + obs_data_set_bool(outputData, "output_audio", false); + tracks = 0x1; + } else obs_data_set_bool(outputData, "output_audio", true); + UpdateSettings(outputData); + obs_data_release(outputData); + } auto trackIndex = 0; for (auto idx = 0; idx < OBS_OUTPUT_MULTI_TRACK; idx++) { - if (!config_get_bool(config, CONFIG_SECTIION, - string("AudioTrack") - .append(to_string(idx + 1)) - .c_str())) - continue; + if ((tracks & (1 << idx)) == 0) continue; auto audioEncoder = obs_audio_encoder_create( obs_encoder_get_id(encoder), string("rtsp_output_audio_track") .append(to_string(idx + 1)) .c_str(), obs_encoder_get_settings(encoder), idx, nullptr); - obs_encoder_set_audio(audioEncoder, - obs_output_audio(obsOutput)); + { + auto audio = obs_output_audio(obsOutput); + if (audio == nullptr) + audio = obs_get_audio(); + obs_encoder_set_audio(audioEncoder, audio); + } audioEncoders.push_back(audioEncoder); obs_output_set_audio_encoder(obsOutput, audioEncoder, trackIndex++); diff --git a/rtsp_output_helper.h b/rtsp_output_helper.h index bb2818b..724d5ab 100644 --- a/rtsp_output_helper.h +++ b/rtsp_output_helper.h @@ -29,6 +29,8 @@ class RtspOutputHelper { void *data) const; std::string GetOutputName() const; uint64_t GetTotalBytes() const; + int GetTotalFrames() const; + int GetFramesDropped() const; bool IsActive() const; private: diff --git a/threadsafe_queue.h b/threadsafe_queue.h index 019f30f..f5bdd2a 100644 --- a/threadsafe_queue.h +++ b/threadsafe_queue.h @@ -13,114 +13,140 @@ using namespace std; template class threadsafe_queue { public: - threadsafe_queue() - : m_bTermination(false) { + threadsafe_queue(size_t size_limit) + : size_limit(size_limit), m_termination(false) + { } - ~threadsafe_queue() {} + ~threadsafe_queue() = default; + + /** + * 1. When termination is not called, one element is dequeued every time the + * queue is called until the queue is empty. This method blocks the thread. + * 2. After termination is called, this method will never block. If it is + * already in a blocked state, contact the blocked state. + * 3. When true is returned, the value is valid. When false is returned, value + * is invalid. Returns false when termination is called and the queue is empty. + **/ - //(1)没有调用termination时,每调用一次出队一个元素,直到队列为空本方法阻塞线程。 - //(2)在调用了termination后,本方法永不阻塞,如果原本已经处于阻塞状态,接触阻塞状态。 - //(3)返回true时,value值有效。返回false时,value值无效。调用了termination且队列为空时返回false. - bool wait_and_pop(T &value) + //return nullptr if the queue is empty + std::shared_ptr wait_and_pop() { unique_lock lk(mut); data_cond.wait(lk, [this] { - return ((!data_queue.empty()) || m_bTermination.load(memory_order_acquire)); + return !data_queue.empty() || + m_termination.load(memory_order_acquire); }); - //不为空则出队 + //dequeue if not empty if (!data_queue.empty()) { - value = move(*data_queue.front()); + shared_ptr res = data_queue.front(); data_queue.pop(); - return true; + return res; } - //队列为空则返回失败 - return false; + //If the queue is empty, return nullptr + return nullptr; } - //队列为空返回false - bool try_pop(T &value) + //return false if the queue is empty + bool wait_and_pop(T &&value) { - lock_guard lk(mut); - if (data_queue.empty()) - { + shared_ptr res = wait_and_pop(); + if (res == nullptr) return false; - } - value = move(*data_queue.front()); - data_queue.pop(); + value = std::move(res); return true; } - std::shared_ptr wait_and_pop() + //return nullptr if the queue is empty + std::shared_ptr try_pop() { - unique_lock lk(mut); - data_cond.wait(lk, [this] { - return ((!data_queue.empty()) || m_bTermination.load(memory_order_acquire)); - }); + lock_guard lk(mut); + + //dequeue if not empty if (!data_queue.empty()) { shared_ptr res = data_queue.front(); data_queue.pop(); return res; } + + //If the queue is empty, return nullptr return nullptr; } - //队列为空返回null - std::shared_ptr try_pop() + //return false if the queue is empty + bool try_pop(T &&value) { - unique_lock lk(mut); - if (data_queue.empty()) - { - return nullptr; - } - shared_ptr res = data_queue.front(); - data_queue.pop(); - return res; + shared_ptr res = try_pop(); + if (res == nullptr) + return false; + value = std::move(res); + return true; } - //插入一项 - void push(T new_value) + //insert queue, move + void move_push(T &&new_value) { - if (m_bTermination.load(memory_order_acquire)) + if (m_termination.load(memory_order_acquire)) return; - shared_ptr data(make_shared(move(new_value))); + shared_ptr data(make_shared(std::move(new_value))); unique_lock lk(mut); data_queue.push(data); + if (data_queue.size() > size_limit) { + data_queue.pop(); + m_dropped_count.fetch_add(1, memory_order_relaxed); + } data_cond.notify_one(); } + //insert queue + void push(T new_value) + { + move_push(new_value); + } + bool empty() { unique_lock lk(mut); return data_queue.empty(); } - int size() + size_t size() { unique_lock lk(mut); return data_queue.size(); } - //设置队列为退出状态。在退出状态下,忽略入队,可以执行出队,但当队列为空时,wait_and_pop不会阻塞。 + + size_t dropped_count() const + { + return m_dropped_count.load(memory_order_relaxed); + } + + //Set this queue to terminated state. + //In the exit state, the enqueue is ignored, and the dequeue can be performed. + //When the queue is empty, wait_and_pop will not block. void termination() { unique_lock lk(mut); - m_bTermination.store(true, memory_order_release); + m_termination.store(true, memory_order_release); data_cond.notify_all(); } - //是退出状态吗 + + //Get whether this queue is terminated bool is_termination() const { - return m_bTermination.load(memory_order_acquire); + return m_termination.load(memory_order_acquire); } private: mutex mut; queue> data_queue; + const size_t size_limit; condition_variable data_cond; - atomic_bool m_bTermination; + atomic_bool m_termination; + atomic_size_t m_dropped_count; }; #endif diff --git a/ui/rtsp_properties.cpp b/ui/rtsp_properties.cpp index beef9b6..83709f3 100644 --- a/ui/rtsp_properties.cpp +++ b/ui/rtsp_properties.cpp @@ -160,12 +160,21 @@ void RtspProperties::onLineEditPasswordTextChanged(const QString &value) const void RtspProperties::onStatusTimerTimeout() { const auto totalBytes = rtspOutputHelper->GetTotalBytes(); + const auto totalFrames = rtspOutputHelper->GetTotalFrames(); + const auto framesDropped = rtspOutputHelper->GetFramesDropped(); const auto bitps = (totalBytes - lastTotalBytes) * 8; lastTotalBytes = totalBytes; ui->labelTotalData->setText( rtsp_properties_get_data_volume_display(totalBytes).c_str()); ui->labelBitrate->setText(QString("%1 kb/s").arg( - bitps / 1000 + (bitps % 1000 >= 500 ? 1 : 0))); + bitps / 1000.0, 0, 'f', 0)); + ui->labelFramesDropped->setText( + QString("%1 / %2 (%3%)") + .arg(framesDropped) + .arg(totalFrames) + .arg(totalFrames == 0 ? 0 + : framesDropped * 100.0 / totalFrames, + 0, 'f', 1)); } void RtspProperties::onButtonStatusChanging(const bool outputStarted, @@ -193,6 +202,7 @@ void RtspProperties::onStatusTimerStatusChanging(const bool start) statusTimer->stop(); ui->labelTotalData->setText("0.0 MB"); ui->labelBitrate->setText("0 kb/s"); + ui->labelFramesDropped->setText("0 / 0 (0.0%)"); } } @@ -283,38 +293,35 @@ void RtspProperties::LoadConfig(config_t *config) const { ui->checkBoxAuto->setChecked( config_get_bool(config, CONFIG_SECTIION, "AutoStart")); - ui->checkBoxAudioTrack1->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack1")); - ui->checkBoxAudioTrack2->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack2")); - ui->checkBoxAudioTrack3->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack3")); - ui->checkBoxAudioTrack4->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack4")); - ui->checkBoxAudioTrack5->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack5")); - ui->checkBoxAudioTrack6->setChecked( - config_get_bool(config, CONFIG_SECTIION, "AudioTrack6")); + + { + auto tracks = + config_get_uint(config, CONFIG_SECTIION, "AudioTracks"); + ui->checkBoxAudioTrack1->setChecked(tracks & (1 << 0)); + ui->checkBoxAudioTrack2->setChecked(tracks & (1 << 1)); + ui->checkBoxAudioTrack3->setChecked(tracks & (1 << 2)); + ui->checkBoxAudioTrack4->setChecked(tracks & (1 << 3)); + ui->checkBoxAudioTrack5->setChecked(tracks & (1 << 4)); + ui->checkBoxAudioTrack6->setChecked(tracks & (1 << 5)); + } } void RtspProperties::SaveConfig(config_t *config) const { if (!config) return; - config_set_bool(config, CONFIG_SECTIION, "AutoStart", ui->checkBoxAuto->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack1", - ui->checkBoxAudioTrack1->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack2", - ui->checkBoxAudioTrack2->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack3", - ui->checkBoxAudioTrack3->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack4", - ui->checkBoxAudioTrack4->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack5", - ui->checkBoxAudioTrack5->isChecked()); - config_set_bool(config, CONFIG_SECTIION, "AudioTrack6", - ui->checkBoxAudioTrack6->isChecked()); + + { + uint64_t tracks = 0; + tracks |= ui->checkBoxAudioTrack1->isChecked() ? (1 << 0) : 0; + tracks |= ui->checkBoxAudioTrack2->isChecked() ? (1 << 1) : 0; + tracks |= ui->checkBoxAudioTrack3->isChecked() ? (1 << 2) : 0; + tracks |= ui->checkBoxAudioTrack4->isChecked() ? (1 << 3) : 0; + tracks |= ui->checkBoxAudioTrack5->isChecked() ? (1 << 4) : 0; + tracks |= ui->checkBoxAudioTrack6->isChecked() ? (1 << 5) : 0; + config_set_uint(config, CONFIG_SECTIION, "AudioTracks", tracks); + } config_save(config); } diff --git a/ui/rtsp_properties.ui b/ui/rtsp_properties.ui index b103abb..9648c53 100644 --- a/ui/rtsp_properties.ui +++ b/ui/rtsp_properties.ui @@ -60,7 +60,7 @@ - + 0 @@ -439,7 +439,7 @@ - + 0 @@ -452,14 +452,40 @@ 0 - + + + + + 0 + 0 + + + + + 60 + 0 + + + + 0kb/s + + + + RtspServer.Properties.Status.TotalDataSent - + + + + RtspServer.Properties.Status.Bitrate + + + + @@ -478,15 +504,21 @@ - - + + + + + 0 + 0 + + - RtspServer.Properties.Status.Bitrate + RtspServer.Properties.Status.DroppedFrames - - + + 0 @@ -500,11 +532,17 @@ - 0kb/s + 0 / 0 (0.0%) + labelBitrate + labelBitrateTitle + labelTotalData + labelTotalDataTitle + labelFramesDroppedTitle + labelFramesDropped