diff --git a/CMakeLists.txt b/CMakeLists.txt index b964cf2e7..1a38f82bf 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,11 +51,14 @@ pkg_check_modules(OPENSSL REQUIRED openssl) pkg_check_modules(LIBCJSON REQUIRED libcjson) pkg_check_modules(UUID REQUIRED uuid) -pkg_check_modules(BASECONVERSION REQUIRED libbaseconversion) -pkg_check_modules(PLAYERLOGMANAGER REQUIRED libplayerlogmanager) -pkg_check_modules(PLAYERFBINTERFACE REQUIRED libplayerfbinterface) -pkg_check_modules(PLAYERGSTINTERFACE REQUIRED libplayergstinterface) -pkg_check_modules(SUBTEC REQUIRED libsubtec) +if(CMAKE_TEST_MW) + message("CMAKE_TEST_MW ON") + pkg_check_modules(BASECONVERSION REQUIRED libbaseconversion) + pkg_check_modules(PLAYERLOGMANAGER REQUIRED libplayerlogmanager) + pkg_check_modules(PLAYERFBINTERFACE REQUIRED libplayerfbinterface) + pkg_check_modules(PLAYERGSTINTERFACE REQUIRED libplayergstinterface) + pkg_check_modules(SUBTEC REQUIRED libsubtec) +endif() if(APPLE) # libcurl < 8.5 exhibits memory leaks. On Ubuntu 22.04 can't update beyond 7.81.0-1ubuntu1.16 without building from source @@ -159,7 +162,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/tsb/api) # Locally built/installed dependencies are here include_directories(${CMAKE_CURRENT_SOURCE_DIR}/.libs/include) - +if(CMAKE_TEST_MW) set(LIBAAMP_DEPENDS ${OS_LD_FLAGS} ${UUID_LINK_LIBRARIES} @@ -180,16 +183,41 @@ set(LIBAAMP_DEPENDS ${SUBTEC_LINK_LIBRARIES} -ldl ) - +else() +set(LIBAAMP_DEPENDS + ${OS_LD_FLAGS} + ${UUID_LINK_LIBRARIES} + ${LIBCJSON_LINK_LIBRARIES} + ${GSTREAMERBASE_LINK_LIBRARIES} + ${GSTREAMER_LINK_LIBRARIES} + ${CURL_LINK_LIBRARIES} + ${LIBDASH_LINK_LIBRARIES} + ${LIBXML2_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${OPENSSL_LIBRARIES} + ${GL_DEPENDS} + ${AAMP_CLI_LD_FLAGS} +-ldl) +endif() # TDB needs to bring back for UT. #include(test/mocks/mocks.cmake NO_POLICY_SCOPE) - +if(CMAKE_TEST_MW) include_directories(${PLAYERFBINTERFACE_INCLUDE_DIRS}) include_directories(${BASECONVERSION_INCLUDE_DIRS}) include_directories(${PLAYERLOGMANAGER_INCLUDE_DIRS}) include_directories(${PLAYERGSTINTERFACE_INCLUDE_DIRS}) include_directories(${SUBTEC_INCLUDE_DIRS}) - +else() + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/drm) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/drm/helper) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/externals) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/externals/contentsecuritymanager) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/playerLogManager) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/middleware/drm/aes) + + add_subdirectory(middleware) +endif() if (CMAKE_INBUILT_AAMP_DEPENDENCIES) message("Building aamp support libraries") include_directories(support/aampabr) @@ -410,7 +438,11 @@ endif() set(LIBAAMP_SOURCES "${LIBAAMP_SOURCES}" "${LIBAAMP_MOCK_SOURCES}") add_library(aamp SHARED ${LIBAAMP_SOURCES}) -target_link_libraries(aamp tsb ${LIBAAMP_DEPENDS} ${LIBAAMP_MOCK_DEPENDS}) +if(CMAKE_TEST_MW) + target_link_libraries(aamp tsb ${LIBAAMP_DEPENDS} ${LIBAAMP_MOCK_DEPENDS}) +else() + target_link_libraries(aamp tsb playergstinterface playerfbinterface ${LIBAAMP_DEPENDS} ${LIBAAMP_MOCK_DEPENDS}) +endif() set_target_properties(aamp PROPERTIES COMPILE_FLAGS "${LIBAAMP_DEFINES} ${OS_CXX_FLAGS}") set_target_properties(aamp PROPERTIES PUBLIC_HEADER "main_aamp.h") set_target_properties(aamp PROPERTIES PRIVATE_HEADER "priv_aamp.h") @@ -432,13 +464,17 @@ set(AAMP_CLI_SOURCES test/aampcli/AampcliPrintf.cpp ) add_executable(aamp-cli ${AAMP_CLI_SOURCES}) -target_link_libraries(aamp-cli aamp tsb -${PLAYERFBINTERFACE_LINK_LIBRARIES} -${BASECONVERSION_LINK_LIBRARIES} -${PLAYERLOGMANAGER_LINK_LIBRARIES} -${PLAYERGSTINTERFACE_LINK_LIBRARIES} -${SUBTEC_LINK_LIBRARIES} -${AAMP_CLI_LD_FLAGS} "-lreadline") +if(CMAKE_TEST_MW) + target_link_libraries(aamp-cli aamp tsb + ${PLAYERFBINTERFACE_LINK_LIBRARIES} + ${BASECONVERSION_LINK_LIBRARIES} + ${PLAYERLOGMANAGER_LINK_LIBRARIES} + ${PLAYERGSTINTERFACE_LINK_LIBRARIES} + ${SUBTEC_LINK_LIBRARIES} + ${AAMP_CLI_LD_FLAGS} "-lreadline") +else() + target_link_libraries(aamp-cli aamp tsb playergstinterface playerfbinterface ${AAMP_CLI_LD_FLAGS} "-lreadline") +endif() #aamp-cli is not an ideal standalone app. It uses private aamp instance for debugging purposes set_target_properties(aamp-cli PROPERTIES COMPILE_FLAGS "${LIBAAMP_DEFINES} ${OS_CXX_FLAGS}") diff --git a/middleware/CMakeLists.txt b/middleware/CMakeLists.txt new file mode 100644 index 000000000..270718fca --- /dev/null +++ b/middleware/CMakeLists.txt @@ -0,0 +1,388 @@ +# LibPlayerGstInterface CMakeLists.txt +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.5) +project(Playergstinterface) + +option(DISABLE_SECURITY_TOKEN "Disable security token" OFF) + +if(DISABLE_SECURITY_TOKEN) + add_definitions(-DDISABLE_SECURITY_TOKEN) +endif() + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0>=1.18.0) +pkg_check_modules(GSTREAMERBASE REQUIRED gstreamer-app-1.0) + +include_directories(${GST_INCLUDE_DIRS} ${GSTREAMER_INCLUDE_DIRS} ${GSTREAMERBASE_INCLUDE_DIRS} ${GSTREAMERVIDEO_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} subtitle playerisobmff isobmff + closedcaptions + closedcaptions/subtec + drm drm/helper + drm/ocdm) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/externals) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/externals/contentsecuritymanager) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/externals/rdk) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/baseConversion) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/playerLogManager) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/playerJsonObject) + + +set(LIBPLAYERGSTINTERFACE_DEPENDS ${OS_LD_FLAGS} ${UUID_LINK_LIBRARIES} ${LIBCJSON_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES} ${GSTREAMER_LINK_LIBRARIES} ${CURL_LINK_LIBRARIES} ${LIBDASH_LINK_LIBRARIES} ${LibXml2_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES} ${OPENGL_LIBRARIES} ${GLEW_LIBRARIES} -ldl) + +#shoud be made part of PLAYERGSTINTERFACE cmakelists +add_subdirectory(baseConversion) +add_subdirectory(playerLogManager) +add_subdirectory(playerJsonObject) +add_subdirectory(externals) + +set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} closedcaptions/PlayerCCManager.cpp PlayerUtils.cpp) + +set(SUBTEC_CLASS_SOURCES playerisobmff/playerisobmffbox.cpp + playerisobmff/playerisobmffbuffer.cpp + subtec/subtecparser/WebVttSubtecParser.cpp + subtec/subtecparser/TtmlSubtecParser.cpp + subtec/subtecparser/TextStyleAttributes.cpp + subtec/subtecparser/WebvttSubtecDevInterface.cpp + playerJsonObject/PlayerJsonObject.cpp + ) + +if(CMAKE_PLATFORM_UBUNTU) + message("CMAKE_PLATFORM_UBUNTU set") + link_directories(${CMAKE_LIBRARY_PATH}) +endif() + +if(CMAKE_SOC_PLATFORM_RPI) + message("CMAKE_SOC_PLATFORM_RPI set") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DRPI=1") +endif() + +#update XCode scheme flags, harmless for non Darwin builds +set (CMAKE_CODE_GENERATE_SCHEME TRUE) +if (CMAKE_PLATFORM_UBUNTU OR CMAKE_SYSTEM_NAME STREQUAL Darwin) +if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(OPENGL_LIBRARIES "-framework OpenGL -framework GLUT") +else() + pkg_check_modules(OPENGL REQUIRED gl) + set(OPENGL_LIBRARIES "${OPENGL_LIBRARIES} -lglut") + pkg_check_modules(GLEW REQUIRED glew) +endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) +endif() + +# Mac OS X +if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + execute_process ( + COMMAND bash -c "xcrun --show-sdk-path" OUTPUT_VARIABLE osxSdkPath OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set(OS_CXX_FLAGS "${OS_CXX_FLAGS} -std=c++14 -g -x objective-c++ -Wno-inconsistent-missing-override -F${osxSdkPath}/System/Library/Frameworks") + set(OS_LD_FLAGS "${OS_LD_FLAGS} -F${osxSdkPath}/System/Library/Frameworks -framework Cocoa -L${osxSdkPath}/../MacOSX.sdk/usr/lib -L.libs/lib -L/usr/local/lib/") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isysroot ${osxSdkPath}/../MacOSX.sdk -I/usr/local/include") + string(STRIP ${OS_LD_FLAGS} OS_LD_FLAGS) + link_directories(${OPENSSL_LIBRARY_DIRS}) + set(CMAKE_THREAD_LIBS_INIT "-lpthread") + set(CMAKE_HAVE_THREADS_LIBRARY 1) + pkg_check_modules(GLIB REQUIRED GLib-2.0) + include_directories(${GLIB_INCLUDE_DIRS}) + + # XCode build flags. Even when using CLANG, the GCC name is required to enable the check + set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_FUNCTION "YES") + set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_VARIABLE "YES") +else() + set(USE_MAC_FOR_RANDOM_GEN "-DUSE_MAC_FOR_RANDOM_GEN") +endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + +set(LIBPLAYERGSTINTERFACE_HEADERS + closedcaptions/CCTrackInfo.h + GstUtils.h + PlayerUtils.h + GstUtils.h + PlayerMetadata.hpp + closedcaptions/PlayerCCManager.h + PlayerScheduler.h + gstplayertaskpool.h + GstHandlerControl.h + InterfacePlayerRDK.h + drm/DrmUtils.h + drm/aes/Aes.h + drm/HlsDrmBase.h + drm/DrmSystems.h + drm/DrmCallbacks.h + drm/ocdm/opencdmsessionadapter.h + drm/DrmJsonObject.h + vendor/SocInterface.h + vendor/amlogic/AmlogicSocInterface.h + vendor/realtek/RealtekSocInterface.h + vendor/brcm/BrcmSocInterface.h + vendor/default/DefaultSocInterface.h + subtitle/vttCue.h + ProcessHandler.h + ) + +install(FILES closedcaptions/CCTrackInfo.h + PlayerScheduler.h + gstplayertaskpool.h + GstHandlerControl.h + InterfacePlayerRDK.h + SocUtils.h + drm/DrmUtils.h + GstUtils.h + PlayerMetadata.hpp + closedcaptions/PlayerCCManager.h + PlayerUtils.h + drm/ocdm/opencdmsessionadapter.h + drm/aes/Aes.h + drm/DrmMemorySystem.h drm/DrmSessionManager.h drm/DrmSystems.h + drm/DrmData.h drm/DrmInfo.h drm/DrmMediaFormat.h drm/DrmCallbacks.h + drm/DrmSession.h drm/ClearKeyDrmSession.h drm/DrmSessionFactory.h drm/ocdm/opencdmsessionadapter.h + drm/helper/DrmHelper.h + drm/HlsDrmBase.h + drm/DrmConstants.h + drm/PlayerHlsDrmSessionInterface.h + drm/PlayerHlsDrmSessionInterfaceBase.h + drm/helper/VanillaDrmHelper.h + drm/HlsOcdmBridgeInterface.h + drm/HlsDrmSessionManager.h + vendor/SocInterface.h + subtitle/subtitleParser.h + subtec/subtecparser/WebVttSubtecParser.hpp + subtec/subtecparser/TtmlSubtecParser.hpp + subtec/subtecparser/WebvttSubtecDevInterface.hpp + subtec/subtecparser/TextStyleAttributes.h + subtec/libsubtec/SubtecPacket.hpp + playerisobmff/playerisobmffbuffer.h + playerisobmff/playerisobmffbox.h + DESTINATION include) + +set(SOURCES + InterfacePlayerRDK.cpp + SocUtils.cpp + GstUtils.cpp + GstHandlerControl.cpp + PlayerScheduler.cpp + gstplayertaskpool.cpp + PlayerUtils.cpp + drm/processProtectionHls.cpp + ProcessHandler.cpp + ) +if(NOT CMAKE_PLATFORM_UBUNTU AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + list(APPEND SOURCES vendor/amlogic/AmlogicSocInterface.cpp + vendor/realtek/RealtekSocInterface.cpp + vendor/brcm/BrcmSocInterface.cpp + vendor/default/DefaultSocInterface.cpp + vendor/SocInterface.cpp) +else() + list(APPEND SOURCES test/utests/fakes/FakeSocInterface.cpp) +endif() +set(LIBPLAYERGSTINTERFACE_DRM_SOURCES drm/PlayerHlsDrmSessionInterface.cpp + drm/DrmSessionManager.cpp + drm/DrmSession.cpp + drm/DrmSessionFactory.cpp + drm/helper/DrmHelper.cpp + drm/helper/DrmHelperFactory.cpp + drm/HlsOcdmBridgeInterface.cpp + drm/DrmUtils.cpp + drm/DrmSystems.h + drm/aes/Aes.cpp + drm/DrmJsonObject.cpp + ) + +if (CMAKE_PLATFORM_UBUNTU OR CMAKE_SYSTEM_NAME STREQUAL Darwin ) + if(CMAKE_SIM_DRM_SUPPORT) + set(CMAKE_USE_OPENCDM_ADAPTER TRUE) + set(CMAKE_USE_OPENCDM_ADAPTER_MOCKS TRUE) + set(CMAKE_USE_THUNDER_OCDM_API_0_2 TRUE) + set(CMAKE_USE_SECCLIENT TRUE) + set(CMAKE_USE_SECCLIENT_MOCKS TRUE) + endif() +endif() + +set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} ${SOURCES} ${SUBTEC_CLASS_SOURCES}) + +include_directories(${GSTVIDEO_INCLUDE_DIRS}) + +if(CMAKE_USE_THUNDER_OCDM_API_0_2) + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DUSE_THUNDER_OCDM_API_0_2") +endif() + +if(CMAKE_USE_OPENCDM_ADAPTER) + message("Using OPEN CDM support enabled") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DUSE_OPENCDM_ADAPTER") + + set(LIBPLAYERGSTINTERFACE_DRM_SOURCES "${LIBPLAYERGSTINTERFACE_DRM_SOURCES}" drm/HlsDrmSessionManager.cpp + drm/HlsOcdmBridge.cpp + drm/processProtectionHls.cpp + ) + + # DRM Helpers + if(CMAKE_USE_WIDEVINE) + set(LIBPLAYERGSTINTERFACE_HELP_SOURCES "${LIBPLAYERGSTINTERFACE_HELP_SOURCES}" drm/helper/WidevineDrmHelper.cpp) + endif() + + if(CMAKE_USE_CLEARKEY) + set(LIBPLAYERGSTINTERFACE_HELP_SOURCES "${LIBPLAYERGSTINTERFACE_HELP_SOURCES}" drm/helper/ClearKeyHelper.cpp) + endif() + + if(CMAKE_USE_PLAYREADY) + set(LIBPLAYERGSTINTERFACE_HELP_SOURCES "${LIBPLAYERGSTINTERFACE_HELP_SOURCES}" drm/helper/PlayReadyHelper.cpp) + endif() + + if(CMAKE_USE_VERIMATRIX) + message("CMAKE_USE_VERIMATRIX set") + set(LIBPLAYERGSTINTERFACE_HELP_SOURCES "${LIBPLAYERGSTINTERFACE_HELP_SOURCES}" drm/helper/VerimatrixHelper.cpp) + endif() +else() + message("No OpenCDM support enabled") +endif() + +if(CMAKE_USE_CLEARKEY) + set(LIBPLAYERGSTINTERFACE_DRM_SOURCES "${LIBPLAYERGSTINTERFACE_DRM_SOURCES}" drm/ClearKeyDrmSession.cpp) + set(LIBPLAYERGSTINTERFACE_HELP_SOURCES "${LIBPLAYERGSTINTERFACE_HELP_SOURCES}" drm/helper/ClearKeyHelper.cpp) + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DUSE_CLEARKEY") +endif() + +if(CMAKE_USE_OPENCDM_ADAPTER) + message("Using OPEN CDM ADAPTER") + # Include OpenCDM-related source files + set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} + drm/ocdm/opencdmsessionadapter.cpp + drm/ocdm/OcdmBasicSessionAdapter.cpp + drm/ocdm/OcdmGstSessionAdapter.cpp + ) + + # Add GStreamer video dependency + set(LIBPLAYERGSTINTERFACE_DEPENDS "${LIBPLAYERGSTINTERFACE_DEPENDS} -lgstvideo-1.0") + + if(CMAKE_USE_OPENCDM_ADAPTER_MOCKS) + # Add mock headers and sources if mock is enabled + set(LIBPLAYERGSTINTERFACE_HEADERS ${LIBPLAYERGSTINTERFACE_HEADERS} open_cdm.h open_cdm_adapter.h) + set(LIBPLAYERGSTINTERFACE_MOCK_SOURCES ${LIBPLAYERGSTINTERFACE_MOCK_SOURCES} test/mocks/opencdmMocks.cpp) + set(LIBPLAYERGSTINTERFACE_MOCK_DEPENDS -lgmock -lgtest) + else() + # Link with actual OpenCDM library + set(LIBPLAYERGSTINTERFACE_DEPENDS ${LIBPLAYERGSTINTERFACE_DEPENDS} "-locdm") + endif() + + # Find OpenCDM headers and include them + find_path(STAGING_INCDIR opencdm) + include_directories(${STAGING_INCDIR}/opencdm) + + # Find GStreamer headers and include them + find_path(STAGING_INCDIR gstreamer-1.0) + include_directories(${STAGING_INCDIR}/gstreamer-1.0) +endif() +set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} ${LIBPLAYERGSTINTERFACE_DRM_SOURCES} ${LIBPLAYERGSTINTERFACE_HELP_SOURCES} ) + +add_library(subtec SHARED subtec/libsubtec/PacketSender.cpp subtec/libsubtec/SubtecChannel.cpp) +set(SUBTEC_PUBLIC_HEADERS subtec/libsubtec/SubtecChannel.hpp subtec/libsubtec/SubtecAttribute.hpp) +set_target_properties(subtec PROPERTIES PUBLIC_HEADER "${SUBTEC_PUBLIC_HEADERS}") +set(SUBTEC_COMPILE_FLAGS "${CMAKE_CXX_FLAGS} -DSUBTEC_PACKET_DEBUG=1") +if(CMAKE_PLATFORM_UBUNTU) + message("CMAKE_PLATFORM_UBUNTU set") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DUBUNTU=1") + set(SUBTEC_COMPILE_FLAGS "${SUBTEC_COMPILE_FLAGS} -DUBUNTU=1") +endif() + +set_target_properties(subtec PROPERTIES COMPILE_FLAGS "${SUBTEC_COMPILE_FLAGS}") + +target_include_directories(subtec PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/playerjsonobject + ${CMAKE_CURRENT_SOURCE_DIR}/subtec/subtecparser + ${CMAKE_CURRENT_SOURCE_DIR}/subtec/libsubtec + ${CMAKE_CURRENT_SOURCE_DIR}/subtitle) + +install (TARGETS subtec + DESTINATION lib + PUBLIC_HEADER DESTINATION include + ) + +if (CMAKE_GST_SUBTEC_ENABLED) + set(CMAKE_SUBTITLE_SUPPORT TRUE) + message("CMAKE_GST_SUBTEC_ENABLED set") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DGST_SUBTEC_ENABLED") +endif() + +if (CMAKE_SUBTITLE_SUPPORT) + message("CMAKE_SUBTITLE_SUPPORT set") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DSUBTITLE_SUPPORTED") + find_path(STAGING_INCDIR closedcaption/ccDataReader.h) + include_directories(${STAGING_INCDIR}/closedcaption) + if (CMAKE_USE_CC_MANAGER_MOCKS) + set(LIBPLAYERGSTINTERFACE_MOCK_SOURCES ${LIBPLAYERGSTINTERFACE_MOCK_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/../test/fakes/ccManagerFakes.cpp) + else() + set(LIBSUBTECCONNECTOR_DEPENDS pthread rdkCCReader subtec) + endif() + + set(LIBSUBTECCONNECTOR_SOURCES + closedcaptions/subtec/SubtecConnector.cpp + closedcaptions/subtec/CCDataController.cpp) + + add_library(subtec_connector SHARED ${LIBSUBTECCONNECTOR_SOURCES}) + target_link_libraries(subtec_connector ${LIBSUBTECCONNECTOR_DEPENDS}) + target_link_libraries(subtec_connector playerlogmanager) + install(TARGETS subtec_connector DESTINATION lib) + + set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} closedcaptions/subtec/PlayerSubtecCCManager.cpp) + set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} closedcaptions/rialto/PlayerRialtoCCManager.cpp) +endif() +add_library(playergstinterface SHARED ${SOURCES} ${LIBPLAYERGSTINTERFACE_HEADERS} ${LIBPLAYERGSTINTERFACE_SOURCES} ${LIBPLAYERGSTINTERFACE_DRM_SOURCES} ${LIBPLAYERGSTINTERFACE_HELP_SOURCES}) + +target_include_directories(playergstinterface PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/externals + ${CMAKE_CURRENT_SOURCE_DIR}/subtec/subtecparser + ${CMAKE_CURRENT_SOURCE_DIR}/subtec/libsubtec + ${CMAKE_CURRENT_SOURCE_DIR}/subtitle + ${CMAKE_CURRENT_SOURCE_DIR}/playerisobmff + ${CMAKE_CURRENT_SOURCE_DIR}/playerjsonobject + ${CMAKE_CURRENT_SOURCE_DIR}/closedcaptions + ${CMAKE_CURRENT_SOURCE_DIR}/closedcaptions/subtec + ${CMAKE_CURRENT_SOURCE_DIR}/closedcaptions/rialto + ${CMAKE_CURRENT_SOURCE_DIR}/vendor) + +if (CMAKE_SUBTITLE_SUPPORT) + target_link_libraries(playergstinterface subtec_connector) +endif() + +string(STRIP "${LIBPLAYERGSTINTERFACE_DEPENDS}" LIBPLAYERGSTINTERFACE_DEPENDS) +message("Building middleware support libraries") +add_subdirectory(gst-plugins) + +target_link_libraries(playergstinterface ${LIBPLAYERGSTINTERFACE_DEPENDS}) + +target_link_libraries(playergstinterface ${GST_LINK_LIBRARIES} ${GSTREAMER_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES}) + +target_link_libraries(playergstinterface ${GSTVIDEO_LIBRARIES} ${LIBPLAYERGSTINTERFACE_DEPENDS}) + +if (NOT CMAKE_PLATFORM_UBUNTU AND NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(playergstinterface gstsvpext) +endif() + +set_target_properties(playergstinterface PROPERTIES COMPILE_FLAGS "${LIBPLAYERGSTINTERFACE_DEFINES} ${OS_CXX_FLAGS}") + +install(TARGETS playergstinterface + LIBRARY DESTINATION lib + PUBLIC_HEADER DESTINATION lib/include +) +target_link_libraries(subtec playerlogmanager) +target_link_libraries(playergstinterface ${GSTVIDEO_LIBRARIES} ${LIBPLAYERGSTINTERFACE_DEPENDS}) +target_link_libraries(playergstinterface subtec) +target_link_libraries(playergstinterface baseconversion) +target_link_libraries(playergstinterface playerfbinterface) +set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} {LIBPLAYERGSTINTERFACE_MOCK_SOURCES}) diff --git a/middleware/GstHandlerControl.cpp b/middleware/GstHandlerControl.cpp new file mode 100644 index 000000000..11f151983 --- /dev/null +++ b/middleware/GstHandlerControl.cpp @@ -0,0 +1,85 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GstHandlerControl.h" +#include "PlayerLogManager.h" +#include +#include + +GstHandlerControl::ScopeHelper& GstHandlerControl::ScopeHelper::operator=(GstHandlerControl::ScopeHelper&& other) +{ + if(mpController) + { + mpController->handlerEnd(); + } + mpController = other.mpController; + other.mpController = nullptr; + return *this; +} + +void GstHandlerControl::handlerEnd() +{ + std::lock_guard guard(mSync); + mInstanceCount--; + if(mInstanceCount<0) + { + mInstanceCount=0; + MW_LOG_ERR("instanceCount<0"); + } + mDoneCond.notify_one(); +} + +bool GstHandlerControl::isEnabled() const +{ + std::lock_guard guard(mSync); + return mEnabled; +} + +GstHandlerControl::ScopeHelper GstHandlerControl::getScopeHelper() +{ + std::lock_guard guard(mSync); + mInstanceCount++; + return GstHandlerControl::ScopeHelper(this); +} + +bool GstHandlerControl::waitForDone(int MaximumDelayMilliseconds, std::string name) +{ + const std::chrono::steady_clock::time_point end = + std::chrono::milliseconds{MaximumDelayMilliseconds} + std::chrono::steady_clock::now(); + + disable(); + + std::unique_lock lock(mSync); + while(mInstanceCount && (std::chrono::steady_clock::now() +#include + + +/** + * @brief GstHandlerControl boilerplate to include at the outer scope of the handler/callback to be managed by a corresponding GstHandlerControl + * Note these macros are intentionally not wrapped in {} as the scopeHelper must remain at the outer scope of the handler/callback + */ +#define HANDLER_CONTROL_HELPER(HANDLER_CONTROL, RTN) auto scopeHelper = HANDLER_CONTROL.getScopeHelper(); \ + if(scopeHelper.returnStraightAway()) return RTN; + +#define HANDLER_CONTROL_HELPER_CALLBACK_VOID() auto scopeHelper = privatePlayer->gstPrivateContext->callbackControl.getScopeHelper(); + +/** + * @class HandlerControl + * @brief Provides a thread safe way of disabling & confirming that handlers or callbacks are disabled (i.e. GstHandlerControl::done()) when + * the corresponding handlers implement the boiler plate using GstHandlerControl::getScopeHelper() & GstHandlerControl::ScopeHelper::returnStraightAway() + */ +class GstHandlerControl{ + public: + + bool isEnabled() const; + + /** + * @class GstHandlerControl::ScopeHelper + * @brief An object that Indicates if the handler is permitted to perform any action (i.e. via GstHandlerControl::ScopeHelper::returnStraightAway()) and + * signals to its corresponding GstHandlerControl object when the handler is complete (so that GstHandlerControl can maintain an accurate count of the number of handlers running) + * ScopeHelper instances should only be accessed from one thread i.e. the corresponding handler. + * GstHandlerControl's methods are threadsafe. + * The lifetime of referenced GstHandlerControl objects must exceed the lifetime of referencing ScopeHelpers. + */ + class ScopeHelper + { + private: + GstHandlerControl* mpController; + + public: + ScopeHelper(): mpController(nullptr){/*empty*/} + + explicit ScopeHelper(GstHandlerControl* pController):mpController(pController){/*empty*/} + + /** + * @brief move constructor + * The new object takes on the responsibility for signaling any corresponding GstHandlerControl from 'other'. + * After this function 'other' will not reference or signal any GstHandlerControl. It's scope is irrelevant. + */ + ScopeHelper(ScopeHelper&& other): mpController(nullptr) + { + mpController = other.mpController; + other.mpController = nullptr; //prevents ScopeHelper::handlerEnd() being called by other + } + + /** + * @brief move assignment + * The assigned to object will signal any currently referenced GstHandlerControl first. Then + * the assigned to object will take on the responsibility any GstHandlerControl referenced by other. + * After this function 'other' will not reference or signal any GstHandlerControl. It's scope is irrelevant. + */ + ScopeHelper& operator=(GstHandlerControl::ScopeHelper&& other); + + //not copyable this would invalidate the count held in GstHandlerControl + ScopeHelper(const ScopeHelper& other)=delete; + ScopeHelper& operator=(const ScopeHelper& other)=delete; + ~ScopeHelper() + { + if(mpController) + { + mpController->handlerEnd(); + } + } + + /** + * @brief should be called at the start of each handler e.g. using HANDLER_CONTROL_HELPER + * @return returns true when the handler should exit straight away without performing any action + */ + bool returnStraightAway() const + { + auto pController = mpController; + return !(pController && pController->isEnabled()); + } + }; + + private: + bool mEnabled; + int mInstanceCount; + mutable std::mutex mSync; + std::condition_variable mDoneCond; + + /** + * @brief Used by ScopeDetecter destructor to indicate that its associated handler has exited + */ + void handlerEnd(); + + public: + GstHandlerControl():mEnabled(true), mInstanceCount(0), mSync(), mDoneCond() + { + //empty + } + + /** + * @brief Indicate to new instances of the handler to run i.e. new ScopeHelpers will return false from ScopeDetecter::returnStraightAway() + */ + void enable() + { + std::lock_guard guard(mSync); + mEnabled = true; + } + + + /** + * @brief Indicate that any future handlers should return without performing any action i.e. new ScopeHelpers will return true from ScopeDetecter::returnStraightAway() + */ + void disable() + { + std::lock_guard guard(mSync); + mEnabled = false; + } + + /** + * @brief The number of instances of the handler currently running (+1 by getScopeHelper(), -1 when the returned ScopeHelper goes out of scope) + */ + int instancesRunning() + { + std::lock_guard guard(mSync); + return mInstanceCount; + } + /** + * @brief Call at the start of the handler, and store the return value in a local variable with handler scope. + * The returnStraightAway() method of the returned object should be called straight afterwards. e.g. using HANDLER_CONTROL_HELPER + * @return A GstHandlerControl::ScopeHelper linked to this object + */ + ScopeHelper getScopeHelper(); + + /** + * @brief + * call disable(), & wait for up to + * MaximumDelayMilliseconds for handlers to exit. + * log an error message if handlers are still running after MaximumDelayMilliseconds + * @return true if no handlers are running when the function exits e.g. instancesRunning() ==0 + */ + bool waitForDone(int MaximumDelayMilliseconds, std::string errormessage); +}; + +#endif /* GST_HANDLER_CONTROL_H */ diff --git a/middleware/GstUtils.cpp b/middleware/GstUtils.cpp new file mode 100644 index 000000000..2e5815112 --- /dev/null +++ b/middleware/GstUtils.cpp @@ -0,0 +1,123 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include "GstUtils.h" +#include +#include "PlayerUtils.h" + +/** + * @brief Get the GStreamer Caps based on the provided format and platform. + * + * @param format The format of the GStreamer stream output. + * @param platform The platform type for which the caps are being generated. + * @return GstCaps* A pointer to the GstCaps object describing the capabilities of the media stream. + */ +GstCaps* GetCaps(GstStreamOutputFormat format) +{ + GstCaps * caps = NULL; + std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + + switch (format) + { + case GST_FORMAT_MPEGTS: + caps = gst_caps_new_simple ("video/mpegts", + "systemstream", G_TYPE_BOOLEAN, TRUE, + "packetsize", G_TYPE_INT, 188, NULL); + break; + case GST_FORMAT_ISO_BMFF: + caps = gst_caps_new_simple("video/quicktime", NULL, NULL); + break; + case GST_FORMAT_AUDIO_ES_MP3: + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 1, NULL); + break; + case GST_FORMAT_AUDIO_ES_AAC: + caps = gst_caps_new_simple ("audio/mpeg", + "mpegversion", G_TYPE_INT, 2, + "stream-format", G_TYPE_STRING, "adts", NULL); + break; + case GST_FORMAT_AUDIO_ES_AC3: + caps = gst_caps_new_simple ("audio/x-ac3", NULL, NULL); + break; + + case GST_FORMAT_AUDIO_ES_AC4: + caps = gst_caps_new_simple ("audio/x-ac4", NULL, NULL); + break; + + case GST_FORMAT_SUBTITLE_TTML: + caps = gst_caps_new_simple("application/ttml+xml", NULL, NULL); + break; + case GST_FORMAT_SUBTITLE_WEBVTT: + caps = gst_caps_new_simple("text/vtt", NULL, NULL); + break; + case GST_FORMAT_SUBTITLE_MP4: + caps = gst_caps_new_simple("application/mp4", NULL, NULL); + break; + case GST_FORMAT_AUDIO_ES_ATMOS: + // Todo :: a) Test with all platforms if atmos works + // b) Test to see if x-eac3 config is enough for atmos stream. + // if x-eac3 is enough then both switch cases can be combined + caps = gst_caps_new_simple ("audio/x-eac3", NULL, NULL); + break; + case GST_FORMAT_AUDIO_ES_EC3: + caps = gst_caps_new_simple ("audio/x-eac3", NULL, NULL); + break; + case GST_FORMAT_VIDEO_ES_H264: + caps = gst_caps_new_simple ("video/x-h264", NULL, NULL); + socInterface->SetH264Caps(caps); + break; + case GST_FORMAT_VIDEO_ES_HEVC: + caps = gst_caps_new_simple("video/x-h265", NULL, NULL); + socInterface->SetHevcCaps(caps); + break; + case GST_FORMAT_VIDEO_ES_MPEG2: + caps = gst_caps_new_simple ("video/mpeg", + "mpegversion", G_TYPE_INT, 2, + "systemstream", G_TYPE_BOOLEAN, FALSE, NULL); + break; //CID:81305 - Using break statement + case GST_FORMAT_UNKNOWN: + g_print("Unknown format %d", format); + break; + case GST_FORMAT_INVALID: + default: + g_print("Unsupported format %d", format); + break; + } + return caps; +} + +/** + * @brief Initialize the GStreamer library for the player CLI. + * @param argc A pointer to the argument count. + * @param argv A pointer to the argument vector. + */ + +void PlayerCliGstInit(int *argc, char ***argv) +{ + gst_init(argc,argv); +} + +/** + * @brief Terminate the GStreamer library for the player CLI. + */ +void PlayerCliGstTerm() +{ + gst_deinit(); +} + diff --git a/middleware/GstUtils.h b/middleware/GstUtils.h new file mode 100644 index 000000000..cffd05f0a --- /dev/null +++ b/middleware/GstUtils.h @@ -0,0 +1,123 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef GST_UTILS_H +#define GST_UTILS_H + +#include +#include "SocInterface.h" + +/** + * @enum GstMediaType + * @brief Media types + * + * @note Please maintain the order video, audio, subtitle, and aux_audio in future. + * This order is to be maintained across fragment, init, and playlist media types. + * These enums are used in a lot of calculations in AAMP code and breaking the order will bring a lot of issues. + * This order is also followed in other enums like AampCurlInstance and TrackType. + */ +enum GstMediaType +{ + eGST_MEDIATYPE_VIDEO, /**< Type video */ + eGST_MEDIATYPE_AUDIO, /**< Type audio */ + eGST_MEDIATYPE_SUBTITLE, /**< Type subtitle */ + eGST_MEDIATYPE_AUX_AUDIO, /**< Type auxiliary audio */ + eGST_MEDIATYPE_MANIFEST, /**< Type manifest */ + eGST_MEDIATYPE_LICENCE, /**< Type license */ + eGST_MEDIATYPE_IFRAME, /**< Type iframe */ + eGST_MEDIATYPE_INIT_VIDEO, /**< Type video init fragment */ + eGST_MEDIATYPE_INIT_AUDIO, /**< Type audio init fragment */ + eGST_MEDIATYPE_INIT_SUBTITLE, /**< Type subtitle init fragment */ + eGST_MEDIATYPE_INIT_AUX_AUDIO, /**< Type auxiliary audio init fragment */ + eGST_MEDIATYPE_PLAYLIST_VIDEO, /**< Type video playlist */ + eGST_MEDIATYPE_PLAYLIST_AUDIO, /**< Type audio playlist */ + eGST_MEDIATYPE_PLAYLIST_SUBTITLE, /**< Type subtitle playlist */ + eGST_MEDIATYPE_PLAYLIST_AUX_AUDIO, /**< Type auxiliary audio playlist */ + eGST_MEDIATYPE_PLAYLIST_IFRAME, /**< Type Iframe playlist */ + eGST_MEDIATYPE_INIT_IFRAME, /**< Type IFRAME init fragment */ + eGST_MEDIATYPE_DSM_CC, /**< Type digital storage media command and control (DSM-CC) */ + eGST_MEDIATYPE_IMAGE, /**< Type image for thumbnail playlist */ + eGST_MEDIATYPE_DEFAULT /**< Type unknown */ +}; + +/** + * @enum GstStreamOutputFormat + * @brief Stream output formats + */ +enum GstStreamOutputFormat +{ + GST_FORMAT_INVALID, /**< Invalid format */ + GST_FORMAT_MPEGTS, /**< MPEG Transport Stream */ + GST_FORMAT_ISO_BMFF, /**< ISO Base Media File format */ + GST_FORMAT_AUDIO_ES_MP3, /**< MP3 Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_AAC, /**< AAC Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_AC3, /**< AC3 Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_EC3, /**< Dolby Digital Plus Elementary Stream */ + GST_FORMAT_AUDIO_ES_ATMOS, /**< ATMOS Audio stream */ + GST_FORMAT_AUDIO_ES_AC4, /**< AC4 Dolby Audio stream */ + GST_FORMAT_VIDEO_ES_H264, /**< MPEG-4 Video Elementary Stream */ + GST_FORMAT_VIDEO_ES_HEVC, /**< HEVC video elementary stream */ + GST_FORMAT_VIDEO_ES_MPEG2, /**< MPEG-2 Video Elementary Stream */ + GST_FORMAT_SUBTITLE_WEBVTT, /**< WebVTT subtitle Stream */ + GST_FORMAT_SUBTITLE_TTML, /**< TTML subtitle Stream */ + GST_FORMAT_SUBTITLE_MP4, /**< Generic MP4 stream */ + GST_FORMAT_UNKNOWN /**< Unknown Format */ +}; + +/** + * @fn gstGetMediaTypeName + * @brief Get the name of the media type + * + * @param[in] mediaType The media type + * @return The name of the media type + */ +const char *gstGetMediaTypeName(GstMediaType mediaType); + +/** + * @fn PlayerCliGstInit + * @brief Initialize GStreamer for the player CLI + * + * @param[in] argc Pointer to number of args for gst_init + * @param[in] argv Pointer to array args for gst_init + */ +void PlayerCliGstInit(int *argc, char ***argv); + +/** + * @fn PlayerCliGstTerm + * @brief Terminate GStreamer for the player CLI + */ +void PlayerCliGstTerm(); + +/** + * @fn GetCaps + * @brief Get the GStreamer capabilities for the given format and platform + * + * @param[in] format The stream output format + * @return The GStreamer capabilities + */ +GstCaps* GetCaps(GstStreamOutputFormat format); + +/** + * @fn GetCurrentTimeMS + * @brief Get the current time in milliseconds + * + * @return The current time in milliseconds + */ +long long GetCurrentTimeMS(void); + +#endif /* GST_UTILS_H */ diff --git a/middleware/InterfacePlayerPriv.h b/middleware/InterfacePlayerPriv.h new file mode 100755 index 000000000..6006c7ff6 --- /dev/null +++ b/middleware/InterfacePlayerPriv.h @@ -0,0 +1,314 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef INTERFACE_PLAYER_PRIV_H +#define INTERFACE_PLAYER_PRIV_H + +#include +#include +#include +#include +#include +#include +#include "PlayerScheduler.h" +#include +#include +#include +#include +#include +#include "GstHandlerControl.h" +#include "gstplayertaskpool.h" +#include +#include +#include +#include +#include "SocInterface.h" +#include "InterfacePlayerRDK.h" +#include "GstUtils.h" + +#define GST_ELEMENT_GET_STATE_RETRY_CNT_MAX 5 +#define GST_TRACK_COUNT 4 /**< internal use - audio+video+sub+aux track */ +#define VIDEO_COORDINATES_SIZE 32 +#define GST_TASK_ID_INVALID 0 +#define GST_NORMAL_PLAY_RATE 1 +#define GST_ERROR_DESCRIPTION_LENGTH 256 +#define NOW_STEADY_TS_MS std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() /**< Getting current steady clock in milliseconds */ + +typedef enum +{ + eGST_MEDIAFORMAT_HLS, /**< HLS Media */ + eGST_MEDIAFORMAT_DASH, /**< Dash Media */ + eGST_MEDIAFORMAT_PROGRESSIVE, /**< Progressive Media */ + eGST_MEDIAFORMAT_HLS_MP4, /**< HLS mp4 Format */ + eGST_MEDIAFORMAT_OTA, /**< OTA Media */ + eGST_MEDIAFORMAT_HDMI, /**< HDMI Format */ + eGST_MEDIAFORMAT_COMPOSITE, /**< Composite Media */ + eGST_MEDIAFORMAT_SMOOTHSTREAMINGMEDIA, /**< Smooth streaming Media */ + eGST_MEDIAFORMAT_RMF, /**< RMF media */ + eGST_MEDIAFORMAT_UNKNOWN /**< Unknown media format */ +} GstMediaFormat; +typedef enum +{ + eGST_PLAY_FLAG_VIDEO = (1 << 0), /**< value is 0x001 */ + eGST_PLAY_FLAG_AUDIO = (1 << 1), /**< value is 0x002 */ + eGST_PLAY_FLAG_TEXT = (1 << 2), /**< value is 0x004 */ + eGST_PLAY_FLAG_VIS = (1 << 3), /**< value is 0x008 */ + eGST_PLAY_FLAG_SOFT_VOLUME = (1 << 4), /**< value is 0x010 */ + eGST_PLAY_FLAG_NATIVE_AUDIO = (1 << 5), /**< value is 0x020 */ + eGST_PLAY_FLAG_NATIVE_VIDEO = (1 << 6), /**< value is 0x040 */ + eGST_PLAY_FLAG_DOWNLOAD = (1 << 7), /**< value is 0x080 */ + eGST_PLAY_FLAG_BUFFERING = (1 << 8), /**< value is 0x100 */ + eGST_PLAY_FLAG_DEINTERLACE = (1 << 9), /**< value is 0x200 */ + eGST_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10) /**< value is 0x400 */ +} eGstPlayFlags; + +enum GstVideoZoomMode +{ + GST_VIDEO_ZOOM_NONE = 0, /**< Video Zoom None */ + GST_VIDEO_ZOOM_DIRECT = 1, /**< Video Zoom Direct */ + GST_VIDEO_ZOOM_NORMAL = 2, /**< Video Zoom Normal */ + GST_VIDEO_ZOOM_16X9_STRETCH = 3, /**< Video Zoom 16x9 stretch */ + GST_VIDEO_ZOOM_4x3_PILLAR_BOX = 4, /**< Video Zoom 4x3 pillar box */ + GST_VIDEO_ZOOM_FULL = 5, /**< Video Zoom Full */ + GST_VIDEO_ZOOM_GLOBAL = 6 /**< Video Zoom Global */ +}; + +enum GstEOSInjectionModeCode +{ + /* EOS events are only injected into the gstreamer pipeline by + * InterfacePlayerRDK::EndOfStreamReached() & InterfacePlayerRDK::Discontinuity(). + */ + GstEOS_INJECTION_MODE_NO_EXTRA, + + /* In addition to the EOS_INJECTION_MODE_NO_EXTRA cases + * EOS is injected in InterfacePlayerRDK::Stop() prior to setting the state to null. + */ + GstEOS_INJECTION_MODE_STOP_ONLY, +}; + +typedef enum +{ + eGST_STATE_IDLE, /**< 0 - Player is idle */ + eGST_STATE_INITIALIZING, /**< 1 - Player is initializing a particular content */ + eGST_STATE_INITIALIZED, /**< 2 - Player has initialized for a content successfully */ + eGST_STATE_PREPARING, /**< 3 - Player is loading all associated resources */ + eGST_STATE_PREPARED, /**< 4 - Player has loaded all associated resources successfully */ + eGST_STATE_BUFFERING, /**< 5 - Player is in buffering state */ + eGST_STATE_PAUSED, /**< 6 - Playback is paused */ + eGST_STATE_SEEKING, /**< 7 - Seek is in progress */ + eGST_STATE_PLAYING, /**< 8 - Playback is in progress */ + eGST_STATE_STOPPING, /**< 9 - Player is stopping the playback */ + eGST_STATE_STOPPED, /**< 10 - Player has stopped playback successfully */ + eGST_STATE_COMPLETE, /**< 11 - Playback completed */ + eGST_STATE_ERROR, /**< 12 - Error encountered and playback stopped */ + eGST_STATE_RELEASED, /**< 13 - Player has released all resources for playback */ + eGST_STATE_BLOCKED /**< 14 - Player has blocked and cant play content*/ +} GstPrivPlayerState; + +struct gst_media_stream +{ + GstElement *sinkbin; /**< Sink element to consume data */ + GstElement *source; /**< to provide data to the pipeline */ + GstStreamOutputFormat format; /**< Stream output format for this stream */ + bool pendingSeek; /**< Flag denotes if a seek event has to be sent to the source */ + bool resetPosition; /**< To indicate that the position of the stream is reset */ + bool bufferUnderrun; + bool eosReached; /**< To indicate the status of End of Stream reached */ + bool sourceConfigured; /**< To indicate that the current source is initialized and configured */ + pthread_mutex_t sourceLock; + uint32_t timeScale; + int32_t trackId; /**< Current Audio Track Id,so far it is implemented for AC4 track selection only */ + bool firstBufferProcessed; /**< Indicates if the first buffer is processed in this stream */ + GstPad *demuxPad; /**< Demux src pad >*/ + gulong demuxProbeId; /**< Demux pad probe ID >*/ + + gst_media_stream() : sinkbin(NULL), source(NULL), format(GST_FORMAT_INVALID), + pendingSeek(false), resetPosition(false), + bufferUnderrun(false), eosReached(false), sourceConfigured(false), sourceLock(PTHREAD_MUTEX_INITIALIZER), timeScale(1), trackId(-1), firstBufferProcessed(false), demuxPad(NULL), demuxProbeId(0) + { + } + + ~gst_media_stream() + { + g_clear_object(&sinkbin); + g_clear_object(&source); + } + + gst_media_stream(const gst_media_stream &) = delete; + + gst_media_stream &operator=(const gst_media_stream &) = delete; +}; + +/** + * @struct GstPlayerPriv + * @brief Holds private variables of InterfacePlayerRDK + */ +struct GstPlayerPriv +{ + GstPlayerPriv(const GstPlayerPriv &) = delete; + GstPlayerPriv &operator=(const GstPlayerPriv &) = delete; + + gst_media_stream stream[GST_TRACK_COUNT]; + MonitorAVState monitorAVstate; + GstElement *pipeline; /**< GstPipeline used for playback. */ + GstBus *bus; /**< Bus for receiving GstEvents from pipeline. */ + guint64 total_bytes; + gint n_audio; /**< Number of audio tracks. */ + gint current_audio; /**< Offset of current audio track. */ + std::mutex TaskControlMutex; /**< For scheduling/de-scheduling or resetting async tasks/variables and timers */ + GstTaskControlData firstProgressCallbackIdleTask; + guint periodicProgressCallbackIdleTaskId; /**< ID of timed handler created for notifying progress events. */ + guint bufferingTimeoutTimerId; /**< ID of timer handler created for buffering timeout. */ + GstElement *video_dec; /**< Video decoder used by pipeline. */ + GstElement *audio_dec; /**< Audio decoder used by pipeline. */ + GstElement *video_sink; /**< Video sink used by pipeline. */ + GstElement *audio_sink; /**< Audio sink used by pipeline. */ + GstElement *subtitle_sink; /**< Subtitle sink used by pipeline. */ + GstTaskPool *task_pool; /**< Task pool in case RT priority is needed. */ + + int rate; /**< Current playback rate. */ + GstVideoZoomMode zoom; /**< Video-zoom setting. */ + bool videoMuted; /**< Video mute status. */ + bool audioMuted; /**< Audio mute status. */ + std::mutex volumeMuteMutex; /**< Mutex to ensure setVolumeOrMuteUnMute is thread-safe. */ + bool subtitleMuted; /**< Subtitle mute status. */ + double audioVolume; /**< Audio volume. */ + guint eosCallbackIdleTaskId; /**< ID of idle handler created for notifying EOS event. */ + std::atomic eosCallbackIdleTaskPending; /**< Set if any eos callback is pending. */ + bool firstFrameReceived; /**< Flag that denotes if first frame was notified. */ + bool pendingPlayState; /**< Flag that denotes if set pipeline to PLAYING state is pending. */ + bool decoderHandleNotified; /**< Flag that denotes if decoder handle was notified. */ + guint firstFrameCallbackIdleTaskId; /**< ID of idle handler created for notifying first frame event. */ + GstEvent *protectionEvent[GST_TRACK_COUNT]; /**< GstEvent holding the pssi data to be sent downstream. */ + std::atomic firstFrameCallbackIdleTaskPending; /**< Set if any first frame callback is pending. */ + bool using_westerossink; /**< true if westeros sink is used as video sink */ + bool usingRialtoSink; /**< true if rialto sink is used for video and audio sinks */ + bool usingClosedCaptionsControl; /**< true if subtitle sink being used for CC control */ + char videoRectangle[VIDEO_COORDINATES_SIZE]; + bool pauseOnStartPlayback; /**< true if should start playback paused */ + std::atomic eosSignalled; /**< Indicates if EOS has signaled */ + gboolean buffering_enabled; /**< enable buffering based on multiqueue */ + gboolean buffering_in_progress; /**< buffering is in progress */ + guint buffering_timeout_cnt; /**< make sure buffering_timeout doesn't get stuck */ + GstState buffering_target_state; /**< the target state after buffering */ + gint64 lastKnownPTS; /**< To store the PTS of last displayed video */ + long long ptsUpdatedTimeMS; /**< Timestamp when PTS was last updated */ + guint ptsCheckForEosOnUnderflowIdleTaskId; /**< ID of task to ensure video PTS is not moving before notifying EOS on underflow. */ + int numberOfVideoBuffersSent; /**< Number of video buffers sent to pipeline */ + gint64 segmentStart; /**< segment start value; required when qtdemux is enabled or restamping is disabled; -1 to send a segment.start query to gstreamer */ + GstQuery *positionQuery; /**< pointer that holds a position query object */ + GstQuery *durationQuery; /**< pointer that holds a duration query object */ + bool paused; /**< if pipeline is deliberately put in PAUSED state due to user interaction */ + GstState pipelineState; /**< current state of pipeline */ + GstTaskControlData firstVideoFrameDisplayedCallbackTask; /**< Task control data of the handler created for notifying state changed to Playing */ + bool firstTuneWithWesterosSinkOff; /**< track if first tune was done for Realtekce build */ + long long decodeErrorMsgTimeMS; /**< Timestamp when decode error message last posted */ + int decodeErrorCBCount; /**< Total decode error cb received within threshold time */ + bool progressiveBufferingEnabled; + bool progressiveBufferingStatus; + bool forwardAudioBuffers; /**< flag denotes if audio buffers to be forwarded to aux pipeline */ + bool enableSEITimeCode; /**< Enables SEI Time Code handling */ + bool firstVideoFrameReceived; /**< flag that denotes if first video frame was notified. */ + bool firstAudioFrameReceived; /**< flag that denotes if first audio frame was notified */ + int NumberOfTracks; /**< Indicates the number of tracks */ + GstPlaybackQualityStruct playbackQuality; /**< video playback quality info */ + struct CallbackData + { + gpointer instance; + gulong id; + std::string name; + CallbackData(gpointer _instance, gulong _id, std::string _name) : instance(_instance), id(_id), name(_name) {}; + CallbackData(const CallbackData &original) : instance(original.instance), id(original.id), name(original.name) {}; + CallbackData(CallbackData &&original) : instance(original.instance), id(original.id), name(original.name) {}; + CallbackData &operator=(const CallbackData &) = delete; + CallbackData &operator=(CallbackData &&original) + { + instance = std::move(original.instance); + id = std::move(original.id); + name = std::move(original.name); + return *this; + } + ~CallbackData() {}; + }; + std::mutex mSignalVectorAccessMutex; + std::vector mCallBackIdentifiers; + GstHandlerControl aSyncControl; + GstHandlerControl syncControl; + GstHandlerControl callbackControl; + + bool filterAudioDemuxBuffers; /**< flag to filter audio demux buffers */ + double seekPosition; /**< the position to seek the pipeline to in seconds */ + GstPlayerPriv(); + ~GstPlayerPriv(); +}; + +class InterfacePlayerPriv +{ + private: + protected: + public: + InterfacePlayerPriv(); + ~InterfacePlayerPriv(); + GstPlayerPriv *gstPrivateContext; + std::shared_ptr socInterface; + std::string mPlayerName; + + /** + * @brief Connects a signal to a handler. + * + * @param[in] instance The instance to connect the signal to. + * @param[in] detailed_signal The detailed signal to connect. + * @param[in] c_handler The callback handler for the signal. + * @param[in] data User data to pass to the callback handler. + */ + void SignalConnect(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data); + + /** + * @brief SendGstEvents + * @param[in] mediaType stream type + * @param[in] pts position value of first buffer + */ + void SendGstEvents(int mediaType, GstClockTime pts, int enableGstPosQuery, bool enablePTSReStamp, int vodTrickModeFPS); + + /** + * @brief Sends a new segment event. + * @param[in] mediaType The type of media stream. + * @param[in] startPts The start PTS value. + * @param[in] stopPts The stop PTS value. + */ + void SendNewSegmentEvent(int mediaType, GstClockTime startPts, GstClockTime stopPts); + + /** + * @fn SendQtDemuxOverrideEvent + * @param[in] mediaType stream type + * @param[in] pts position value of buffer + * @param[in] ptr buffer pointer + * @param[in] len length of buffer + * @ret TRUE if override is enabled, FALSE otherwise + */ + gboolean SendQtDemuxOverrideEvent(int mediaType, GstClockTime pts, bool enablePTSReStamp, int vodTrickModeFPS, const void *ptr = nullptr, size_t len = 0); + + /** + * @fn ForwardBuffersToAuxPipeline + * + * @param[in] buffer - input buffer to be forwarded + */ + void ForwardBuffersToAuxPipeline(GstBuffer *buffer, bool pauseInjector, void *user_data); + +}; +#endif diff --git a/middleware/InterfacePlayerRDK.cpp b/middleware/InterfacePlayerRDK.cpp new file mode 100644 index 000000000..ef79f1a45 --- /dev/null +++ b/middleware/InterfacePlayerRDK.cpp @@ -0,0 +1,5350 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "mp4demux.hpp" +#include +#include "InterfacePlayerRDK.h" +#include "InterfacePlayerPriv.h" +#include +#include +#include +#include "PlayerLogManager.h" +#include "GstUtils.h" +#include +#include "PlayerExternalsInterface.h" //ToDo: Replace once outputprotection moved to middleware +#include +#include "TextStyleAttributes.h" +#include +#include +#ifdef USE_EXTERNAL_STATS +#include "player-xternal-stats.h" +#endif +#include "PlayerUtils.h" + +#define DEFAULT_BUFFERING_TO_MS 10 /**< TimeOut interval to check buffer fullness */ +#define DEFAULT_BUFFERING_MAX_MS (1000) /**< max buffering time */ +#define DEFAULT_BUFFERING_MAX_CNT (DEFAULT_BUFFERING_MAX_MS/DEFAULT_BUFFERING_TO_MS) /**< max buffering timeout count */ +#define NORMAL_PLAY_RATE 1 +#define DEFAULT_TIMEOUT_FOR_SOURCE_SETUP (1000) /**< Default timeout value in milliseconds */ +#define DEFAULT_AVSYNC_FREERUN_THRESHOLD_SECS 12 /**< Currently MAX FRAG DURATION + 2*/ +#define INVALID_RATE -9999 + + +#if GLIB_CHECK_VERSION(2, 68, 0) +// avoid deprecated g_memdup when g_memdup2 available +#define PLAYER_G_MEMDUP(src, size) g_memdup2((src), (gsize)(size)) +#else +#define PLAYER_G_MEMDUP(src, size) g_memdup((src), (guint)(size)) +#endif +#define GST_DELAY_BETWEEN_PTS_CHECK_FOR_EOS_ON_UNDERFLOW 500 /**< A timeout interval in milliseconds to check pts in case of underflow */ +#define GST_MIN_DECODE_ERROR_INTERVAL 10000 /**< Minimum time interval in milliseconds between two decoder error CB to send anomaly error */ +#define BUFFERING_TIMEOUT_PRIORITY -70 /**< 0 is DEFAULT priority whereas -100 is the HIGH_PRIORITY */ + + +// for now name is being kept as aamp should be changed when gst-plugins are migrated +static const char* GstPluginNamePR = "playreadydecryptor"; +static const char* GstPluginNameWV = "widevinedecryptor"; +static const char* GstPluginNameCK = "clearkeydecryptor"; +static const char* GstPluginNameVMX = "verimatrixdecryptor"; +#define GST_MIN_PTS_UPDATE_INTERVAL 4000 /**< Time duration in milliseconds if exceeded and pts has not changed; it is concluded pts is not changing */ + +#include +#define GST_NORMAL_PLAY_RATE 1 + +/*InterfacePlayerRDK constructor*/ +InterfacePlayerRDK::InterfacePlayerRDK() : +mProtectionLock(), mPauseInjector(false), mSourceSetupMutex(), stopCallback(NULL), tearDownCb(NULL), notifyFirstFrameCallback(NULL), +mSourceSetupCV(), mScheduler(), callbackMap(), setupStreamCallbackMap(), mDrmSystem(NULL), mEncrypt(NULL), mDRMSessionManager(NULL) +{ + interfacePlayerPriv = new InterfacePlayerPriv(); + m_gstConfigParam = new Configs(); + m_gstConfigParam->framesToQueue = SocUtils::RequiredQueuedFrames(); + pthread_mutex_init(&mProtectionLock, NULL); + for (int i = 0; i < GST_TRACK_COUNT; i++) + pthread_mutex_init(&interfacePlayerPriv->gstPrivateContext->stream[i].sourceLock, NULL); + // start Scheduler Worker for task handling + mScheduler.StartScheduler(); +} + +/* InterfacePlayerRDK destructor*/ +InterfacePlayerRDK::~InterfacePlayerRDK() +{ + DestroyPipeline(); + if (mDrmSystem) + { + delete[] mDrmSystem; + } + mScheduler.StopScheduler(); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + pthread_mutex_destroy(&interfacePlayerPriv->gstPrivateContext->stream[i].sourceLock); + } + pthread_mutex_destroy(&mProtectionLock); + delete interfacePlayerPriv; +} + +InterfacePlayerPriv::InterfacePlayerPriv():mPlayerName() +{ + gstPrivateContext = new GstPlayerPriv(); + socInterface = SocInterface::CreateSocInterface(); +} + +InterfacePlayerPriv::~InterfacePlayerPriv() +{ + delete gstPrivateContext; + gstPrivateContext = nullptr; +} + +/*GstPlayerPriv constructor*/ +GstPlayerPriv::GstPlayerPriv() : monitorAVstate(), pipeline(NULL), bus(NULL), +total_bytes(0), n_audio(0), current_audio(0), +periodicProgressCallbackIdleTaskId(GST_TASK_ID_INVALID), +bufferingTimeoutTimerId(GST_TASK_ID_INVALID), video_dec(NULL), audio_dec(NULL), TaskControlMutex(), firstProgressCallbackIdleTask("FirstProgressCallback"), +video_sink(NULL), audio_sink(NULL), subtitle_sink(NULL), task_pool(NULL), +rate(GST_NORMAL_PLAY_RATE), zoom(GST_VIDEO_ZOOM_NONE), videoMuted(false), audioMuted(false), volumeMuteMutex(), subtitleMuted(false), +audioVolume(1.0), eosCallbackIdleTaskId(GST_TASK_ID_INVALID), eosCallbackIdleTaskPending(false), +firstFrameReceived(false), pendingPlayState(false), decoderHandleNotified(false), +firstFrameCallbackIdleTaskId(GST_TASK_ID_INVALID), firstFrameCallbackIdleTaskPending(false), +using_westerossink(false), usingRialtoSink(false), usingClosedCaptionsControl(false), pauseOnStartPlayback(false), eosSignalled(false), +buffering_enabled(FALSE), buffering_in_progress(FALSE), buffering_timeout_cnt(0), +buffering_target_state(GST_STATE_NULL), +lastKnownPTS(0), ptsUpdatedTimeMS(0), ptsCheckForEosOnUnderflowIdleTaskId(GST_TASK_ID_INVALID), +numberOfVideoBuffersSent(0), segmentStart(0), positionQuery(NULL), durationQuery(NULL), +paused(false), pipelineState(GST_STATE_NULL), +firstVideoFrameDisplayedCallbackTask("FirstVideoFrameDisplayedCallback"), +firstTuneWithWesterosSinkOff(false), +decodeErrorMsgTimeMS(0), decodeErrorCBCount(0), +progressiveBufferingEnabled(false), progressiveBufferingStatus(false), forwardAudioBuffers(false), +enableSEITimeCode(true), firstVideoFrameReceived(false), firstAudioFrameReceived(false), NumberOfTracks(0), playbackQuality{}, +filterAudioDemuxBuffers(false), +aSyncControl(), syncControl(), callbackControl(), seekPosition(0) +{ + memset(videoRectangle, '\0', VIDEO_COORDINATES_SIZE); + /* default video scaling should take into account actual graphics + * resolution instead of assuming 1280x720. + * By default we where setting the resolution has 0,0,1280,720. + * For Full HD this default resolution will not scale to full size. + * So, we no need to set any default rectangle size here, + * since the video will display full screen, if a gstreamer pipeline is started + * using the westerossink connected using westeros compositor. + */ + strcpy(videoRectangle, ""); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + protectionEvent[i] = NULL; + } +} +/*GstPlayerPriv destructor*/ +GstPlayerPriv::~GstPlayerPriv() +{ + g_clear_object(&pipeline); + g_clear_object(&bus); + g_clear_object(&video_dec); + g_clear_object(&audio_dec); + g_clear_object(&video_sink); + g_clear_object(&audio_sink); + g_clear_object(&subtitle_sink); + g_clear_object(&task_pool); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + g_clear_object(&protectionEvent[i]); + } + g_clear_object(&positionQuery); + g_clear_object(&durationQuery); +} + +/** + * @brief Callback for handling video samples in Player's GStreamer player. + * @param[in] object The GStreamer element. + * @param[in] _this The instance of the player. + * @return The flow return status. + */ +static GstFlowReturn InterfacePlayerRDK_OnVideoSample(GstElement *object, void *_this); + +InterfacePlayerPriv* InterfacePlayerRDK::GetPrivatePlayer() +{ + return interfacePlayerPriv; +} + +/** + * @brief Callback for handling video samples in Player's GStreamer player. + * @param[in] object The GStreamer element. + * @param[in] _this The instance of the player. + * @return The flow return status. + */ +bool InterfacePlayerRDK::IsPipelinePaused() +{ + return interfacePlayerPriv->gstPrivateContext->paused; +} + +/** + * @brief Sets a flag indicating that pipeline transition to PLAYING state is pending + */ +void InterfacePlayerRDK::EnablePendingPlayState() +{ + interfacePlayerPriv->gstPrivateContext->pendingPlayState = true; +} + + /** + * @brief Sets a flag indicating that pipeline transition to PLAYING state is pending + */ +const char *gstGetMediaTypeName(GstMediaType mediaType) +{ + static const char *name[] = + { + "video",//eMEDIATYPE_VIDEO + "audio",//eMEDIATYPE_AUDIO + "text",//eMEDIATYPE_SUBTITLE + "aux_audio",//eMEDIATYPE_AUX_AUDIO + "manifest",//eMEDIATYPE_MANIFEST + "licence",//eMEDIATYPE_LICENCE + "iframe",//eMEDIATYPE_IFRAME + "init_video",//eMEDIATYPE_INIT_VIDEO + "init_audio",//eMEDIATYPE_INIT_AUDIO + "init_text",//eMEDIATYPE_INIT_SUBTITLE + "init_aux_audio",//eMEDIATYPE_INIT_AUX_AUDIO + "playlist_video",//eMEDIATYPE_PLAYLIST_VIDEO + "playlist_audio",//eMEDIATYPE_PLAYLIST_AUDIO + "playlist_text",//eMEDIATYPE_PLAYLIST_SUBTITLE + "playlist_aux_audio",//eMEDIATYPE_PLAYLIST_AUX_AUDIO + "playlist_iframe",//eMEDIATYPE_PLAYLIST_IFRAME + "init_iframe",//eMEDIATYPE_INIT_IFRAME + "dsm_cc",//eMEDIATYPE_DSM_CC + "image",//eMEDIATYPE_IMAGE + }; + if( mediaType < eGST_MEDIATYPE_DEFAULT ) + { + return name[mediaType]; + } + else + { + return "UNKNOWN"; + } +} + + +static GstStateChangeReturn SetStateWithWarnings(GstElement *element, GstState targetState); +/** + * @brief Configures the GStreamer pipeline. + * @param format Video format. + * @param audioFormat Audio format. + * @param auxFormat Auxiliary format. + * @param subFormat Whether subtitle format is enabled. + * @param bESChangeStatus Whether ES change status is enabled. + * @param forwardAudioToAux Whether audio should be forwarded to the auxiliary output. + * @param setReadyAfterPipelineCreation Whether to set the player as ready after pipeline creation. + * @param isSubEnable Whether subtitles are enabled. + * @param trackId Track ID. + * @param rate Bitrate. + * @param pipelineName Pipeline name. + * @param PipelinePriority Pipeline priority. + */ +void InterfacePlayerRDK::ConfigurePipeline(int format, int audioFormat, int auxFormat, + int subFormat, bool bESChangeStatus, bool forwardAudioToAux, bool setReadyAfterPipelineCreation, + bool isSubEnable, int32_t trackId, gint rate, const char *pipelineName, int PipelinePriority, bool FirstFrameFlag, std::string manifestUrl) +{ + mFirstFrameRequired = FirstFrameFlag; + GstStreamOutputFormat gstFormat = static_cast(format); + GstStreamOutputFormat gstAudioFormat = static_cast(audioFormat); + GstStreamOutputFormat gstAuxFormat = static_cast(auxFormat); + GstStreamOutputFormat gstSubFormat = static_cast(subFormat); + + GstStreamOutputFormat newFormat[GST_TRACK_COUNT]; + newFormat[eGST_MEDIATYPE_VIDEO] = gstFormat; + newFormat[eGST_MEDIATYPE_AUDIO] = gstAudioFormat; + + bool newClosedCaptionsControl = false; + + if(isSubEnable) + { + MW_LOG_MIL("Gstreamer subs enabled"); + newFormat[eGST_MEDIATYPE_SUBTITLE] = gstSubFormat; + } + else + { + MW_LOG_MIL("Gstreamer subs disabled"); + newFormat[eGST_MEDIATYPE_SUBTITLE]=GST_FORMAT_INVALID; + } + + /*Enable sending of audio data to the auxiliary output*/ + if(forwardAudioToAux) + { + MW_LOG_MIL("InterfacePlayerRDK: Override auxFormat %d -> %d", auxFormat, audioFormat); + interfacePlayerPriv->gstPrivateContext->forwardAudioBuffers = true; + newFormat[eGST_MEDIATYPE_AUX_AUDIO] = gstAudioFormat; + } + else + { + interfacePlayerPriv->gstPrivateContext->forwardAudioBuffers = false; + newFormat[eGST_MEDIATYPE_AUX_AUDIO] = gstAuxFormat; + } + + if(!(m_gstConfigParam->useWesterosSink)) + { + interfacePlayerPriv->gstPrivateContext->using_westerossink = false; + interfacePlayerPriv->gstPrivateContext->firstTuneWithWesterosSinkOff = interfacePlayerPriv->socInterface->IsFirstTuneWithWesteros(); + } + + else + { + interfacePlayerPriv->gstPrivateContext->using_westerossink = true; + interfacePlayerPriv->socInterface->SetWesterosSinkState(true); + } + + if(!(m_gstConfigParam->useRialtoSink)) + { + interfacePlayerPriv->gstPrivateContext->usingRialtoSink = false; + MW_LOG_MIL("Rialto disabled"); + } + else + { + interfacePlayerPriv->gstPrivateContext->usingRialtoSink = true; + + // If no subtitles defined, then create a closed caption control stream + newClosedCaptionsControl = (gstSubFormat == GST_FORMAT_INVALID); + + // To avoid out of band subtitles being removed during trickplay, + // check if they were previously configured, and don't enable Closed Caption Control. + newClosedCaptionsControl &= (interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].format == GST_FORMAT_INVALID); + + if (interfacePlayerPriv->gstPrivateContext->using_westerossink) + { + MW_LOG_WARN("Rialto and Westeros Sink enabled"); + } + else + { + MW_LOG_MIL("Rialto enabled"); + } + if (newClosedCaptionsControl) + { + MW_LOG_MIL("Using CC Control Stream"); + } + } + + if(rate != INVALID_RATE) + { + interfacePlayerPriv->gstPrivateContext->rate = rate; + } + + if (interfacePlayerPriv->gstPrivateContext->pipeline == NULL || interfacePlayerPriv->gstPrivateContext->bus == NULL) + { + MW_LOG_MIL("Create pipeline %s (pipeline %p bus %p)", pipelineName, interfacePlayerPriv->gstPrivateContext->pipeline, interfacePlayerPriv->gstPrivateContext->bus); + CreatePipeline(pipelineName, PipelinePriority); /*Create a new pipeline if pipeline or the message bus does not exist*/ + } + + if(setReadyAfterPipelineCreation) + { + if(SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_ERR("InterfacePlayerRDK_Configure GST_STATE_READY failed on forceful set"); + } + else + { + MW_LOG_INFO("Forcefully set pipeline to ready state due to track_id change"); + PipelineSetToReady = true; + } + } + bool configureStream[GST_TRACK_COUNT] = {}; + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[i]; + if(stream->format != newFormat[i]) + { + if (newFormat[i] != GST_FORMAT_INVALID) + { + MW_LOG_MIL("Closing stream %d old format = %d, new format = %d",i, stream->format, newFormat[i]); + configureStream[i] = true; + interfacePlayerPriv->gstPrivateContext->NumberOfTracks++; + } + } + if(interfacePlayerPriv->socInterface->ShouldTearDownForTrickplay()) + { + if(interfacePlayerPriv->gstPrivateContext->rate > 1 || interfacePlayerPriv->gstPrivateContext->rate < 0) + { + if (eGST_MEDIATYPE_VIDEO == i) + configureStream[i] = true; + else + { + TearDownStream((int)i); + configureStream[i] = false; + } + } + } + /* Force configure the bin for mid stream audio type change */ + if (!configureStream[i] && bESChangeStatus && (eGST_MEDIATYPE_AUDIO == i)) + { + MW_LOG_MIL("AudioType Changed. Force configure pipeline"); + configureStream[i] = true; + } + + stream->resetPosition = true; + stream->eosReached = false; + stream->firstBufferProcessed = false; + } + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[i]; + + if ((configureStream[i] && (newFormat[i] != GST_FORMAT_INVALID)) || + /* Allow to create audio pipeline along with video pipeline if trickplay initiated before the pipeline going to play/paused state to fix unthrottled trickplay */ + (trickTeardown && (eGST_MEDIATYPE_AUDIO == i))) // remove the trickTeardown api not required + { + trickTeardown = false; + TearDownStream((int)i); + stream->format = newFormat[i]; + stream->trackId = trackId; + + /* Sets up the stream for the given MediaType */ + if(0 != InterfacePlayer_SetupStream((GstMediaType)i, manifestUrl)) + { + MW_LOG_ERR("InterfacePlayerRDK: track %d failed", i); + //Don't kill the tune for subtitles + if (eGST_MEDIATYPE_SUBTITLE != (GstMediaType)i) + { + return; + } + } + } + else if ((eGST_MEDIATYPE_SUBTITLE == i) && + newClosedCaptionsControl && + !interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl) + { + TearDownStream(eGST_MEDIATYPE_SUBTITLE); + interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl = true; + SetupClosedCaptionControlStream(); + } + } + if ((interfacePlayerPriv->gstPrivateContext->usingRialtoSink) && (m_gstConfigParam->media != eGST_MEDIAFORMAT_PROGRESSIVE)) + { + /* Reconfigure the Rialto video sink to update the single path stream + * property. This enables rialtomsevideosink to call + * allSourcesAttached() at the right time to enable streaming on the + * server side. + * For progressive media, we don't know what tracks are used. + */ + GstElement* vidsink = NULL; + g_object_get(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO].sinkbin, "video-sink", &vidsink, NULL); + if(vidsink) + { + gboolean videoOnly = (audioFormat == GST_FORMAT_INVALID); + MW_LOG_INFO("Setting single-path-stream to %d", videoOnly); + g_object_set(vidsink, "single-path-stream", videoOnly, NULL); + } + else + { + MW_LOG_WARN("Couldn't get video-sink"); + } + } + if (interfacePlayerPriv->gstPrivateContext->pauseOnStartPlayback && GST_NORMAL_PLAY_RATE == interfacePlayerPriv->gstPrivateContext->rate) + { + MW_LOG_INFO("Setting state to GST_STATE_PAUSED - pause on playback enabled"); + interfacePlayerPriv->gstPrivateContext->paused = true; + interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; + if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PAUSED failed"); + } + } + /* If buffering is enabled, set the pipeline in Paused state, once sufficient content has been buffered the pipeline will be set to GST_STATE_PLAYING */ + else if (interfacePlayerPriv->gstPrivateContext->buffering_enabled && format != GST_FORMAT_INVALID && GST_NORMAL_PLAY_RATE == interfacePlayerPriv->gstPrivateContext->rate) + { + MW_LOG_INFO("Setting state to GST_STATE_PAUSED, target state to GST_STATE_PLAYING"); + interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING; + interfacePlayerPriv->gstPrivateContext->buffering_in_progress = true; + interfacePlayerPriv->gstPrivateContext->buffering_timeout_cnt = DEFAULT_BUFFERING_MAX_CNT; + + if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_ERR("InterfacePlayerRDK_Configure GST_STATE_PAUSED failed"); + } + interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; + interfacePlayerPriv->gstPrivateContext->paused = false; + } + else + { + MW_LOG_INFO("Setting state to GST_STATE_PLAYING"); + if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_ERR("InterfacePlayerRDK: GST_STATE_PLAYING failed"); + } + interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; + interfacePlayerPriv->gstPrivateContext->paused = false; + } + interfacePlayerPriv->gstPrivateContext->eosSignalled = false; + interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent = 0; + interfacePlayerPriv->gstPrivateContext->decodeErrorMsgTimeMS = 0; + interfacePlayerPriv->gstPrivateContext->decodeErrorCBCount = 0; + if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + MW_LOG_INFO("RialtoSink subtitle_sink = %p ",interfacePlayerPriv->gstPrivateContext->subtitle_sink); + GstContext *context = gst_context_new("streams-info", false); + GstStructure *contextStructure = gst_context_writable_structure(context); + if( !interfacePlayerPriv->gstPrivateContext->subtitle_sink ) MW_LOG_WARN( "subtitle_sink==NULL" ); + gst_structure_set( + contextStructure, + "video-streams", G_TYPE_UINT, (interfacePlayerPriv->gstPrivateContext->video_sink)?0x1u:0x0u, + "audio-streams", G_TYPE_UINT, (interfacePlayerPriv->gstPrivateContext->audio_sink)?0x1u:0x0u, + "text-streams", G_TYPE_UINT, (interfacePlayerPriv->gstPrivateContext->subtitle_sink)?0x1u:0x0u, + nullptr ); + gst_element_set_context(GST_ELEMENT(interfacePlayerPriv->gstPrivateContext->pipeline), context); + gst_context_unref(context); + } +} + +/** + * @brief Invoked synchronously when a message is available on the bus + * @param[in] bus the GstBus that sent the message + * @param[in] msg the GstMessage + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval GST_BUS_PASS to pass the message to the async queue + */ +static GstBusSyncReply bus_sync_handler(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * pInterfacePlayerRDK); + +void InterfacePlayerRDK::SetPauseOnStartPlayback(bool enable) +{ + interfacePlayerPriv->gstPrivateContext->pauseOnStartPlayback = enable; +} + +/** + * @brief Idle callback to notify first frame rendered event + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_REMOVE, if the source should be removed + */ +gboolean InterfacePlayerRDK::IdleCallbackOnFirstFrame(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + InterfacePlayerPriv* privatePlayer = nullptr; + + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::firstVideoFrameReceived); + privatePlayer->gstPrivateContext->firstFrameCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + privatePlayer->gstPrivateContext->firstFrameCallbackIdleTaskPending = false; + } + return G_SOURCE_REMOVE; +} + +/** + * @brief Callback invoked after first video frame decoded + * @param[in] object pointer to element raising the callback + * @param[in] arg0 number of arguments + * @param[in] arg1 array of arguments + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + */ +static void GstPlayer_OnFirstVideoFrameCallback(GstElement* object, guint arg0, gpointer arg1, + InterfacePlayerRDK * pInterfacePlayerRDK) + +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + privatePlayer->gstPrivateContext->firstVideoFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_VIDEO); + +} + +/**Add commentMore actions + * @brief Gets the monitor AV state. + * @return A pointer to the MonitorAVState structure containing the AV status or nullptr. + */ +const MonitorAVState& InterfacePlayerRDK::GetMonitorAVState() +{ + return interfacePlayerPriv->gstPrivateContext->monitorAVstate; +} + +/** + * @brief Callback invoked after first audio buffer decoded + * @param[in] object pointer to element raising the callback + * @param[in] arg0 number of arguments + * @param[in] arg1 array of arguments + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + */ +static void GstPlayer_OnAudioFirstFrameAudDecoder(GstElement* object, guint arg0, gpointer arg1, + InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + privatePlayer->gstPrivateContext->firstAudioFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_AUDIO); +} + +/** + * @brief Idle callback to notify end-of-stream event + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_REMOVE, if the source should be removed + */ +gboolean InterfacePlayerRDK::IdleCallbackOnEOS(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + + InterfacePlayerPriv* privatePlayer = nullptr; + + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + MW_LOG_MIL("eosCallbackIdleTaskId %d", privatePlayer->gstPrivateContext->eosCallbackIdleTaskId); + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::notifyEOS); + privatePlayer->gstPrivateContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + privatePlayer->gstPrivateContext->eosCallbackIdleTaskPending = false; + } + return G_SOURCE_REMOVE; +} + +/** + * @brief Updates the monitor AV status. + * + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + */ +void MonitorAV( InterfacePlayerRDK *pInterfacePlayerRDK ) +{ + const int AVSYNC_POSITIVE_THRESHOLD_MS = pInterfacePlayerRDK->m_gstConfigParam->monitorAvsyncThresholdPositiveMs; + const int AVSYNC_NEGATIVE_THRESHOLD_MS = pInterfacePlayerRDK->m_gstConfigParam->monitorAvsyncThresholdNegativeMs; + const int JUMP_THRESHOLD_MS = pInterfacePlayerRDK->m_gstConfigParam->monitorAvJumpThresholdMs; + + GstState state = GST_STATE_VOID_PENDING; + GstState pending = GST_STATE_VOID_PENDING; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + GstClockTime timeout = 0; + gint64 av_position[2] = {0,0}; + gint rc = gst_element_get_state(privatePlayer->gstPrivateContext->pipeline, &state, &pending, timeout ); + if( rc == GST_STATE_CHANGE_SUCCESS ) + { + if( state == GST_STATE_PLAYING ) + { + struct MonitorAVState *monitorAVState = &privatePlayer->gstPrivateContext->monitorAVstate; + const char *description = NULL; + int numTracks = 0; + bool bigJump = false; + long long tNow = GetCurrentTimeMS(); + if( !monitorAVState->tLastReported ) + { + monitorAVState->tLastReported = tNow; + } + // skip reading audio position when trickplay is active + int maxTracks = (privatePlayer->gstPrivateContext->rate == GST_NORMAL_PLAY_RATE) ? 2 : 1; + for( int i=0; igstPrivateContext->stream[i].sinkbin; + if( sinkbin && (privatePlayer->gstPrivateContext->stream[i].format != GST_FORMAT_INVALID)) + { + gint64 position = GST_CLOCK_TIME_NONE; + if( gst_element_query_position(sinkbin, GST_FORMAT_TIME, &position) ) + { + long long ms = GST_TIME_AS_MSECONDS(position); + if( ms == monitorAVState->av_position[i] ) + { + if( description ) + { // both tracks stalled + description = "stall"; + } + else + { + description = (i==eGST_MEDIATYPE_VIDEO)?"video freeze":"audio drop"; + } + } + else if( i == eGST_MEDIATYPE_VIDEO && monitorAVState->happy ) + { + auto actualDelta = ms - monitorAVState->av_position[i]; + auto expectedDelta = tNow - monitorAVState->tLastSampled; + if( actualDelta > expectedDelta+JUMP_THRESHOLD_MS ) + { + bigJump = true; + } + } + av_position[i] = ms; + numTracks++; + } + } + } + monitorAVState->tLastSampled = tNow; + switch( numTracks ) + { + case 0: + description = "eos"; + break; + case 1: + description = "trickplay"; + break; + case 2: + { + int delta = (int)(av_position[eGST_MEDIATYPE_VIDEO] - av_position[eGST_MEDIATYPE_AUDIO]); + if( delta > AVSYNC_POSITIVE_THRESHOLD_MS || delta < AVSYNC_NEGATIVE_THRESHOLD_MS ) + { + if( !description ) + { // both moving, but diverged + description = "avsync"; + } + } + else if( bigJump ) + { // workaround to detect decoders that jump over AV gaps without delay + description = "jump"; + } + } + break; + default: + break; + } + if( !description ) + { // fill in OK if nothing flagged + description = "ok"; + } + if( monitorAVState->description!=description ) + { // log only when interpretation of AV state has changed + if( monitorAVState->description ) + { // avoid logging for initial NULL description + MW_LOG_MIL( "MonitorAV_%s: %" G_GINT64_FORMAT ",%" G_GINT64_FORMAT ",%d, %" G_GINT64_FORMAT "", + monitorAVState->description, + (gint64)monitorAVState->av_position[eGST_MEDIATYPE_VIDEO], + (gint64)monitorAVState->av_position[eGST_MEDIATYPE_AUDIO], + (int)(monitorAVState->av_position[eGST_MEDIATYPE_VIDEO] - monitorAVState->av_position[eGST_MEDIATYPE_AUDIO]), + (gint64)monitorAVState->tLastSampled - monitorAVState->tLastReported ); + } + MW_LOG_MIL( "MonitorAV_%s: %" G_GINT64_FORMAT ",%" G_GINT64_FORMAT ",%d,0", + description, + av_position[eGST_MEDIATYPE_VIDEO], + av_position[eGST_MEDIATYPE_AUDIO], + (int)(av_position[eGST_MEDIATYPE_VIDEO] - av_position[eGST_MEDIATYPE_AUDIO]) ); + monitorAVState->tLastReported = monitorAVState->tLastSampled; + monitorAVState->description = description; + } + // remember most recently sniffed pair of video and audio positions + monitorAVState->av_position[eGST_MEDIATYPE_VIDEO] = av_position[eGST_MEDIATYPE_VIDEO]; + monitorAVState->av_position[eGST_MEDIATYPE_AUDIO] = av_position[eGST_MEDIATYPE_AUDIO]; + } + } + else + { + MW_LOG_WARN( "gst_element_get_state %d", state ); + } +} + +/** + * @brief Timer's callback to notify playback progress event + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_CONTINUE, this function to be called periodically + */ +gboolean InterfacePlayerRDK::ProgressCallbackOnTimeout(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + InterfacePlayerPriv* privatePlayer = nullptr; + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + if (pInterfacePlayerRDK->m_gstConfigParam->monitorAV) + { + MonitorAV(pInterfacePlayerRDK); + } + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::progressCb); + MW_LOG_TRACE("current %d, stored %d ", g_source_get_id(g_main_current_source()), privatePlayer->gstPrivateContext->periodicProgressCallbackIdleTaskId); + } + return G_SOURCE_CONTINUE; +} + +/** + * @brief Idle callback to start progress notifier timer + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_REMOVE, if the source should be removed + */ +gboolean InterfacePlayerRDK::IdleCallback(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + InterfacePlayerPriv* privatePlayer = nullptr; + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::idleCb); + pInterfacePlayerRDK->IdleTaskClearFlags(privatePlayer->gstPrivateContext->firstProgressCallbackIdleTask); + + if ( !(pInterfacePlayerRDK->TimerIsRunning( privatePlayer->gstPrivateContext->periodicProgressCallbackIdleTaskId)) ) + { + double reportProgressInterval = pInterfacePlayerRDK->m_gstConfigParam->progressTimer; + reportProgressInterval *= 1000; //convert s to ms + + GSourceFunc timerFunc = ProgressCallbackOnTimeout; + pInterfacePlayerRDK->TimerAdd(timerFunc, (int)reportProgressInterval, privatePlayer->gstPrivateContext->periodicProgressCallbackIdleTaskId, user_data, "periodicProgressCallbackIdleTask"); + } + else + { + MW_LOG_INFO("Progress callback already available: periodicProgressCallbackIdleTaskId %d", privatePlayer->gstPrivateContext->periodicProgressCallbackIdleTaskId); + } + } + return G_SOURCE_REMOVE; +} + +/** + * @brief Idle callback to notify first video frame was displayed + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_REMOVE, if the source should be removed + */ +gboolean InterfacePlayerRDK::IdleCallbackFirstVideoFrameDisplayed(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + InterfacePlayerPriv* privatePlayer = nullptr; + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::firstVideoFrameDisplayed); + pInterfacePlayerRDK->IdleTaskRemove(privatePlayer->gstPrivateContext->firstVideoFrameDisplayedCallbackTask); + } + return G_SOURCE_REMOVE; +} + +bool gst_StartsWith( const char *inputStr, const char *prefix ); +/** + *@brief set the encrypted content, should be used by playready plugin + */ +void InterfacePlayerRDK::setEncryption(void *Encrypt, void *DRMSessionManager) +{ + mEncrypt = Encrypt; + mDRMSessionManager = DRMSessionManager; +} + +/** + *@brief sets the preferred drm by app + *@param[in] drmID preferred drm + */ +void InterfacePlayerRDK::SetPreferredDRM(const char *drmID) +{ + if (drmID != NULL) + { + if (mDrmSystem != NULL) + { + delete[] mDrmSystem; + } + mDrmSystem = new char[strlen(drmID) + 1]; + if (mDrmSystem != NULL) + { + strcpy(mDrmSystem, drmID); + } + else + { + MW_LOG_ERR("Memory allocation failed for mDrmSystem\n"); + } + } +} + +/** + * @brief Called from the mainloop when a message is available on the bus + * @param[in] bus the GstBus that sent the message + * @param[in] msg the GstMessage + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval FALSE if the event source should be removed. + */ +static gboolean bus_message(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * pInterfacePlayerRDK); + +/** + * @brief check if element is instance + */ +static void type_check_instance( const char * str, GstElement * elem); + +/** + * @fn InterfacePlayerRDK_SignalEOS + * @brief Signal EOS to the appsrc associated with the supplied media stream + * @param[in] media_stream the media stream to inject EOS into + */ +static void GstPlayer_SignalEOS(gst_media_stream& stream) +{ + if (stream.source) + { + auto ret = gst_app_src_end_of_stream(GST_APP_SRC_CAST(stream.source)); + //GST_FLOW_OK is expected in PAUSED or PLAYING states; GST_FLOW_FLUSHING is expected in other states. + if (ret != GST_FLOW_OK) + { + MW_LOG_WARN("gst_app_src_push_buffer error: %d", ret); + } + } +} + +static void GstPlayer_SignalEOS(gst_media_stream* stream) +{ + if(stream) + { + GstPlayer_SignalEOS(*stream); + } +} + +/** + * @brief inject EOS for all media types to ensure the pipeline can be set to NULL quickly*/ +static void GstPlayer_SignalEOS(GstPlayerPriv* gstPrivateContext) +{ + MW_LOG_MIL(" InterfacePlayer: Inject EOS into all streams."); + + if(gstPrivateContext && gstPrivateContext->pipeline) + { + for(int mediaType=eGST_MEDIATYPE_VIDEO; mediaType<=eGST_MEDIATYPE_AUX_AUDIO; mediaType++) + { + GstPlayer_SignalEOS(gstPrivateContext->stream[mediaType]); + } + } + else + { + MW_LOG_WARN(" InterfacePlayer: null pointer check failed"); + } +} + +/** + * @fn SetSeekPosition + * @param[in] positionSecs - the start position to seek the pipeline to in seconds + */ +void InterfacePlayerRDK::SetSeekPosition(double positionSecs) +{ + interfacePlayerPriv->gstPrivateContext->seekPosition = positionSecs; + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + interfacePlayerPriv->gstPrivateContext->stream[i].pendingSeek = true; + } +} + + +static constexpr int RECURSION_LIMIT = 10; + +/** + * @brief GetElementPointers adds the supplied element/bin and any child elements up to RECURSION_LIMIT depth to elements + */ +static void GetElementPointers(gpointer pElementOrBin, std::set& elements, int& recursionCount) +{ + recursionCount++; + if(RECURSION_LIMIT < recursionCount) + { + MW_LOG_ERR(" Interface recursion limit exceeded"); + } + else if(GST_IS_ELEMENT(pElementOrBin)) + { + elements.insert(pElementOrBin); + if(GST_IS_BIN(pElementOrBin)) + { + for (auto currentListItem = GST_BIN_CHILDREN(reinterpret_cast<_GstElement*>(pElementOrBin)); + currentListItem; + currentListItem = currentListItem->next) + { + auto currentChildElement = currentListItem->data; + if (nullptr != currentChildElement) + { + //Recursive function call to support nesting of gst elements up RECURSION_LIMIT + GetElementPointers(currentChildElement, elements, recursionCount); + } + } + } + } + + recursionCount--; +} + +/** + * @brief GetElementPointers returns a set of pointers to the supplied element/bin and any child elements up to RECURSION_LIMIT depth + */ +static std::set GetElementPointers(gpointer pElementOrBin) +{ + int recursionCount = 0; + std::set elements; + GetElementPointers(pElementOrBin, elements, recursionCount); + return elements; +} + +void InterfacePlayerRDK::DisconnectSignals() +{ + const std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->mSignalVectorAccessMutex); + if(m_gstConfigParam->enableDisconnectSignals) + { + std::set elements = GetElementPointers(interfacePlayerPriv->gstPrivateContext->pipeline); + + for(const auto& data: interfacePlayerPriv->gstPrivateContext->mCallBackIdentifiers) + { + if (data.instance == nullptr) + { + MW_LOG_ERR(" InterfacePlayerRDK: %s signal handler, connected instance pointer is null", data.name.c_str()); + } + else if(data.id == 0) + { + MW_LOG_ERR(" InterfacePlayerRDK: %s signal handler id is 0", data.name.c_str()); + } + else if(!elements.count(data.instance)) + { + // This is expected following some tune failures + MW_LOG_WARN(" InterfacePlayerRDK: %s signal handler, connected instance is not in the pipeline", data.name.c_str()); + } + else if(!g_signal_handler_is_connected(data.instance, data.id)) + { + MW_LOG_ERR("InterfacePlayerRDK: %s signal handler not connected", data.name.c_str()); + } + else + { + MW_LOG_WARN("InterfacePlayerRDK: disconnecting %s signal handler", data.name.c_str()); + g_signal_handler_disconnect(data.instance, data.id); + } + } + } + else + { + MW_LOG_WARN("Interface enableDisconnectSignals==false. Signals have not been disconnected."); + } + interfacePlayerPriv->gstPrivateContext->mCallBackIdentifiers.clear(); +} +/** + * @brief TimerRemove - remove a glib timer in thread safe manner, if it exists + */ +void InterfacePlayerRDK::TimerRemove(guint& taskId, const char* timerName) +{ + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + if ( 0 != taskId ) + { + MW_LOG_INFO("InterfacePlayerRDK: Remove timer '%.50s', %d", (nullptr!=timerName) ? timerName : "unknown", taskId); + g_source_remove(taskId); /* Removes the source as per the taskId */ + taskId = 0; + } + else + { + MW_LOG_TRACE("Interface Timer '%.50s' with taskId = %d already removed.", (nullptr!=timerName) ? timerName : "unknown", taskId); + } +} +/** + * @fn RemoveProbes + * @brief Remove probes from the pipeline + */ +void InterfacePlayerRDK::RemoveProbes() +{ + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + RemoveProbe((int)i); + } +} + +/** + * @fn RemoveProbe + * @brief Remove probe for a particular media type + * @param[in] mediaType The media type for which the probe should be removed + */ +void InterfacePlayerRDK::RemoveProbe(int type) +{ + GstMediaType mediaType = static_cast(type); + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[mediaType]; + if (stream->demuxProbeId && stream->demuxPad) + { + MW_LOG_WARN("InterfacePlayerRDK: Removing probe for media type %d, probe id %lu", mediaType, stream->demuxProbeId); + gst_pad_remove_probe(stream->demuxPad, stream->demuxProbeId); + stream->demuxProbeId = 0; + stream->demuxPad = NULL; + } +} + +/** + * @brief Cleanup an existing Gstreamer pipeline and associated resources + */ +void InterfacePlayerRDK::DestroyPipeline() +{ + if (interfacePlayerPriv->gstPrivateContext->pipeline) + { + /*"Destroying gstreamer pipeline" should only be logged when there is a pipeline to destroy + and each "Destroying gstreamer pipeline" log entry should have one, prior "Creating gstreamer pipeline" log entry*/ + MW_LOG_MIL("Interface Destroying gstreamer pipeline"); + gst_object_unref(interfacePlayerPriv->gstPrivateContext->pipeline); /* Decreases the reference count on gstPrivateContext->pipeline, in this case it will become zero, + the reference to gstPrivateContext->pipeline will be freed in gstreamer */ + interfacePlayerPriv->gstPrivateContext->pipeline = NULL; + } + if (interfacePlayerPriv->gstPrivateContext->bus) + { + gst_bus_remove_watch(interfacePlayerPriv->gstPrivateContext->bus); + gst_object_unref(interfacePlayerPriv->gstPrivateContext->bus); /* Decreases the reference count on gstPrivateContext->bus, in this case it will become zero, + the reference to gstPrivateContext->bus will be freed in gstreamer */ + interfacePlayerPriv->gstPrivateContext->bus = NULL; + } + if(interfacePlayerPriv->gstPrivateContext->task_pool) + { + gst_object_unref(interfacePlayerPriv->gstPrivateContext->task_pool); + interfacePlayerPriv->gstPrivateContext->task_pool = NULL; + } + + if (interfacePlayerPriv->gstPrivateContext->positionQuery) + { + /* Decrease the refcount of the query. If the refcount reaches 0, the query will be freed */ + gst_query_unref(interfacePlayerPriv->gstPrivateContext->positionQuery); + interfacePlayerPriv->gstPrivateContext->positionQuery = NULL; + } + + //video decoder handle will change with new pipeline + interfacePlayerPriv->gstPrivateContext->decoderHandleNotified = false; + interfacePlayerPriv->gstPrivateContext->NumberOfTracks = 0; +} + +/** + * @brief wraps gst_element_get_name handling unnamed elements and resource freeing + * @param[in] element a GstElement + * @retval The name of element or "unnamed element" as a std::string + */ +static std::string SafeName(GstElement *element) +{ + std::string name; + auto elementName = gst_element_get_name(element); + if(elementName) + { + name = elementName; + g_free((void *)elementName); + } + else + { + name = "unnamed element"; + } + return name; +} + +/** + * @brief Generates a state description for gst target, next and pending state i.e. **not current state**. + * @param[in] state - the state of the current element + * @param[in] start - a char to place before the state text e.g. on open bracket + * @param[in] end - a char to place after the state text e.g. a close bracket + * @param[in] currentState - the current state from the same element as 'state' + * @param[in] parentState - the state of the parent, if there is one + * @return - "" unless state is 'interesting' otherwise *start* *state description* *end* e.g. {GST_STATE_READY} + */ +static std::string StateText(GstState state, char start, char end, GstState currentState, GstState parentState = GST_STATE_VOID_PENDING) +{ + if((state == GST_STATE_VOID_PENDING) || ((state == currentState) && ((state == parentState) || (parentState == GST_STATE_VOID_PENDING)))) + { + return ""; + } + else + { + std::string returnStringBuilder(1, start); + returnStringBuilder += gst_element_state_get_name(state); + returnStringBuilder += end; + return returnStringBuilder; + } +} + +/** + * @brief - returns a string describing pElementOrBin and its children (if any). + * The top level elements name:state are shown along with any child elements in () separated by , + * State information is displayed as GST_STATE[GST_STATE_TARGET]{GST_STATE_NEXT} + * Target state, next state and pending state are not always shown. + * Where GST_STATE_CHANGE for the element is not GST_STATE_CHANGE_SUCCESS an additional character is appended to the element name: + GST_STATE_CHANGE_FAILURE: "!", GST_STATE_CHANGE_ASYNC:"~", GST_STATE_CHANGE_NO_PREROLL:"*" + * @param[in] pElementOrBin - pointer to a gst element or bin + * @param[in] pParent - parent (optional) + * @param recursionCount - variable shared with self calls to limit recursion depth + * @return - description string + */ +static std::string GetStatus(gpointer pElementOrBin, int& recursionCount, gpointer pParent = nullptr) +{ + recursionCount++; + constexpr int RECURSION_LIMIT = 10; + if(RECURSION_LIMIT < recursionCount) + { + return "recursion limit exceeded"; + } + + std::string returnStringBuilder(""); + if(nullptr !=pElementOrBin) + { + if(GST_IS_ELEMENT(pElementOrBin)) + { + auto pElement = reinterpret_cast<_GstElement*>(pElementOrBin); + + bool validParent = (pParent != nullptr) && GST_IS_ELEMENT(pParent); + + returnStringBuilder += SafeName(pElement); + GstState state; + GstState statePending; + auto changeStatus = gst_element_get_state(pElement, &state, &statePending, 0); + switch(changeStatus) + { + case GST_STATE_CHANGE_FAILURE: + returnStringBuilder +="!"; + break; + + case GST_STATE_CHANGE_SUCCESS: + //no annotation + break; + + case GST_STATE_CHANGE_ASYNC: + returnStringBuilder +="~"; + break; + + case GST_STATE_CHANGE_NO_PREROLL: + returnStringBuilder +="*"; + break; + + default: + returnStringBuilder +="?"; + break; + } + + returnStringBuilder += ":"; + + returnStringBuilder += gst_element_state_get_name(state); + + returnStringBuilder += StateText(statePending, '<', '>', state, + validParent?GST_STATE_PENDING(pParent):GST_STATE_VOID_PENDING); + returnStringBuilder += StateText(GST_STATE_TARGET(pElement), '[', ']', state, + validParent?GST_STATE_TARGET(pParent):GST_STATE_VOID_PENDING); + returnStringBuilder += StateText(GST_STATE_NEXT(pElement), '{', '}', state, + validParent?GST_STATE_NEXT(pParent):GST_STATE_VOID_PENDING); + } + + //note bin inherits from element so name bin name is also printed above, with state info where applicable + if(GST_IS_BIN(pElementOrBin)) + { + returnStringBuilder += " ("; + + auto pBin = reinterpret_cast<_GstElement*>(pElementOrBin); + bool first = true; + for (auto currentListItem = GST_BIN_CHILDREN(pBin); + currentListItem; + currentListItem = currentListItem->next) + { + if(first) + { + first = false; + } + else + { + returnStringBuilder += ", "; + } + + auto currentChildElement = currentListItem->data; + if (nullptr != currentChildElement) + { + //Recursive function call to support nesting of gst elements up RECURSION_LIMIT + returnStringBuilder += GetStatus(currentChildElement, recursionCount, pBin); + } + } + returnStringBuilder += ")"; + } + } + recursionCount--; + return returnStringBuilder; +} + +static void LogStatus(GstElement* pElementOrBin) +{ + int recursionCount = 0; + MW_LOG_MIL("InterfacePlayerRDK: %s Status: %s",SafeName(pElementOrBin).c_str(), GetStatus(pElementOrBin, recursionCount).c_str()); +} + + +/** + * @brief wraps gst_element_set_state and adds log messages where applicable + * @param[in] element the GstElement whose state is to be changed + * @param[in] targetState the GstState to apply to element + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval Result of the state change (from inner gst_element_set_state()) + */ +static GstStateChangeReturn SetStateWithWarnings(GstElement *element, GstState targetState) +{ + GstStateChangeReturn rc = GST_STATE_CHANGE_FAILURE; + if(element) + { + //in a synchronous only transition gst_element_set_state can lockup if there are pipeline errors + bool syncOnlyTransition = (targetState==GST_STATE_NULL)||(targetState==GST_STATE_READY); + + GstState current; /* To hold the current state of the element */ + GstState pending; /* Pending state, used in printing the pending state of the element */ + + auto stateChangeReturn = gst_element_get_state(element, ¤t, &pending, 0); /* Get the current playing state of the element with no blocking timeout, this function is MT-safe */ + switch(stateChangeReturn) + { + case GST_STATE_CHANGE_FAILURE: + MW_LOG_ERR("InterfacePlayerRDK: %s is in FAILURE state : current %s pending %s", SafeName(element).c_str(),gst_element_state_get_name(current), gst_element_state_get_name(pending)); + LogStatus(element); + break; + case GST_STATE_CHANGE_SUCCESS: + MW_LOG_DEBUG("InterfacePlayerRDK: %s is in success state : current %s pending %s", SafeName(element).c_str(),gst_element_state_get_name(current), gst_element_state_get_name(pending)); + break; + case GST_STATE_CHANGE_ASYNC: + if(syncOnlyTransition) + { + MW_LOG_MIL("InterfacePlayerRDK: %s state is changing asynchronously : current %s pending %s", SafeName(element).c_str(),gst_element_state_get_name(current), gst_element_state_get_name(pending)); + LogStatus(element); + } + break; + default: + MW_LOG_ERR("InterfacePlayerRDK: %s is in an unknown state", SafeName(element).c_str()); + break; + } + + if(syncOnlyTransition) + { + MW_LOG_MIL("InterfacePlayerRDK: Attempting to set %s state to %s", SafeName(element).c_str(), gst_element_state_get_name(targetState)); + } + else + { + MW_LOG_DEBUG("InterfacePlayerRDK: Attempting to set %s state to %s", SafeName(element).c_str(), gst_element_state_get_name(targetState)); + } + rc = gst_element_set_state(element, targetState); /* Set the state of the element to the targetState, this function is MT-safe*/ + if(syncOnlyTransition) + { + MW_LOG_MIL("InterfacePlayerRDK: %s state set to %s", SafeName(element).c_str(), gst_element_state_get_name(targetState)); + } + else + { + MW_LOG_DEBUG(" InterfacePlayerRDK: %s state set to %s, rc:%d", SafeName(element).c_str(), gst_element_state_get_name(targetState), rc); + } + } + else + { + MW_LOG_ERR("InterfacePlayerRDK: Attempted to set the state of a null pointer"); + } + return rc; +} + + +void InterfacePlayerRDK::TearDownStream(int type) +{ + tearDownCb(true, type); + gst_media_stream* stream = &interfacePlayerPriv->gstPrivateContext->stream[type]; + RemoveProbe(type); + stream->bufferUnderrun = false; + stream->eosReached = false; + + GstMediaType mediaType = static_cast(type); + + pthread_mutex_lock(&stream->sourceLock); + if ((stream->format != GST_FORMAT_INVALID) || + (mediaType == eGST_MEDIATYPE_SUBTITLE && interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl)) + { + if (interfacePlayerPriv->gstPrivateContext->pipeline) + { + interfacePlayerPriv->gstPrivateContext->buffering_in_progress = false; /* stopping pipeline, don't want to change state if GST_MESSAGE_ASYNC_DONE message comes in */ + /* set the playbin state to NULL before detach it */ + if (stream->sinkbin) + { + if (GST_STATE_CHANGE_FAILURE == SetStateWithWarnings(GST_ELEMENT(stream->sinkbin), GST_STATE_NULL)) + { + MW_LOG_ERR("InterfacePlayerRDK::TearDownStream: Failed to set NULL state for sinkbin"); + } + if (!gst_bin_remove(GST_BIN(interfacePlayerPriv->gstPrivateContext->pipeline), GST_ELEMENT(stream->sinkbin))) /* Removes the sinkbin element from the pipeline */ + { + MW_LOG_ERR("InterfacePlayerRDK::TearDownStream: Unable to remove sinkbin from pipeline"); + } + } + else + { + MW_LOG_WARN("InterfacePlayerRDK::TearDownStream: sinkbin = NULL, skip remove sinkbin from pipeline"); + } + } + //After sinkbin is removed from pipeline, a new decoder handle may be generated + if (((mediaType == eGST_MEDIATYPE_VIDEO) && !interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl) || + ((mediaType == eGST_MEDIATYPE_SUBTITLE) && interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl)) + { + interfacePlayerPriv->gstPrivateContext->decoderHandleNotified = false; + } + stream->format = GST_FORMAT_INVALID; + interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl = false; + g_clear_object(&stream->sinkbin); + g_clear_object(&stream->source); + stream->sourceConfigured = false; + } + pthread_mutex_unlock(&stream->sourceLock); + if (mediaType == eGST_MEDIATYPE_VIDEO) + { + g_clear_object(&interfacePlayerPriv->gstPrivateContext->video_dec); + g_clear_object(&interfacePlayerPriv->gstPrivateContext->video_sink); + } + else if (mediaType == eGST_MEDIATYPE_AUDIO) + { + g_clear_object(&interfacePlayerPriv->gstPrivateContext->audio_dec); + g_clear_object(&interfacePlayerPriv->gstPrivateContext->audio_sink); + } + else if (mediaType == eGST_MEDIATYPE_SUBTITLE) + { + g_clear_object(&interfacePlayerPriv->gstPrivateContext->subtitle_sink); + } + tearDownCb(false, mediaType); + MW_LOG_MIL("InterfacePlayerRDK::TearDownStream: exit mediaType = %d", mediaType); +} + +void InterfacePlayerRDK::Stop(bool keepLastFrame) +{ + std::lock_guard lock(mMutex); + /* make the execution of this function more deterministic and + * reduce scope for potential pipeline lockups*/ + + interfacePlayerPriv->gstPrivateContext->syncControl.disable(); + interfacePlayerPriv->gstPrivateContext->aSyncControl.disable(); + std::unique_lock sourceSetupLock(mSourceSetupMutex); + mSourceSetupCV.notify_all(); + if(interfacePlayerPriv->gstPrivateContext->bus) + { + gst_bus_remove_watch(interfacePlayerPriv->gstPrivateContext->bus); /* Remove the watch from bus so that gstreamer no longer sends messages to it */ + } + + if(!keepLastFrame) + { + interfacePlayerPriv->gstPrivateContext->firstFrameReceived = false; + interfacePlayerPriv->gstPrivateContext->firstVideoFrameReceived = false; + interfacePlayerPriv->gstPrivateContext->firstAudioFrameReceived = false ; + } + IdleTaskRemove(interfacePlayerPriv->gstPrivateContext->firstProgressCallbackIdleTask); + + this->TimerRemove(interfacePlayerPriv->gstPrivateContext->periodicProgressCallbackIdleTaskId, "periodicProgressCallbackIdleTaskId"); + if (interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove bufferingTimeoutTimerId %d", interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId); + g_source_remove(interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId); + interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId = PLAYER_TASK_ID_INVALID; + } + if (interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove ptsCheckForEosCallbackIdleTaskId %d",interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId); + g_source_remove(interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId); + interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId = PLAYER_TASK_ID_INVALID; + } + if (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove eosCallbackIdleTaskId %d",interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + mScheduler.RemoveTask(interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending = false; + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + } + if (interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove firstFrameCallbackIdleTaskId %d",interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId); + mScheduler.RemoveTask(interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId); + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending = false; + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + } + IdleTaskRemove(interfacePlayerPriv->gstPrivateContext->firstVideoFrameDisplayedCallbackTask); + /* Prevent potential side effects of injecting EOS and + * make the stop process more deterministic by: + 1) Confirming that bus handlers (disabled above) have completed + 2) disabling and disconnecting signals + 3) confirming that all signal handlers have completed. + * This should complete very quickly and + * should not have a significant performance impact.*/ + interfacePlayerPriv->gstPrivateContext->syncControl.waitForDone(50, "bus_sync_handler"); + interfacePlayerPriv->gstPrivateContext->aSyncControl.waitForDone(50, "bus_message"); + interfacePlayerPriv->gstPrivateContext->callbackControl.disable(); + DisconnectSignals(); + interfacePlayerPriv->gstPrivateContext->aSyncControl.waitForDone(100, "callback handler"); + + // Remove probes before setting the pipeline to NULL + RemoveProbes(); + + if (interfacePlayerPriv->gstPrivateContext->pipeline) + { + const auto EOSMode = m_gstConfigParam->eosInjectionMode; + if(GstEOS_INJECTION_MODE_STOP_ONLY == EOSMode) + { + //Ensure prompt transition to GST_STATE_NULL + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext); + } + + interfacePlayerPriv->gstPrivateContext->buffering_in_progress = false; /* stopping pipeline, don't want to change state if GST_MESSAGE_ASYNC_DONE message comes in */ + SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_NULL); + MW_LOG_MIL(" InterfacePlayerRDK: Pipeline state set to null"); + } + if(PlayerExternalsInterface::IsPlayerExternalsInterfaceInstanceActive()) + { + std::shared_ptr pInstance = PlayerExternalsInterface::GetPlayerExternalsInterfaceInstance(); + pInstance->setGstElement((GstElement *)(NULL)); + } + for(int i = 0; igstPrivateContext->rate = GST_NORMAL_PLAY_RATE; + interfacePlayerPriv->gstPrivateContext->lastKnownPTS = 0; + interfacePlayerPriv->gstPrivateContext->segmentStart = 0; + interfacePlayerPriv->gstPrivateContext->paused = false; + interfacePlayerPriv->gstPrivateContext->pipelineState = GST_STATE_NULL; + // Reset mute and volume params + interfacePlayerPriv->gstPrivateContext->audioMuted = false; + interfacePlayerPriv->gstPrivateContext->videoMuted = false; + interfacePlayerPriv->gstPrivateContext->subtitleMuted = false; + interfacePlayerPriv->gstPrivateContext->audioVolume = 1.0; +} + +void InterfacePlayerRDK::ResetGstEvents() +{ + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + interfacePlayerPriv->gstPrivateContext->stream[i].resetPosition = true; + // Pipeline is already flushed, no need to send seek event again + interfacePlayerPriv->gstPrivateContext->stream[i].pendingSeek = false; + interfacePlayerPriv->gstPrivateContext->stream[i].eosReached = false; + interfacePlayerPriv->gstPrivateContext->stream[i].firstBufferProcessed = false; + } + +} + +void InterfacePlayerRDK::SetPendingSeek(bool state) +{ + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + interfacePlayerPriv->gstPrivateContext->stream[i].pendingSeek = state; + } +} + +bool InterfacePlayerRDK::GetTrickTeardown() +{ + return trickTeardown; +} + +void InterfacePlayerRDK::SetTrickTearDown(bool state) +{ + trickTeardown = state; +} + +/** + * @brief IdleTaskRemove - remove an async task in a thread safe manner, if it is queued + */ +bool InterfacePlayerRDK::IdleTaskRemove(GstTaskControlData& taskDetails) +{ + bool ret = false; + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + + if (0 != taskDetails.taskID) + { + MW_LOG_INFO("InterfacePlayerRDK: Remove task <%.50s> with ID %d", taskDetails.taskName.c_str(), taskDetails.taskID); + mScheduler.RemoveTask(taskDetails.taskID); + taskDetails.taskID = 0; + ret = true; + } + else + { + MW_LOG_TRACE("InterfacePlayerRDK: Task already removed <%.50s>, with ID %d", taskDetails.taskName.c_str(), taskDetails.taskID); + } + taskDetails.taskIsPending = false; + return ret; +} + +bool InterfacePlayerRDK::IsUsingRialtoSink() +{ + if (interfacePlayerPriv->gstPrivateContext) + { + return interfacePlayerPriv->gstPrivateContext->usingRialtoSink; + } + return false; +} + +/** + * @brief Flush cached GstBuffers and set seek position & rate + */ +bool InterfacePlayerRDK::Flush(double position, int rate, bool shouldTearDown, bool isAppSeek) +{ + GstState aud_current; + GstState aud_pending; + GstState current; + GstState pending; + + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO]; + interfacePlayerPriv->gstPrivateContext->rate = rate; + interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun = false; + interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun = false; + if (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove eosCallbackIdleTaskId %d", interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + mScheduler.RemoveTask(interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending = false; + + } + if (interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove ptsCheckForEosCallbackIdleTaskId %d", interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId); + g_source_remove(interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId); + interfacePlayerPriv->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId = PLAYER_TASK_ID_INVALID; + + } + if (interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId) + { + MW_LOG_MIL("InterfacePlayerRDK: Remove bufferingTimeoutTimerId %d", interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId); + g_source_remove(interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId); + interfacePlayerPriv->gstPrivateContext->bufferingTimeoutTimerId = PLAYER_TASK_ID_INVALID; + + } + + // If the pipeline is not setup, we will cache the value for later + SetSeekPosition(position); + + if (interfacePlayerPriv->gstPrivateContext->pipeline == NULL) + { + MW_LOG_WARN("InterfacePlayerRDK: Pipeline is NULL"); + return false; + } + bool bAsyncModify = false; + if (!interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + bAsyncModify = interfacePlayerPriv->socInterface->DisableAsyncAudio(interfacePlayerPriv->gstPrivateContext->audio_sink, rate, isAppSeek); + // Send EOS to audio sink to prevent flush getting blocked waiting for preroll + if (interfacePlayerPriv->gstPrivateContext->audio_sink) + { + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO]); + } + } + GstStateChangeReturn ret; + ret = gst_element_get_state(interfacePlayerPriv->gstPrivateContext->pipeline, ¤t, &pending, 100 * GST_MSECOND); + if ((current != GST_STATE_PLAYING && current != GST_STATE_PAUSED) || ret == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_WARN("InterfacePlayerRDK: Pipeline state %s, ret %u", gst_element_state_get_name(current), ret); + if (shouldTearDown) + { + MW_LOG_WARN("InterfacePlayerRDK: Pipeline is not in playing/paused state, hence resetting it"); + if (rate > GST_NORMAL_PLAY_RATE) + { + SetTrickTearDown(true); + } + + stopCallback(true); + // Set the rate back to the original value if it was an recovery Stop() call + interfacePlayerPriv->gstPrivateContext->rate = rate; + } + return false; + } + + else + { + /* pipeline may enter paused state even when audio decoder is not ready, check again */ + if (interfacePlayerPriv->gstPrivateContext->audio_dec) + { + ret = gst_element_get_state(interfacePlayerPriv->gstPrivateContext->audio_dec, &aud_current, &aud_pending, 0); + if ((aud_current != GST_STATE_PLAYING && aud_current != GST_STATE_PAUSED) || ret == GST_STATE_CHANGE_FAILURE) + { + if (shouldTearDown) + { + MW_LOG_WARN("InterfacePlayerRDK: Pipeline is in playing/paused state, but audio_dec is in %s state, resetting it ret %u", + gst_element_state_get_name(aud_current), ret); + stopCallback(true); + // Set the rate back to the original value if it was an recovery Stop() call + interfacePlayerPriv->gstPrivateContext->rate = rate; + return false; + } + } + } + MW_LOG_MIL("InterfacePlayerRDK: Pipeline is in %s state position %f ret %d", gst_element_state_get_name(current), position, ret); + } + /* Disabling the flush flag to avoid */ + /* flush call again (which may cause freeze sometimes) */ + /* from SendGstEvents() API. + */ + ResetGstEvents(); + MW_LOG_INFO("InterfacePlayerRDK: Pipeline flush seek - start = %f rate = %d", position, rate); + double playRate = 1.0; + if (eGST_MEDIAFORMAT_PROGRESSIVE == static_cast(m_gstConfigParam->media)) + { + playRate = rate; + } + + if ((stream->format == GST_FORMAT_ISO_BMFF) && (eGST_MEDIAFORMAT_PROGRESSIVE != static_cast(m_gstConfigParam->media))) + { + if ((interfacePlayerPriv->socInterface->IsSimulatorSink() || interfacePlayerPriv->gstPrivateContext->usingRialtoSink) && rate != GST_NORMAL_PLAY_RATE) + { + MW_LOG_INFO("Resetting seek position to zero"); + position = 0; + } + } + if (!gst_element_seek(interfacePlayerPriv->gstPrivateContext->pipeline, playRate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, + position * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + { + MW_LOG_ERR("Seek failed"); + SetPendingSeek(true); + //Save the updated seek position + SetSeekPosition(position); + } + + if ((interfacePlayerPriv->gstPrivateContext->usingRialtoSink) && + (interfacePlayerPriv->gstPrivateContext->audio_sink) && + (rate != GST_NORMAL_PLAY_RATE)) + { + /* + * If trickplay, avoid tearing down the pipeline in ConfigurePipeline(), + * by bringing the audio pipeline out of pre-roll which would block streaming. + */ + MW_LOG_INFO("Trickplay rate %d - send eos to audio sink", rate); + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO]); + } + + if(bAsyncModify) + { + interfacePlayerPriv->socInterface->SetSinkAsync(interfacePlayerPriv->gstPrivateContext->audio_sink, (gboolean)TRUE); + } + interfacePlayerPriv->gstPrivateContext->eosSignalled = false; + interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent = 0; + return true; +} +void InterfacePlayerPriv::SignalConnect(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) +{ + { + const std::lock_guard lock(gstPrivateContext->mSignalVectorAccessMutex); + auto id = g_signal_connect(instance, detailed_signal, c_handler, data); + if(0mCallBackIdentifiers.push_back(std::move(Identifier)); + } + else + { + MW_LOG_WARN("InterfacePlayerRDK: Could not connect %s", detailed_signal); + } + } + gstPrivateContext->callbackControl.enable(); +} + +static gboolean gstappsrc_seek(void *src, guint64 offset, void* _this) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK*)_this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER(privatePlayer->gstPrivateContext->callbackControl, TRUE); + MW_LOG_TRACE("appsrc %p seek-signal - offset %" G_GUINT64_FORMAT, src, offset); + return TRUE; +} +static GstMediaType gstGetMediaTypeForSource(const void *source, const void *_this) +{ + if (source && _this) + { + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK*)_this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + /* eMEDIATYPE_VIDEO, eMEDIATYPE_AUDIO, eMEDIATYPE_SUBTITLE, eMEDIATYPE_AUX_AUDIO */ + if (source == privatePlayer->gstPrivateContext->stream[i].source) + { + return static_cast(i); + } + } + MW_LOG_WARN("unmapped source!"); + } + else + { + MW_LOG_ERR("Null check failed."); + } + + return eGST_MEDIATYPE_DEFAULT; +} +/** + * @brief Callback for appsrc "need-data" signal + * @param[in] source pointer to appsrc instance triggering "need-data" signal + * @param[in] size size of data required + * @param[in] _this pointer to InterfacePlayerRDK instance associated with the playback + */ +static void gst_need_data(void *source, guint size, void *_this) +{ + InterfacePlayerRDK* pInterfacePlayerRDK = static_cast(_this); + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + GstMediaType mediaType = gstGetMediaTypeForSource(source, pInterfacePlayerRDK); + if (mediaType != eGST_MEDIATYPE_DEFAULT) + { + struct gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[mediaType]; + if(stream) + { + int media = static_cast(mediaType); + pInterfacePlayerRDK->NeedDataCb(media); + } + else + { + MW_LOG_ERR( "Null check failed." ); + } + } +} +static void gst_enough_data(GstElement *source, void *_this) +{ + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK*)_this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + if(pInterfacePlayerRDK) + { + if (!pInterfacePlayerRDK->mPauseInjector) // avoid processing enough data if the downloads are already disabled. + { + GstMediaType mediaType = gstGetMediaTypeForSource(source, pInterfacePlayerRDK); + if (mediaType != eGST_MEDIATYPE_DEFAULT) + { + struct gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[mediaType]; + if(stream) + { + int media = static_cast(mediaType); + pInterfacePlayerRDK->EnoughDataCb(media); + } + else + { + MW_LOG_ERR( "%s Null check failed.", gstGetMediaTypeName(mediaType)); + } + } + } + } + else + { + MW_LOG_ERR( "Null check failed." ); + } +} +void InterfacePlayerRDK::InitializeSourceForPlayer(void *PlayerInstance, void * source, int type) +{ + InterfacePlayerRDK* _this = (InterfacePlayerRDK*)PlayerInstance; + InterfacePlayerPriv* privatePlayer = _this->GetPrivatePlayer(); + GstCaps * caps = NULL; + GstMediaType mediaType = static_cast(type); + gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[mediaType]; + privatePlayer->SignalConnect(source, "need-data", G_CALLBACK(gst_need_data), _this); + privatePlayer->SignalConnect(source, "enough-data", G_CALLBACK(gst_enough_data), _this); /* Sets up the call back function for enough data event */ + privatePlayer->SignalConnect(source, "seek-data", G_CALLBACK(gstappsrc_seek), _this); /* Sets up the call back function for seek data event */ + gst_app_src_set_stream_type(GST_APP_SRC(source), GST_APP_STREAM_TYPE_SEEKABLE); + if (eGST_MEDIATYPE_VIDEO == mediaType ) + { + int MaxGstVideoBufBytes = m_gstConfigParam->videoBufBytes; + MW_LOG_INFO("Setting gst Video buffer max bytes to %d", MaxGstVideoBufBytes); + g_object_set(source, "max-bytes", (guint64)MaxGstVideoBufBytes, NULL); /* Sets the maximum video buffer bytes as per configuration*/ + + if( privatePlayer->gstPrivateContext->usingRialtoSink && + !privatePlayer->socInterface->IsVideoMaster(privatePlayer->gstPrivateContext->video_sink) ) + { + // This property is required so that the segment event sent via gst_app_src_push_sample + MW_LOG_INFO("Setting handle-segment-change to 1"); + g_object_set(source, "handle-segment-change", TRUE, NULL); + } + } + else if (eGST_MEDIATYPE_AUDIO == mediaType || eGST_MEDIATYPE_AUX_AUDIO == mediaType) + { + + int MaxGstAudioBufBytes = m_gstConfigParam->audioBufBytes; + MW_LOG_INFO("Setting gst Audio buffer max bytes to %d", MaxGstAudioBufBytes); + g_object_set(source, "max-bytes", (guint64)MaxGstAudioBufBytes, NULL); /* Sets the maximum audio buffer bytes as per configuration*/ + } + g_object_set(source, "min-percent", 50, NULL); /* Trigger the need data event when the queued bytes fall below 50% */ + /* "format" can be used to perform seek or query/conversion operation*/ + /* gstreamer.freedesktop.org recommends to use GST_FORMAT_TIME 'if you don't have a good reason to query for samples/frames' */ + g_object_set(source, "format", GST_FORMAT_TIME, NULL); + + // If using Closed Caption Control subtitle stream, set the caps to application/x-subtitle-cc + if ((eGST_MEDIATYPE_SUBTITLE == mediaType) && (privatePlayer->gstPrivateContext->usingClosedCaptionsControl)) + { + caps = gst_caps_new_simple("application/x-subtitle-cc", NULL, NULL); + } + else if( stream->format!=GST_FORMAT_ISO_BMFF || !m_gstConfigParam->useMp4Demux ) + { + caps = GetCaps(static_cast(stream->format)); + } + else + { + MW_LOG_MIL("Skipping caps for now, will be set from mp4Demux later"); + } + + if (caps != NULL) + { + gst_app_src_set_caps(GST_APP_SRC(source), caps); + gst_caps_unref(caps); + } + else + { + /* If capabilities can not be established, set typefind TRUE. typefind determines the media-type of a stream and once type has been + * detected it sets its src pad caps to the found media type + */ + g_object_set(source, "typefind", TRUE, NULL); + } + stream->sourceConfigured = true; +} + +static GstPadProbeReturn InterfacePlayerRDK_DemuxPadProbeCallback(GstPad * pad, GstPadProbeInfo * info, void* _this) +{ + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER(info); + if (_this) + { + auto* player = static_cast(_this); + InterfacePlayerPriv* privatePlayer = player->GetPrivatePlayer(); + // Filter audio buffers until video PTS is reached + if (privatePlayer->gstPrivateContext->filterAudioDemuxBuffers && + pad == privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].demuxPad) + { + // PTS in nanoseconds + gint64 currentPTS = (((double)((InterfacePlayerRDK*)_this)->GetVideoPTS() / (double)90000) * GST_SECOND); + if (GST_BUFFER_PTS(buffer) < currentPTS) + { + MW_LOG_INFO("Dropping buffer: currentPTS=%" G_GINT64_FORMAT " buffer pts=%" G_GINT64_FORMAT, currentPTS, GST_BUFFER_PTS(buffer)); + return GST_PAD_PROBE_DROP; + } + else + { + MW_LOG_WARN("Resetting filterAudioDemuxBuffers buffer pts=%" G_GINT64_FORMAT, GST_BUFFER_PTS(buffer)); + privatePlayer->gstPrivateContext->filterAudioDemuxBuffers = false; + } + } + } + return GST_PAD_PROBE_OK; +} +static GstPadProbeReturn InterfacePlayerRDK_DemuxPadProbeCallbackAny(GstPad *pad, GstPadProbeInfo *info, void *_this) +{ + GstPadProbeReturn rtn = GST_PAD_PROBE_OK; + MW_LOG_TRACE("type %u",info->type); + if (info->type & GST_PAD_PROBE_TYPE_BUFFER) + { + rtn = InterfacePlayerRDK_DemuxPadProbeCallback(pad, info, _this); + } + return rtn; +} +static void GstPlayer_OnDemuxPadAddedCb(GstElement* demux, GstPad* newPad, void* _this) +{ + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK *)_this; + InterfacePlayerPriv* privatePlayer = nullptr; + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + GstPadProbeType mask = GST_PAD_PROBE_TYPE_INVALID; + + if (pInterfacePlayerRDK->m_gstConfigParam->seamlessAudioSwitch) + { + mask = GST_PAD_PROBE_TYPE_BUFFER; + } + if (pInterfacePlayerRDK->m_gstConfigParam->enablePTSReStamp) + { + //cast to Keep compiler happy + mask = static_cast(static_cast(mask) | static_cast(GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM)); + } + MW_LOG_TRACE("mask %u",mask); + // We need to identify which stream the demux belongs to. + // We can't use a CAPS based check, for use-cases such as aux-audio + GstElement *parent = GST_ELEMENT_PARENT(demux); + bool found = false; + while (parent) + { + if (gst_StartsWith(GST_ELEMENT_NAME(parent), "playbin")) + { + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[i]; + if (parent == stream->sinkbin) + { + if (stream->demuxPad == NULL) + { + stream->demuxPad = newPad; + stream->demuxProbeId = gst_pad_add_probe(newPad, + mask, + (GstPadProbeCallback)InterfacePlayerRDK_DemuxPadProbeCallbackAny, + pInterfacePlayerRDK, + NULL); + MW_LOG_WARN("Added probe to qtdemux type[%d] src pad: %s", i, GST_PAD_NAME(newPad)); + } + else + { + MW_LOG_WARN("Ignoring additional pad"); + } + found = true; + } + } + break; + } + MW_LOG_TRACE("Got Parent: %s", GST_ELEMENT_NAME(parent)); + parent = GST_ELEMENT_PARENT(parent); + } + if (!found) + { + GstCaps* caps = gst_pad_get_current_caps(newPad); + gchar *capsStr = gst_caps_to_string(caps); + MW_LOG_WARN("No matching stream found for demux: %s and caps: %s", GST_ELEMENT_NAME(demux), capsStr); + g_free(capsStr); + if (caps) + { + gst_caps_unref(caps); + } + } + } +} +static void element_setup_cb(void *playbin, void *element, void *instance) +{ + gchar* elemName = gst_element_get_name((GstElement*)element); + if (elemName && gst_StartsWith(elemName, "qtdemux")) + { + MW_LOG_WARN( "Add pad-added callback to demux:%s", elemName); + g_signal_connect(element, "pad-added", G_CALLBACK(GstPlayer_OnDemuxPadAddedCb), instance); + } + g_free(elemName); +} + +/** + * @brief Initialize properties/callback of appsrc + * @param[in] _this pointer to InterfacePlayerRDK instance associated with the playback + * @param[in] source pointer to appsrc instance to be initialized + * @param[in] mediaType stream type + */ +static void gstInitializeSource(void *_this, GObject *source, int iMediaType = eGST_MEDIATYPE_VIDEO) +{ + + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK*)_this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + GstMediaType mediaType = (GstMediaType)iMediaType; + + gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[mediaType]; + + pInterfacePlayerRDK->InitializeSourceForPlayer(pInterfacePlayerRDK,source, (int)mediaType); + stream->sourceConfigured = true; +} +/** + * @brief Callback when source is added by playbin + * @param[in] object a GstObject + * @param[in] orig the object that originated the signal + * @param[in] pspec the property that changed + * @param[in] _this pointer to InterfacePlayerRDK instance associated with the playback + */ +static void gst_found_source(GObject * object, GObject * orig, GParamSpec * pspec, void* data ) +{ + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK*)data; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + GstMediaType mediaType = eGST_MEDIATYPE_DEFAULT; + if (object == G_OBJECT(privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO].sinkbin)) + { + MW_LOG_MIL("Found source for video"); + mediaType = eGST_MEDIATYPE_VIDEO; + } + else if (object == G_OBJECT(privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].sinkbin)) + { + MW_LOG_MIL("Found source for audio"); + mediaType = eGST_MEDIATYPE_AUDIO; + } + else if (object == G_OBJECT(privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUX_AUDIO].sinkbin)) + { + MW_LOG_MIL("Found source for auxiliary audio"); + mediaType = eGST_MEDIATYPE_AUX_AUDIO; + } + else if(object == G_OBJECT(privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].sinkbin)) + { + MW_LOG_MIL("Found source for subtitle"); + mediaType = eGST_MEDIATYPE_SUBTITLE; + } + else + { + MW_LOG_WARN("found_source didn't find a valid source"); + } + if( mediaType != eGST_MEDIATYPE_DEFAULT) + { + gst_media_stream *stream; + stream = &privatePlayer->gstPrivateContext->stream[mediaType]; + g_object_get(orig, pspec->name, &stream->source, NULL); + gstInitializeSource(pInterfacePlayerRDK, G_OBJECT(stream->source), mediaType); + } +} +static void callback_element_added (GstElement * element, GstElement * source, gpointer data) +{ + InterfacePlayerRDK * pInterfacePlayerRDK = (InterfacePlayerRDK*)data; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + MW_LOG_INFO("callback_element_added: %s",GST_ELEMENT_NAME(source)); + if (element == privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUX_AUDIO].sinkbin) + { + privatePlayer->socInterface->SetAudioRoutingProperties(source); + } +} +/** + * @brief callback when the source has been created + * @param[in] element is the pipeline + * @param[in] source the creation of source triggered this callback + * @param[in] data pointer to data associated with the playback + */ +static void httpsoup_source_setup (GstElement * element, GstElement * source, gpointer data) +{ + InterfacePlayerRDK * pInterfacePlayerRDK = (InterfacePlayerRDK*)data; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + if (!strcmp(GST_ELEMENT_NAME(source), "source")) + { + std::string networkProxyValue = pInterfacePlayerRDK->m_gstConfigParam->networkProxy; /* Get the proxy network setting from configuration*/ + if(!networkProxyValue.empty()) + { + g_object_set(source, "proxy", networkProxyValue.c_str(), NULL); + MW_LOG_MIL("httpsoup -> Set network proxy '%s'", networkProxyValue.c_str()); + } + } + if (pInterfacePlayerRDK->m_gstConfigParam->media == eGST_MEDIAFORMAT_PROGRESSIVE) //Setting souphttpsrc priority back to GST_RANK_PRIMARY + { + GstPluginFeature* pluginFeature = gst_registry_lookup_feature (gst_registry_get (), "souphttpsrc"); + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK: souphttpsrc plugin feature not available;"); + } + else + { + MW_LOG_INFO("InterfacePlayerRDK: souphttpsrc plugin priority set to GST_RANK_PRIMARY"); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY ); + gst_object_unref(pluginFeature); + } + } +} +static GstElement* InterfacePlayerRDK_GetAppSrc(void *_this, GstMediaType mediaType) +{ + GstElement *source; + source = gst_element_factory_make("appsrc", NULL); + if (NULL == source) + { + MW_LOG_WARN("InterfacePlayerRDK_GetAppSrc Cannot create source"); + return NULL; + } + gstInitializeSource(_this, G_OBJECT(source), mediaType); + return source; +} + +/** + * @brief Callback function to get video frames + */ +GstFlowReturn InterfacePlayerRDK_OnVideoSample(GstElement* object, void *_this) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK*)_this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); +#if defined(__APPLE__) + HANDLER_CONTROL_HELPER(privatePlayer->gstPrivateContext->callbackControl, GST_FLOW_OK); + if(pInterfacePlayerRDK && pInterfacePlayerRDK->gstCbExportYUVFrame) + { + GstSample *sample = gst_app_sink_pull_sample (GST_APP_SINK (object)); + if (sample) + { + int width, height; + GstCaps *caps = gst_sample_get_caps(sample); + GstStructure *capsStruct = gst_caps_get_structure(caps,0); + gst_structure_get_int(capsStruct,"width",&width); + gst_structure_get_int(capsStruct,"height",&height); + GstBuffer *buffer = gst_sample_get_buffer(sample); + if (buffer) + { + GstMapInfo map; + if (gst_buffer_map(buffer, &map, GST_MAP_READ)) + { + pInterfacePlayerRDK->gstCbExportYUVFrame(map.data, (int)map.size, width, height); + gst_buffer_unmap(buffer, &map); + } + else + { + MW_LOG_ERR("buffer map failed"); + } + } + else + { + MW_LOG_ERR("buffer NULL"); + } + gst_sample_unref(sample); + } + else + { + MW_LOG_WARN("sample NULL"); + } + } +#endif + return GST_FLOW_OK; +} + +/** + * @fn SetupClosedCaptionControlStream + */ +void InterfacePlayerRDK::SetupClosedCaptionControlStream() +{ + GstElement *subtitlebin = nullptr, *appsrc = nullptr, *textsink = nullptr; + + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK*)this; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + gst_media_stream* stream = &privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE]; + + // Check elements are not already assigned + if (stream->sinkbin) + { + MW_LOG_ERR("Sinkbin already assigned"); + g_clear_object(&stream->sinkbin); + } + if (privatePlayer->gstPrivateContext->subtitle_sink) + { + MW_LOG_ERR("subtitle_sink already assigned"); + g_clear_object(&privatePlayer->gstPrivateContext->subtitle_sink); + } + if (stream->source) + { + MW_LOG_ERR("source already assigned"); + g_clear_object(&stream->source); + } + + // Create elements + // Note: rialtomsesubtitlesink is a custom sink that handles CC command data + if (!(appsrc = InterfacePlayerRDK_GetAppSrc(pInterfacePlayerRDK, eGST_MEDIATYPE_SUBTITLE))) + { + MW_LOG_ERR("Failed to create subtitle appsrc"); + } + else if (!(textsink = gst_element_factory_make("rialtomsesubtitlesink", NULL))) + { + MW_LOG_ERR("Failed to create subtitle sink"); + } + else if (!(subtitlebin = gst_bin_new("subtitlebin"))) + { + MW_LOG_ERR("Failed to create subtitle bin"); + } + else + { + // Add created elements to bin and link, then add to pipeline + // Note: appsrc caps are set in InitializeSourceForPlayer() + gst_bin_add_many(GST_BIN(subtitlebin), appsrc, textsink, NULL); + if (!gst_element_link(appsrc, textsink)) + { + MW_LOG_ERR("Failed to link subtitle elements"); + } + else if (!gst_bin_add(GST_BIN(privatePlayer->gstPrivateContext->pipeline), subtitlebin)) + { + MW_LOG_ERR("Failed to add subtitle bin to pipeline"); + } + else + { + // Everything succeeded, retain references to the elements + stream->source = GST_ELEMENT(gst_object_ref_sink(appsrc)); + privatePlayer->gstPrivateContext->subtitle_sink = GST_ELEMENT(gst_object_ref_sink(textsink)); + stream->sinkbin = GST_ELEMENT(gst_object_ref_sink(subtitlebin)); + + MW_LOG_INFO("Added subtitle bin with %s %p to pipeline", + GST_ELEMENT_NAME(privatePlayer->gstPrivateContext->subtitle_sink), + privatePlayer->gstPrivateContext->subtitle_sink); + + privatePlayer->SignalConnect(stream->sinkbin, "deep-notify::source", G_CALLBACK(gst_found_source), this); + if (!gst_element_sync_state_with_parent(stream->sinkbin)) + { + MW_LOG_ERR("Failed to sync subtitle bin to parent"); + } + + // Set initial mute state, will be updated by PlayerCCManager + g_object_set(privatePlayer->gstPrivateContext->subtitle_sink, "mute", TRUE, NULL); + } + } + + // If any of the above failed, clear all the elements + if (!stream->sinkbin) + { + g_clear_object(&appsrc); + g_clear_object(&textsink); + g_clear_object(&subtitlebin); + } +} + +int InterfacePlayerRDK::SetupStream(int streamId, void *playerInstance, std::string manifest) +{ + InterfacePlayerRDK* pInterfacePlayerRDK = (InterfacePlayerRDK*)playerInstance; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + gst_media_stream* stream = &pInterfacePlayerRDK->interfacePlayerPriv->gstPrivateContext->stream[streamId]; + if (eGST_MEDIATYPE_SUBTITLE == streamId) + { + if(m_gstConfigParam->gstreamerSubsEnabled) + { + if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + stream->sinkbin = GST_ELEMENT(gst_object_ref_sink(gst_element_factory_make("playbin", NULL))); + MW_LOG_INFO("subs using rialto subtitle sink"); + GstElement* textsink = gst_element_factory_make("rialtomsesubtitlesink", NULL); + if (textsink) + { + MW_LOG_INFO("Created rialtomsesubtitlesink: %s", GST_ELEMENT_NAME(textsink)); + } + else + { + MW_LOG_WARN("Failed to create rialtomsesubtitlesink"); + } + auto subtitlebin = gst_bin_new("subtitlebin"); + auto vipertransform = gst_element_factory_make("vipertransform", NULL); + gst_bin_add_many(GST_BIN(subtitlebin),vipertransform,textsink,NULL); + gst_element_link(vipertransform, textsink); + gst_element_add_pad(subtitlebin, gst_ghost_pad_new("sink", gst_element_get_static_pad(vipertransform, "sink"))); + + g_object_set(stream->sinkbin, "text-sink", subtitlebin, NULL); + interfacePlayerPriv->gstPrivateContext->subtitle_sink = textsink; + MW_LOG_MIL("using rialtomsesubtitlesink muted=%d sink=%p", interfacePlayerPriv->gstPrivateContext->subtitleMuted, interfacePlayerPriv->gstPrivateContext->subtitle_sink); + g_object_set(textsink, "mute", interfacePlayerPriv->gstPrivateContext->subtitleMuted ? TRUE : FALSE, NULL); + } + else + { + MW_LOG_INFO("subs using subtecbin"); + stream->sinkbin = gst_element_factory_make("subtecbin", NULL); /* Creates a new element of "subtecbin" type and returns a new GstElement */ + if (!stream->sinkbin) /* When a new element can not be created a NULL is returned */ + { + MW_LOG_WARN("Cannot set up subtitle subtecbin"); + return -1; + } + stream->sinkbin = GST_ELEMENT(gst_object_ref_sink(stream->sinkbin)); /* Retain a counted reference to sinkbin. */ + g_object_set(G_OBJECT(stream->sinkbin), "sync", FALSE, NULL); + + stream->source = GST_ELEMENT(gst_object_ref_sink(InterfacePlayerRDK_GetAppSrc(pInterfacePlayerRDK, eGST_MEDIATYPE_SUBTITLE))); + gst_bin_add_many(GST_BIN(interfacePlayerPriv->gstPrivateContext->pipeline), stream->source, stream->sinkbin, NULL); /* Add source and sink to the current pipeline */ + + if (!gst_element_link_many(stream->source, stream->sinkbin, NULL)) /* forms a GstElement link chain; linking stream->source to stream->sinkbin */ + { + MW_LOG_ERR("Failed to link subtitle elements"); + return -1; + } + + gst_element_sync_state_with_parent(stream->source); + gst_element_sync_state_with_parent(stream->sinkbin); + interfacePlayerPriv->gstPrivateContext->subtitle_sink = GST_ELEMENT(gst_object_ref(stream->sinkbin)); + g_object_set(stream->sinkbin, "mute", interfacePlayerPriv->gstPrivateContext->subtitleMuted ? TRUE : FALSE, NULL); + return 0; + } + } + } + else + { + MW_LOG_INFO("using playbin"); /* Media is not subtitle, use the generic playbin */ + stream->sinkbin = GST_ELEMENT(gst_object_ref_sink(gst_element_factory_make("playbin", NULL))); /* Creates a new element of "playbin" type and returns a new GstElement */ + + if (m_gstConfigParam->tcpServerSink) + { + MW_LOG_INFO("using tcpserversink"); + GstElement* sink = gst_element_factory_make("tcpserversink", NULL); + int tcp_port = m_gstConfigParam->tcpPort; + // TCPServerSinkPort of 0 is treated specially and should not be incremented for audio + if (eGST_MEDIATYPE_VIDEO == streamId) + { + g_object_set (G_OBJECT (sink), "port", tcp_port,"host","127.0.0.1",NULL); + g_object_set(stream->sinkbin, "video-sink", sink, NULL); + } + else if (eGST_MEDIATYPE_AUDIO == streamId) + { + g_object_set (G_OBJECT (sink), "port", (tcp_port>0)?tcp_port+1:tcp_port,"host","127.0.0.1",NULL); + g_object_set(stream->sinkbin, "audio-sink", sink, NULL); + } + } + else if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink && eGST_MEDIATYPE_VIDEO == streamId) + { + MW_LOG_INFO("using rialtomsevideosink"); + GstElement* vidsink = gst_element_factory_make("rialtomsevideosink", NULL); + if (vidsink) + { + MW_LOG_INFO("Created rialtomsevideosink: %s", GST_ELEMENT_NAME(vidsink)); + g_object_set(stream->sinkbin, "video-sink", vidsink, NULL); /* In the stream->sinkbin, set the video-sink property to vidsink */ + GstMediaFormat mediaFormat = (GstMediaFormat)m_gstConfigParam->media; + if(eGST_MEDIAFORMAT_HLS == mediaFormat) + { + MW_LOG_INFO("setting has-drm=false for clear HLS/TS playback"); + g_object_set(vidsink, "has-drm", FALSE, NULL); + } + interfacePlayerPriv->gstPrivateContext->video_sink = vidsink; + } + else + { + MW_LOG_WARN("Failed to create rialtomsevideosink"); + } + } + else if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink && eGST_MEDIATYPE_AUDIO == streamId) + { + MW_LOG_INFO("using rialtomseaudiosink"); + GstElement* audSink = gst_element_factory_make("rialtomseaudiosink",NULL); + if(audSink) + { + MW_LOG_INFO("Created rialtomseaudiosink : %s",GST_ELEMENT_NAME(audSink)); + g_object_set(stream->sinkbin, "audio-sink", audSink, NULL); + interfacePlayerPriv->gstPrivateContext->audio_sink = audSink; + } + else + { + MW_LOG_WARN("Failed to create rialtomseaudiosink"); + } + } + else if (interfacePlayerPriv->gstPrivateContext->using_westerossink && eGST_MEDIATYPE_VIDEO == streamId) + { + GstElement* vidsink = interfacePlayerPriv->socInterface->GetVideoSink(stream->sinkbin); + (void)vidsink; + } + +#if defined(__APPLE__) + if( pInterfacePlayerRDK->gstCbExportYUVFrame ) + { + if (eGST_MEDIATYPE_VIDEO == streamId) + { + MW_LOG_MIL("using appsink"); + GstElement* appsink = gst_element_factory_make("appsink", NULL); + assert(appsink); + GstCaps *caps = gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "I420", NULL); + gst_app_sink_set_caps (GST_APP_SINK(appsink), caps); + g_object_set (G_OBJECT(appsink), "emit-signals", TRUE, "sync", TRUE, NULL); + privatePlayer->SignalConnect(appsink, "new-sample", G_CALLBACK (InterfacePlayerRDK_OnVideoSample), this); + g_object_set(stream->sinkbin, "video-sink", appsink, NULL); + GstObject **oldobj = (GstObject **)&interfacePlayerPriv->gstPrivateContext->video_sink; + GstObject *newobj = (GstObject *)appsink; + gst_object_replace( oldobj, newobj ); + } + } +#endif + + if (eGST_MEDIATYPE_AUX_AUDIO == streamId) + { + // We need to route audio through audsrvsink + GstElement *audiosink = gst_element_factory_make("audsrvsink", NULL); /* Creates a new element of "audsrvsink" type and returns a new GstElement */ + g_object_set(audiosink, "session-type", 2, NULL); + g_object_set(audiosink, "session-name", "btSAP", NULL); + g_object_set(audiosink, "session-private", TRUE, NULL); + + g_object_set(stream->sinkbin, "audio-sink", audiosink, NULL); /* In the stream->sinkbin, set the audio-sink property to audiosink */ + if (privatePlayer->socInterface->RequiredElementSetup()) + { + privatePlayer->SignalConnect(stream->sinkbin, "element-setup", G_CALLBACK(callback_element_added), this); + } + + MW_LOG_MIL("using audsrvsink"); + } + } + gst_bin_add(GST_BIN(interfacePlayerPriv->gstPrivateContext->pipeline), stream->sinkbin); /* Add the stream sink to the pipeline */ + + gint flags; + g_object_get(stream->sinkbin, "flags", &flags, NULL); /* Read the state of the current flags */ + MW_LOG_MIL("playbin flags1: 0x%x", flags); + + bool isSub = (eGST_MEDIATYPE_SUBTITLE == streamId); + privatePlayer->socInterface->SetPlaybackFlags(flags, isSub); + g_object_set(stream->sinkbin, "flags", flags, NULL); // needed? + + GstMediaFormat mediaFormat = (GstMediaFormat)m_gstConfigParam->media; + if((mediaFormat != eGST_MEDIAFORMAT_PROGRESSIVE) || ( m_gstConfigParam->appSrcForProgressivePlayback)) + { + g_object_set(stream->sinkbin, "uri", "appsrc://", NULL); /* Assign uri property to appsrc, this will enable data insertion into pipeline */ + privatePlayer->SignalConnect(stream->sinkbin, "deep-notify::source", G_CALLBACK(gst_found_source), this); + } + else + { + GstPluginFeature* pluginFeature = gst_registry_lookup_feature (gst_registry_get (), "souphttpsrc"); //increasing souphttpsrc priority + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK: souphttpsrc plugin feature not available;"); + } + else + { + MW_LOG_INFO("InterfacePlayerRDK: souphttpsrc plugin priority set to GST_RANK_PRIMARY + 111"); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY + 111); + gst_object_unref(pluginFeature); + } + g_object_set(stream->sinkbin, "uri", manifest.c_str(), NULL); + privatePlayer->SignalConnect(stream->sinkbin, "source-setup", G_CALLBACK(httpsoup_source_setup), this); + } + + if (( ((mediaFormat == eGST_MEDIAFORMAT_DASH || mediaFormat == eGST_MEDIAFORMAT_HLS_MP4) && + m_gstConfigParam->seamlessAudioSwitch)) + || + (mediaFormat == eGST_MEDIAFORMAT_DASH && eGST_MEDIATYPE_VIDEO == streamId && + m_gstConfigParam->enablePTSReStamp)) + { + // Send the media_stream object so that qtdemux can be instantly mapped to media type without caps/parent check + g_signal_connect(stream->sinkbin, "element_setup", G_CALLBACK(element_setup_cb), pInterfacePlayerRDK); + } + if (eGST_MEDIATYPE_VIDEO == streamId && (mediaFormat==eGST_MEDIAFORMAT_DASH || mediaFormat==eGST_MEDIAFORMAT_HLS_MP4)) + { + // enable multiqueue + int MaxGstVideoBufBytes = m_gstConfigParam->videoBufBytes; + MW_LOG_INFO("Setting gst Video buffer size bytes to %d", MaxGstVideoBufBytes); + privatePlayer->socInterface->SetVideoBufferSize(stream->sinkbin, MaxGstVideoBufBytes); + } + if (eGST_MEDIATYPE_AUDIO == streamId) + { + privatePlayer->socInterface->ConfigurePluginPriority(); + } + gst_element_sync_state_with_parent(stream->sinkbin); + return 0; +} + +/** + * @fn SendGstEvents + * @param[in] mediaType stream type + */ +void InterfacePlayerPriv::SendGstEvents(int mediaType, GstClockTime pts, int enableGstPosQuery, bool enablePTSReStamp, int vodTrickModeFPS) +{ + gst_media_stream* stream = &gstPrivateContext->stream[mediaType]; + gboolean enableOverride = FALSE; + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(stream->source), "src"); /* Retrieves the src pad */ + + if(stream->pendingSeek) + { + if(gstPrivateContext->seekPosition > 0) + { + MW_LOG_MIL("gst_element_seek_simple! mediaType:%d pts:%" GST_TIME_FORMAT " seekPosition:%" GST_TIME_FORMAT, + mediaType, GST_TIME_ARGS(pts), GST_TIME_ARGS(gstPrivateContext->seekPosition * GST_SECOND)); + if(!gst_element_seek_simple(GST_ELEMENT(stream->source), GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, (gstPrivateContext->seekPosition * GST_SECOND))) + { + MW_LOG_ERR("Seek failed"); + } + + } + stream->pendingSeek = false; + } + + enableOverride = SendQtDemuxOverrideEvent(mediaType, pts, enablePTSReStamp, vodTrickModeFPS); //need to change to priv + GstMediaType type = static_cast(mediaType); + + if (type == eGST_MEDIATYPE_VIDEO) + { + // Westerossink gives position as an absolute value from segment.start. In Player's GStreamer pipeline + // appsrc's base class - basesrc sends an additional segment event since we performed a flushing seek. + // To figure out the new segment.start, we need to send a segment query which will be replied + // by basesrc to get the updated segment event values. + // When override is enabled qtdemux internally restamps and sends segment.start = 0 which is part of + // Player's change in qtdemux so we don't need to query segment.start + // Enabling position query based progress reporting for non-westerossink configurations. + // Player will send a segment.start query if segmentStart is -1. + if (enableGstPosQuery && (enableOverride == FALSE)) + { + gstPrivateContext->segmentStart = -1; + } + else + { + gstPrivateContext->segmentStart = 0; + } + } + + if (stream->format == GST_FORMAT_ISO_BMFF) + { + // There is a possibility that only single protection event is queued for multiple type + // since they are encrypted using same id. Hence check if protection event is queued for + // other types + GstEvent* event = gstPrivateContext->protectionEvent[mediaType]; + if (event == NULL) + { + // Check protection event for other types + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + if (i != mediaType && gstPrivateContext->protectionEvent[i] != NULL) + { + event = gstPrivateContext->protectionEvent[i]; + break; + } + } + } + if(event) + { + MW_LOG_MIL("pushing protection event! mediatype: %d", type); + if (!gst_pad_push_event(sourceEleSrcPad, gst_event_ref(event))) + { + MW_LOG_ERR("push protection event failed!"); + } + } + } + gst_object_unref(sourceEleSrcPad); + stream->resetPosition = false; +} + +/** + * @fn SendQtDemuxOverrideEvent + * @param[in] mediaType stream type + * @param[in] pts position value of buffer + * @param[in] ptr buffer pointer + * @param[in] len length of buffer + * @ret TRUE if override is enabled, FALSE otherwise + */ +gboolean InterfacePlayerPriv::SendQtDemuxOverrideEvent(int mediaType, GstClockTime pts, bool enablePTSReStamp, int vodTrickModeFPS, const void *ptr, size_t len) +{ + gst_media_stream* stream = &gstPrivateContext->stream[mediaType]; + gboolean enableOverride = false; + GstMediaType type = static_cast(mediaType); + + if (!enablePTSReStamp) + { + enableOverride = (gstPrivateContext->rate != GST_NORMAL_PLAY_RATE); + } + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(stream->source), "src"); /* Retrieves the src pad */ + if (stream->format == GST_FORMAT_ISO_BMFF && type != eGST_MEDIATYPE_SUBTITLE) + { + /* The below statement creates a new eventStruct with the player's name suffixed with '_override' and sets its three variables as follows:- + 1) the variable 'enable' has datatype of G_TYPE_BOOLEAN and has value enableOverride. + 2) the variable 'rate' has datatype of G_TYPE_FLOAT and is set to (float)playerInstance->gstPrivateContext->rate. + 3) the variable playerName suffixed with 'player' has datatype of G_TYPE_BOOLEAN and a value of TRUE. + */ + std::string overrideName = mPlayerName + "_override"; + std::string player = mPlayerName + "player"; + GstStructure * eventStruct = gst_structure_new(overrideName.c_str(), "enable", G_TYPE_BOOLEAN, enableOverride, "rate", G_TYPE_FLOAT, (float)gstPrivateContext->rate, player.c_str(), G_TYPE_BOOLEAN, TRUE, "fps", G_TYPE_UINT, (guint)vodTrickModeFPS, NULL); + if (!gst_pad_push_event(sourceEleSrcPad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, eventStruct))) + { + MW_LOG_ERR("Error on sending qtdemux override event"); + } + } + gst_object_unref(sourceEleSrcPad); + return enableOverride; +} + +/** + * @brief Check if audio buffers to be forwarded or not + */ +bool InterfacePlayerRDK::ForwardAudioBuffersToAux() +{ + return (interfacePlayerPriv->gstPrivateContext->forwardAudioBuffers && interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUX_AUDIO].format != GST_FORMAT_INVALID); +} + +/** + * @brief Get the video rectangle co-ordinates + */ +std::string InterfacePlayerRDK::GetVideoRectangle() +{ + return std::string(interfacePlayerPriv->gstPrivateContext->videoRectangle); +} + +void InterfacePlayerRDK::SetSubtitlePtsOffset(std::uint64_t pts_offset) +{ + if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + if(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].source) + { + MW_LOG_INFO("usingRialtoSink pts_offset gst_seek_simple %" PRIu64 "", pts_offset); + GstClockTime pts = ((double)pts_offset) * GST_SECOND; + GstStructure *structure{gst_structure_new("set-pts-offset", "pts-offset", G_TYPE_UINT64, pts, nullptr)}; + if (!gst_element_send_event(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].source, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure))) + { + MW_LOG_WARN("usingRialtoSink Failed to seek text-sink element"); + } + } + } + else if (interfacePlayerPriv->gstPrivateContext->subtitle_sink) + { + MW_LOG_INFO("pts_offset %" PRIu64 ", subtitle_sink =%p", pts_offset, interfacePlayerPriv->gstPrivateContext->subtitle_sink); + //We use seek_pos_seconds as an offset during seek, so we subtract that here to get an offset from zero position + g_object_set(interfacePlayerPriv->gstPrivateContext->subtitle_sink, "pts-offset", static_cast(pts_offset*1000), NULL); + } + else + MW_LOG_INFO("subtitle_sink is NULL"); +} + +/** + * @brief Reset first frame + */ +void InterfacePlayerRDK::ResetFirstFrame(void) +{ + MW_LOG_WARN("Reset first frame"); + interfacePlayerPriv->gstPrivateContext->firstFrameReceived = false; +} + +GstPlaybackQualityStruct* InterfacePlayerRDK::GetVideoPlaybackQuality(void) +{ + GstStructure *stats= 0; + GstElement *element; + if((interfacePlayerPriv->socInterface->IsPlaybackQualityFromSink())) + { + element = interfacePlayerPriv->gstPrivateContext->video_sink; + } + else + { + element = interfacePlayerPriv->gstPrivateContext->video_dec; + } + if( element ) + { + g_object_get( G_OBJECT(element), "stats", &stats, NULL ); + if ( stats ) + { + const GValue *value; + value= gst_structure_get_value( stats, "rendered" ); + if ( value ) + { + interfacePlayerPriv->gstPrivateContext->playbackQuality.rendered= g_value_get_uint64( value ); + } + value= gst_structure_get_value( stats, "dropped" ); + if ( value ) + { + interfacePlayerPriv->gstPrivateContext->playbackQuality.dropped= g_value_get_uint64( value ); + } + MW_LOG_MIL("rendered %lld dropped %lld", interfacePlayerPriv->gstPrivateContext->playbackQuality.rendered, interfacePlayerPriv->gstPrivateContext->playbackQuality.dropped); + gst_structure_free( stats ); + return &interfacePlayerPriv->gstPrivateContext->playbackQuality; + } + else + { + MW_LOG_ERR("Failed to get sink stats"); + } + } + return NULL; +} + +/** + * @brief Get playback position in MS + */ +long long InterfacePlayerRDK::GetPositionMilliseconds(void) +{ + long long rc = 0; + if (interfacePlayerPriv->gstPrivateContext->pipeline == NULL) + { + MW_LOG_ERR("Pipeline is NULL"); + return rc; + } + if (interfacePlayerPriv->gstPrivateContext->positionQuery == NULL) + { + MW_LOG_ERR("Position query is NULL"); + return rc; + } + // Perform gstreamer query and related operation only when pipeline is playing or if deliberately put in paused + if (interfacePlayerPriv->gstPrivateContext->pipelineState != GST_STATE_PLAYING && + !(interfacePlayerPriv->gstPrivateContext->pipelineState == GST_STATE_PAUSED && interfacePlayerPriv->gstPrivateContext->paused) && + // The player should be (and probably soon will be) in the playing state so don't exit early. + GST_STATE_TARGET(interfacePlayerPriv->gstPrivateContext->pipeline) != GST_STATE_PLAYING) + { + MW_LOG_INFO("Pipeline is in %s state %s target state, paused=%d returning position as %lld", gst_element_state_get_name(interfacePlayerPriv->gstPrivateContext->pipelineState), gst_element_state_get_name(GST_STATE_TARGET(interfacePlayerPriv->gstPrivateContext->pipeline)), interfacePlayerPriv->gstPrivateContext->paused, rc); + return rc; + } + gst_media_stream* video = &interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO]; + // segment.start needs to be queried + if (interfacePlayerPriv->gstPrivateContext->segmentStart == -1) + { + GstQuery *segmentQuery = gst_query_new_segment(GST_FORMAT_TIME); + // Send query to video playbin in pipeline. + // Special case include trickplay, where only video playbin is active + // This is to get the actual start position from video decoder/sink. If these element doesn't support the query appsrc should respond + if (gst_element_query(video->source, segmentQuery) == TRUE) + { + gint64 start; + gst_query_parse_segment(segmentQuery, NULL, NULL, &start, NULL); + interfacePlayerPriv->gstPrivateContext->segmentStart = GST_TIME_AS_MSECONDS(start); + MW_LOG_MIL("InterfacePlayerRDK: Segment start: %" G_GINT64_FORMAT, interfacePlayerPriv->gstPrivateContext->segmentStart); + } + else + { + MW_LOG_ERR("InterfacePlayerRDK: segment query failed"); + } + gst_query_unref(segmentQuery); + } + if (gst_element_query(video->sinkbin,interfacePlayerPriv->gstPrivateContext->positionQuery) == TRUE) + { + gint64 pos = 0; + int rate = interfacePlayerPriv->gstPrivateContext->rate; + gst_query_parse_position(interfacePlayerPriv->gstPrivateContext->positionQuery, NULL, &pos); + if ( eGST_MEDIAFORMAT_PROGRESSIVE == static_cast(m_gstConfigParam->media)) + { + rate = 1; // MP4 position query always return absolute value + } + if (interfacePlayerPriv->gstPrivateContext->segmentStart > 0) + { + // Deduct segment.start to find the actual time of media that's played. + rc = (GST_TIME_AS_MSECONDS(pos) - interfacePlayerPriv->gstPrivateContext->segmentStart) * rate; + MW_LOG_DEBUG("positionQuery pos - %" G_GINT64_FORMAT " rc - %lld SegStart -%" G_GINT64_FORMAT, GST_TIME_AS_MSECONDS(pos), rc,interfacePlayerPriv->gstPrivateContext->segmentStart); + } + else + { + rc = GST_TIME_AS_MSECONDS(pos) * rate; + MW_LOG_DEBUG("positionQuery pos - %" G_GINT64_FORMAT " rc - %lld" , GST_TIME_AS_MSECONDS(pos), rc); + } + //MW_LOG_MIL("InterfacePlayerRDK: with positionQuery pos - %" G_GINT64_FORMAT " rc - %lld", GST_TIME_AS_MSECONDS(pos), rc); + //positionQuery is not unref-ed here, because it could be reused for future position queries + } + return rc; +} + +/** + * @brief Get playback duration in MS + */ +long InterfacePlayerRDK::GetDurationMilliseconds(void) +{ + long rc = 0; + if( interfacePlayerPriv->gstPrivateContext->pipeline ) + { + if( interfacePlayerPriv->gstPrivateContext->pipelineState == GST_STATE_PLAYING || // playing + (interfacePlayerPriv->gstPrivateContext->pipelineState == GST_STATE_PAUSED && interfacePlayerPriv->gstPrivateContext->paused) ) // paused by user + { + interfacePlayerPriv->gstPrivateContext->durationQuery = gst_query_new_duration(GST_FORMAT_TIME); /*Constructs a new stream duration query object to query in the given format */ + if( interfacePlayerPriv->gstPrivateContext->durationQuery ) + { + gboolean res = gst_element_query(interfacePlayerPriv->gstPrivateContext->pipeline,interfacePlayerPriv->gstPrivateContext->durationQuery); + if( res ) + { + gint64 duration; + gst_query_parse_duration(interfacePlayerPriv->gstPrivateContext->durationQuery, NULL, &duration); /* parses the value into duration */ + rc = GST_TIME_AS_MSECONDS(duration); + } + else + { + MW_LOG_ERR("Duration query failed"); + } + gst_query_unref(interfacePlayerPriv->gstPrivateContext->durationQuery); /* Decreases the refcount of the durationQuery. In this case the count will be zero, so it will be freed*/ + interfacePlayerPriv->gstPrivateContext->durationQuery = NULL; + } + else + { + MW_LOG_WARN("Duration query is NULL"); + } + } + else + { + MW_LOG_WARN("Pipeline is in %s state", gst_element_state_get_name(interfacePlayerPriv->gstPrivateContext->pipelineState) ); + } + } + else + { + MW_LOG_WARN("Pipeline is null"); + } + return rc; +} +/** + * @brief Get video display's width and height + */ +void InterfacePlayerRDK::GetVideoSize(int &width, int &height) +{ + int x; + int y; + int w = 0; + int h = 0; + if ((4 == sscanf(interfacePlayerPriv->gstPrivateContext->videoRectangle, "%d,%d,%d,%d", &x, &y, &w, &h)) && (w > 0) && (h > 0)) + { + width = w; + height = h; + } +} + +void InterfacePlayerRDK::SetSubtitleMute(bool muted) +{ + interfacePlayerPriv->gstPrivateContext->subtitleMuted = muted; + if (interfacePlayerPriv->gstPrivateContext->subtitle_sink) + { + MW_LOG_INFO("muted %d, subtitle_sink =%p", muted, interfacePlayerPriv->gstPrivateContext->subtitle_sink); + g_object_set(interfacePlayerPriv->gstPrivateContext->subtitle_sink, "mute", interfacePlayerPriv->gstPrivateContext->subtitleMuted ? TRUE : FALSE, NULL); /* Update the 'mute' property of the sink */ + } + else + MW_LOG_INFO("subtitle_sink is NULL"); +} + +/** + * @brief Set video display rectangle co-ordinates + */ +void InterfacePlayerRDK::SetVideoRectangle(int x, int y, int w, int h) +{ + int currentX = 0, currentY = 0, currentW = 0, currentH = 0; + if (strcmp(interfacePlayerPriv->gstPrivateContext->videoRectangle, "") != 0) + { + sscanf(interfacePlayerPriv->gstPrivateContext->videoRectangle,"%d,%d,%d,%d",¤tX,¤tY,¤tW,¤tH); + } + //check the existing VideoRectangle co-ordinates + if ((currentX == x) && (currentY == y) && (currentW == w) && (currentH == h)) + { + MW_LOG_TRACE("Ignoring new co-ordinates, same as current Rect (x:%d, y:%d, w:%d, h:%d)", currentX, currentY, currentW, currentH); + //ignore setting same rectangle co-ordinates and return + return; + } + snprintf(interfacePlayerPriv->gstPrivateContext->videoRectangle, sizeof(interfacePlayerPriv->gstPrivateContext->videoRectangle), "%d,%d,%d,%d", x,y,w,h); + MW_LOG_MIL("Rect %s, video_sink =%p", + interfacePlayerPriv->gstPrivateContext->videoRectangle, interfacePlayerPriv->gstPrivateContext->video_sink); + if (m_gstConfigParam->enableRectPropertyCfg) + { + if (interfacePlayerPriv->gstPrivateContext->video_sink) + { + g_object_set(interfacePlayerPriv->gstPrivateContext->video_sink, "rectangle", interfacePlayerPriv->gstPrivateContext->videoRectangle, NULL); + } + else + { + MW_LOG_WARN("Scaling not possible at this time"); + } + } + else + { + MW_LOG_WARN("New co-ordinates ignored since westerossink is used"); + } +} + +/** + * @brief Un-pause pipeline and notify buffer end event to player + */ +bool InterfacePlayerRDK::StopBuffering(bool forceStop, bool &isPlaying) +{ + bool sendEndEvent = false; + if (interfacePlayerPriv->gstPrivateContext->video_dec) + { + int frames = -1; + g_object_get(interfacePlayerPriv->gstPrivateContext->video_dec,"queued_frames",(uint*)&frames,NULL); + bool stopBuffering = forceStop; + if( !stopBuffering ) + { + if (frames == -1 || frames >= m_gstConfigParam->framesToQueue) + { + stopBuffering = true; + } + } + + if (!stopBuffering) + { + static int bufferLogCount = 0; + if (0 == (bufferLogCount++ % 10) ) + { + MW_LOG_WARN("Not enough data available to stop buffering, frames %d !", frames); + } + } + + else + { + MW_LOG_MIL("Enough data available to stop buffering, frames %d !", frames); + GstState current, pending; + + if(GST_STATE_CHANGE_FAILURE == gst_element_get_state(interfacePlayerPriv->gstPrivateContext->pipeline, ¤t, &pending, 0 * GST_MSECOND)) + { + sendEndEvent = false; + } + else + { + isPlaying = true; + if (current == GST_STATE_PLAYING) + { + sendEndEvent = true; + } + } + } + + } + return sendEndEvent; +} + +/** + * @brief Retrieve the Closed Caption sink handle from pipeline + */ +unsigned long InterfacePlayerRDK::GetCCDecoderHandle() +{ + gpointer dec_handle = NULL; + + if (interfacePlayerPriv->gstPrivateContext->usingClosedCaptionsControl) + { + dec_handle = interfacePlayerPriv->gstPrivateContext->subtitle_sink; + MW_LOG_INFO("CC Decoder handle %p", dec_handle); + } + else + { + if(interfacePlayerPriv->gstPrivateContext->video_dec != NULL) + { + interfacePlayerPriv->socInterface->GetCCDecoderHandle(&dec_handle, interfacePlayerPriv->gstPrivateContext->video_dec); + } + MW_LOG_INFO("CC Decoder handle received %p for video_dec %p", dec_handle, interfacePlayerPriv->gstPrivateContext->video_dec); + } + return (unsigned long)dec_handle; +} + +/** + * @brief Wait for source element to be configured. + */ +bool InterfacePlayerRDK::WaitForSourceSetup(int mediaType) +{ + bool ret = false; + GstMediaType type = static_cast(mediaType); + gst_media_stream* stream = &interfacePlayerPriv->gstPrivateContext->stream[type]; + int timeRemaining = DEFAULT_TIMEOUT_FOR_SOURCE_SETUP; + int waitInterval = 100; // Polling interval (ms) + + std::unique_lock lock(mSourceSetupMutex); + MW_LOG_WARN("Source element[%p] for track[%d] not configured, waiting for setup!", stream->source, type); + + while (timeRemaining >= 0) + { + // Wait for either a notification or timeout + auto timeout = std::chrono::milliseconds(waitInterval); + mSourceSetupCV.wait_for(lock, timeout); + if (mPauseInjector) + { + //Playback stopped by application + break; + } + else + { + if (stream->sourceConfigured) + { + MW_LOG_MIL("Source element[%p] for track[%d] setup completed!", stream->source, type); + ret = true; + break; + } + } + + // Reduce remaining time + timeRemaining -= waitInterval; + } + + // If not successful, log timeout + if (!ret) + { + MW_LOG_WARN("Wait for source element setup for track[%d] exited/timed out!", mediaType); + } + + return ret; +} + +/** + * @brief Forward buffer to aux pipeline + */ +void InterfacePlayerPriv::ForwardBuffersToAuxPipeline(GstBuffer *buffer, bool pauseInjector, void *user_data) +{ + gst_media_stream *stream = &gstPrivateContext->stream[eGST_MEDIATYPE_AUX_AUDIO]; + InterfacePlayerRDK *instance = static_cast(user_data); + if (!stream->sourceConfigured && stream->format != GST_FORMAT_INVALID) + { + bool status = instance->WaitForSourceSetup((int)eGST_MEDIATYPE_AUX_AUDIO); + if (pauseInjector && !status) + { + // Buffer is not owned by us, no need to free + return; + } + } + + GstBuffer *fwdBuffer = gst_buffer_new(); + if (fwdBuffer != NULL) + { + if (FALSE == gst_buffer_copy_into(fwdBuffer, buffer, GST_BUFFER_COPY_ALL, 0, -1)) + { + MW_LOG_ERR("Error while copying audio buffer to auxiliary buffer!!"); + gst_buffer_unref(fwdBuffer); + return; + } + //MW_LOG_TRACE("Forward audio buffer to auxiliary pipeline!!"); + GstFlowReturn ret = gst_app_src_push_buffer(GST_APP_SRC(stream->source), fwdBuffer); + if (ret != GST_FLOW_OK) + { + MW_LOG_ERR("gst_app_src_push_buffer error: %d[%s] mediaType %d", ret, gst_flow_get_name (ret), (int)eGST_MEDIATYPE_AUX_AUDIO); + assert(false); + } + } +} + +bool InterfacePlayerRDK::HandleVideoBufferSent() +{ + bool isFirstBuffer = (interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent == 0); + interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent++; + return isFirstBuffer; +} + +void InterfacePlayerRDK::SetPlayerName(std::string name) +{ + interfacePlayerPriv->mPlayerName = name; +} + +/** + * @brief Inject stream buffer to gstreamer pipeline + */ +bool InterfacePlayerRDK::SendHelper(int type, const void *ptr, size_t len, double fpts, double fdts, double fDuration, double fragmentPTSoffset, bool copy, bool initFragment, bool &discontinuity, bool ¬ifyFirstBufferProcessed, bool &sendNewSegmentEvent, bool &resetTrickUTC, bool &firstBufferPushed) +{ + GstMediaType mediaType = static_cast(type); + GstClockTime pts = (GstClockTime)(fpts * GST_SECOND); + GstClockTime dts = (GstClockTime)(fdts * GST_SECOND); + GstClockTime duration = (GstClockTime)(fDuration * 1000000000LL); + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[mediaType]; + if (eGST_MEDIATYPE_SUBTITLE == mediaType && discontinuity) + { + MW_LOG_WARN( "[%d] Discontinuity detected - setting subtitle clock to %" GST_TIME_FORMAT " dAR %d rP %d init %d sC %d", + mediaType, + GST_TIME_ARGS(pts), + !mPauseInjector, + stream->resetPosition, + initFragment, + stream->sourceConfigured ); + //gst_element_seek_simple(GST_ELEMENT(stream->source), GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, pts); + } + + bool segmentEventSent = false; + bool isFirstBuffer = stream->resetPosition; + // Make sure source element is present before data is injected + // If format is FORMAT_INVALID, we don't know what we are doing here + pthread_mutex_lock(&stream->sourceLock); + + if (!stream->sourceConfigured && stream->format != GST_FORMAT_INVALID) + { + bool status = WaitForSourceSetup(type); + + if (mPauseInjector || !status) + { + pthread_mutex_unlock(&stream->sourceLock); + return false; + } + } + if (isFirstBuffer) + { + //Send Gst Event when first buffer received after new tune, seek or period change + int enableGstQuery = m_gstConfigParam->enableGstPosQuery; + interfacePlayerPriv->SendGstEvents((int)mediaType, pts, enableGstQuery, m_gstConfigParam->enablePTSReStamp, m_gstConfigParam->vodTrickModeFPS); + + if (mediaType == eGST_MEDIATYPE_AUDIO && ForwardAudioBuffersToAux()) + { + interfacePlayerPriv->SendGstEvents((int)eGST_MEDIATYPE_AUX_AUDIO, pts, enableGstQuery, m_gstConfigParam->enablePTSReStamp, m_gstConfigParam->vodTrickModeFPS); + } + + // included to fix av sync / trickmode speed issues + // Also add check for trick-play on 1st frame. + if( interfacePlayerPriv->gstPrivateContext->video_sink && + sendNewSegmentEvent == true) + { + interfacePlayerPriv->SendNewSegmentEvent(mediaType, pts, 0); + segmentEventSent = true; + } + MW_LOG_DEBUG("mediaType[%d] SendGstEvents - first buffer received !!! initFragment: %d, pts: %" G_GUINT64_FORMAT, mediaType, initFragment, pts); + } + + sendNewSegmentEvent = segmentEventSent; + bool bPushBuffer = !mPauseInjector; + if(bPushBuffer) + { + GstBuffer *buffer; + + // If pts restamp is enabled in config + // we need to set the pts offset used for subtitles else set it to 0 + gint64 pts_offset; + + if (m_gstConfigParam->enablePTSReStamp) + { + pts_offset = -(gint64)(fragmentPTSoffset * 1000L); + } + else + { + pts_offset = 0; + } + + if(copy) + { + buffer = gst_buffer_new_and_alloc((guint)len); + + if (buffer) + { + GstMapInfo map; + gst_buffer_map(buffer, &map, GST_MAP_WRITE); + memcpy(map.data, ptr, len); + gst_buffer_unmap(buffer, &map); + GST_BUFFER_PTS(buffer) = pts; + GST_BUFFER_DTS(buffer) = dts; + GST_BUFFER_DURATION(buffer) = duration; + if (mediaType == eGST_MEDIATYPE_SUBTITLE) + GST_BUFFER_OFFSET(buffer) = pts_offset; + + MW_LOG_DEBUG("Sending segment for mediaType[%d]. pts %" G_GUINT64_FORMAT " dts %" G_GUINT64_FORMAT, mediaType, pts, dts); + MW_LOG_DEBUG(" fragmentPTSoffset %" G_GINT64_FORMAT, pts_offset); + } + else + { + bPushBuffer = false; + } + } + else + { // transfer + buffer = gst_buffer_new_wrapped((gpointer)ptr,(gsize)len); + + if (buffer) + { + GST_BUFFER_PTS(buffer) = pts; + GST_BUFFER_DTS(buffer) = dts; + GST_BUFFER_DURATION(buffer) = duration; + if (mediaType == eGST_MEDIATYPE_SUBTITLE) + GST_BUFFER_OFFSET(buffer) = pts_offset; + + MW_LOG_INFO("Sending segment for mediaType[%d]. pts %" G_GUINT64_FORMAT " dts %" G_GUINT64_FORMAT" len:%zu init:%d discontinuity:%d dur:%" G_GUINT64_FORMAT, + mediaType, pts, dts, len, initFragment, discontinuity,duration); + + } + else + { + bPushBuffer = false; + } + } + + if (bPushBuffer) + { + if (mediaType == eGST_MEDIATYPE_AUDIO && ForwardAudioBuffersToAux()) + { + interfacePlayerPriv->ForwardBuffersToAuxPipeline(buffer, mPauseInjector, this); + } + if( mediaType<2 && m_gstConfigParam->useMp4Demux && + !copy /* avoid using this path for hls/ts */ ) + { + static Mp4Demux *m_mp4Demux[2]; + Mp4Demux *mp4Demux = m_mp4Demux[mediaType]; + if( !mp4Demux ) + { + mp4Demux = new Mp4Demux(); + m_mp4Demux[mediaType] = mp4Demux; + } + mp4Demux->Parse(ptr,len); + int count = mp4Demux->count(); + if( count>0 ) + { // media segment + for( int i=0; igetLen(i); + double pts = mp4Demux->getPts(i); + double dts = mp4Demux->getDts(i); + double dur = mp4Demux->getDuration(i); + GstStructure *drm = mp4Demux->getDrmMetadata(i); + gpointer data = g_malloc(sampleLen); + if( data ) + { + memcpy( data, mp4Demux->getPtr(i), sampleLen ); + GstBuffer *gstBuffer = gst_buffer_new_wrapped( data, sampleLen); + GST_BUFFER_PTS(gstBuffer) = (GstClockTime)(pts * GST_SECOND); + GST_BUFFER_DTS(gstBuffer) = (GstClockTime)(dts * GST_SECOND); + GST_BUFFER_DURATION(gstBuffer) = (GstClockTime)(dur * 1000000000LL); + if (drm) + { + gst_buffer_add_protection_meta(gstBuffer, drm); + } + GstFlowReturn ret = gst_app_src_push_buffer(GST_APP_SRC(stream->source),gstBuffer); + if( ret == GST_FLOW_OK ) + { + stream->bufferUnderrun = false; + if( isFirstBuffer ) + { + firstBufferPushed = true; + stream->firstBufferProcessed = true; + } + } + } + } + } + else + { // init header + mp4Demux->setCaps( GST_APP_SRC(stream->source) ); + } + if( !copy ) + { + g_free((gpointer)ptr); + } + } + else + { + GstFlowReturn ret = gst_app_src_push_buffer(GST_APP_SRC(stream->source), buffer); + + if (ret != GST_FLOW_OK) + { + MW_LOG_ERR("gst_app_src_push_buffer error: %d[%s] mediaType %d", ret, gst_flow_get_name (ret), (int)mediaType); + if (ret != GST_FLOW_EOS && ret != GST_FLOW_FLUSHING) + { // an unexpected error has occurred + if (mediaType == eGST_MEDIATYPE_SUBTITLE) + { // occurs sometimes when injecting subtitle fragments + if (!stream->source) + { + MW_LOG_ERR("subtitle appsrc is NULL"); + } + else if (!GST_IS_APP_SRC(stream->source)) + { + MW_LOG_ERR("subtitle appsrc is invalid"); + } + } + else + { // if we get here, something has gone terribly wrong + assert(0); + } + } + } + else if (stream->bufferUnderrun) + { + stream->bufferUnderrun = false; + } + + // PROFILE_BUCKET_FIRST_BUFFER after successful push of first gst buffer + if (isFirstBuffer == true && ret == GST_FLOW_OK) + firstBufferPushed = true; + if (!stream->firstBufferProcessed && !initFragment) + { + stream->firstBufferProcessed = true; + } + } + } + } + discontinuity = isFirstBuffer || discontinuity; + pthread_mutex_unlock(&stream->sourceLock); + if (isFirstBuffer) + { + if (!interfacePlayerPriv->gstPrivateContext->using_westerossink && !interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + notifyFirstBufferProcessed = true; + } + resetTrickUTC = interfacePlayerPriv->socInterface->ResetTrickUTC(); + } + if (eGST_MEDIATYPE_VIDEO == mediaType) + { + interfacePlayerPriv->gstPrivateContext->numberOfVideoBuffersSent++; + } + return bPushBuffer; +} + +void InterfacePlayerRDK::PauseInjector() +{ + std::unique_lock lock(mSourceSetupMutex); + mPauseInjector = true; +} + +void InterfacePlayerRDK::ResumeInjector() +{ + std::unique_lock lock(mSourceSetupMutex); + mPauseInjector = false; + mSourceSetupCV.notify_all(); +} + +/** + * @brief Send new segment event to pipeline + */ +void InterfacePlayerPriv::SendNewSegmentEvent(int type, GstClockTime startPts ,GstClockTime stopPts) +{ + GstMediaType mediaType = static_cast(type); + gst_media_stream* stream = &gstPrivateContext->stream[mediaType]; + if (stream->format == GST_FORMAT_ISO_BMFF) + { + GstSegment segment; + gst_segment_init(&segment, GST_FORMAT_TIME); + + segment.start = startPts; + segment.position = 0; + segment.rate = GST_NORMAL_PLAY_RATE; + segment.applied_rate = GST_NORMAL_PLAY_RATE; + + if(stopPts) + { + segment.stop = stopPts; + } + + if( (GstMediaType)mediaType == eGST_MEDIATYPE_VIDEO ) + { + bool isVideoMaster = socInterface->IsVideoMaster(gstPrivateContext->video_sink); + if( !isVideoMaster ) + { + // set applied_rate to trickplay rate if video sink doesn't use vmaster + // so that it can correctly handle there being no audio + segment.applied_rate = gstPrivateContext->rate; + } + } + + if (gstPrivateContext->usingRialtoSink) + { + GstCaps *currentCaps = gst_app_src_get_caps(GST_APP_SRC(stream->source)); + GstSample *sample = gst_sample_new (nullptr, currentCaps, &segment, nullptr); + + MW_LOG_INFO("Pushing sample with segment for mediaType[%d]. start %" G_GUINT64_FORMAT " stop %" G_GUINT64_FORMAT" rate %f applied_rate %f", mediaType, segment.start, segment.stop, segment.rate, segment.applied_rate); + if (GST_FLOW_OK != gst_app_src_push_sample(GST_APP_SRC(stream->source), sample)) + { + MW_LOG_ERR("Failed to push sample with segment for mediaType[%d]", mediaType); + } + gst_sample_unref(sample); + gst_caps_unref(currentCaps); + } + else + { + MW_LOG_INFO("Sending segment event for mediaType[%d]. start %" G_GUINT64_FORMAT " stop %" G_GUINT64_FORMAT" rate %f applied_rate %f", mediaType, segment.start, segment.stop, segment.rate, segment.applied_rate); + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(stream->source), "src"); + GstEvent* event = gst_event_new_segment (&segment); + if (!gst_pad_push_event(sourceEleSrcPad, event)) + { + MW_LOG_ERR("Failed to push segment event for mediaType[%d]", mediaType); + } + gst_object_unref(sourceEleSrcPad); + + } + } +} + +/** + * @brief Generate a protection event + */ +void InterfacePlayerRDK::QueueProtectionEvent(const std::string& formatType, const char *protSystemId, const void *initData, size_t initDataSize, int mediaType) +{ + /* There is a possibility that only single protection event is queued for multiple type since they are encrypted using same id. + * Don't worry if you see only one protection event queued here. + */ + GstMediaType type = static_cast(mediaType); + pthread_mutex_lock(&mProtectionLock); + if (interfacePlayerPriv->gstPrivateContext->protectionEvent[type] != NULL) + { + MW_LOG_MIL("Previously cached protection event is present for type(%d), clearing!", type); + gst_event_unref(interfacePlayerPriv->gstPrivateContext->protectionEvent[type]); + interfacePlayerPriv->gstPrivateContext->protectionEvent[type] = NULL; + } + pthread_mutex_unlock(&mProtectionLock); + + MW_LOG_MIL("Queueing protection event for type(%d) keysystem(%s) initData(%p) initDataSize(%zu)", type, protSystemId, initData, initDataSize); + + /* Giving invalid initData into ProtectionEvent causing "GStreamer-CRITICAL" assertion error. So if the initData is valid then its good to call the ProtectionEvent further. */ + if (initData && initDataSize) + { + GstBuffer *pssi; + + pssi = gst_buffer_new_wrapped(PLAYER_G_MEMDUP (initData, initDataSize), (gsize)initDataSize); + pthread_mutex_lock(&mProtectionLock); + interfacePlayerPriv->gstPrivateContext->protectionEvent[type] = gst_event_new_protection (protSystemId, pssi, formatType.c_str()); + pthread_mutex_unlock(&mProtectionLock); + + gst_buffer_unref (pssi); + } +} + +/** + * @brief Cleanup generated protection event + */ +void InterfacePlayerRDK::ClearProtectionEvent() +{ + pthread_mutex_lock(&mProtectionLock); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + if(interfacePlayerPriv->gstPrivateContext->protectionEvent[i]) + { + MW_LOG_MIL("removing protection event! "); + gst_event_unref (interfacePlayerPriv->gstPrivateContext->protectionEvent[i]); + interfacePlayerPriv->gstPrivateContext->protectionEvent[i] = NULL; + } + } + pthread_mutex_unlock(&mProtectionLock); +} +/** + * @brief Validate pipeline state transition within a max timeout + * @param[in] _this pointer to InterfacePlayerRDK instance + * @param[in] stateToValidate state to be validated + * @param[in] msTimeOut max timeout in MS + * @retval Current pipeline state + */ +static GstState validateStateWithMsTimeout( InterfacePlayerRDK *pInterfacePlayerRDK, GstState stateToValidate, guint msTimeOut) +{ + GstState gst_current; + GstState gst_pending; + float timeout = 100.0; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + gint gstGetStateCnt = GST_ELEMENT_GET_STATE_RETRY_CNT_MAX; + + do + { + if ((GST_STATE_CHANGE_SUCCESS + == gst_element_get_state(privatePlayer->gstPrivateContext->pipeline, &gst_current, &gst_pending, timeout * GST_MSECOND)) + && (gst_current == stateToValidate)) + { + GST_WARNING( + "validateStateWithMsTimeout - PIPELINE gst_element_get_state - SUCCESS : State = %d, Pending = %d", + gst_current, gst_pending); + return gst_current; + } + g_usleep (msTimeOut * 1000); // Let pipeline safely transition to required state + } + while ((gst_current != stateToValidate) && (gstGetStateCnt-- != 0)); + + MW_LOG_ERR("validateStateWithMsTimeout - PIPELINE gst_element_get_state - FAILURE : State = %d, Pending = %d", + gst_current, gst_pending); + return gst_current; +} + +/** + * @brief To pause/play pipeline + */ +bool InterfacePlayerRDK::Pause(bool pause , bool forceStopGstreamerPreBuffering) +{ + bool retValue = true; + if (interfacePlayerPriv->gstPrivateContext->pipeline != NULL) + { + GstState nextState = pause ? GST_STATE_PAUSED : GST_STATE_PLAYING; + interfacePlayerPriv->gstPrivateContext->buffering_target_state = nextState; + + if (GST_STATE_PAUSED == nextState && forceStopGstreamerPreBuffering) + { + /* maybe in a timing case during the playback start, + * gstreamer pre buffering and underflow buffering runs simultaneously and + * it will end up pausing the pipeline due to buffering_target_state has the value as GST_STATE_PAUSED. + * To avoid this case, stopping the gstreamer pre buffering logic by setting the buffering_in_progress to false + * and the resume play will be handled from StopBuffering once after getting enough buffer/frames. + */ + interfacePlayerPriv->gstPrivateContext->buffering_in_progress = false; + } + + GstStateChangeReturn rc = SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, nextState); + if (GST_STATE_CHANGE_ASYNC == rc) + { + /* CID:330433 Waiting while holding lock. Sleep introduced in validateStateWithMsTimeout to prevent continuous polling when synchronizing pipeline state. + * Too risky to remove mutex lock. It may be replaced if approach is redesigned in future */ + /* wait a bit longer for the state change to conclude */ + if (nextState != validateStateWithMsTimeout(this,nextState, 100)) + { + MW_LOG_ERR("InterfacePlayerRDK_Pause - validateStateWithMsTimeout - FAILED GstState %d", nextState); + } + } + else if (GST_STATE_CHANGE_SUCCESS != rc) + { + MW_LOG_ERR("InterfacePlayerRDK_Pause - gst_element_set_state - FAILED rc %d", rc); + } + + interfacePlayerPriv->gstPrivateContext->buffering_target_state = nextState; + interfacePlayerPriv->gstPrivateContext->paused = pause; + interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; + } + else + { + MW_LOG_WARN("Pipeline is NULL"); + retValue = false; + } + return retValue; +} + +/** + * @brief Check if PTS is changing + * @retval true if PTS changed from lastKnown PTS or timeout hasn't expired, will optimistically return true if video-pts attribute is not available from decoder + */ +bool InterfacePlayerRDK::CheckForPTSChangeWithTimeout(long timeout) +{ + bool ret = true; + gint64 currentPTS = GetVideoPTS(); /* Gets the currentPTS from the 'video-pts' property of the element */ + if (currentPTS != 0) + { + if (currentPTS != interfacePlayerPriv->gstPrivateContext->lastKnownPTS) + { + MW_LOG_MIL("InterfacePlayerRDK: There is an update in PTS prevPTS:%" G_GINT64_FORMAT " newPTS: %" G_GINT64_FORMAT , + interfacePlayerPriv->gstPrivateContext->lastKnownPTS, currentPTS); + interfacePlayerPriv->gstPrivateContext->ptsUpdatedTimeMS = NOW_STEADY_TS_MS; /* save a copy of the current steady clock in milliseconds */ + interfacePlayerPriv->gstPrivateContext->lastKnownPTS = currentPTS; + } + else + { + long diff = NOW_STEADY_TS_MS - interfacePlayerPriv->gstPrivateContext->ptsUpdatedTimeMS; + if (diff > timeout) + { + MW_LOG_WARN("InterfacePlayerRDK: Video PTS hasn't been updated for %ld ms and timeout - %ld ms", diff, timeout); + ret = false; + } + } + } + else + { + MW_LOG_MIL("InterfacePlayerRDK: video-pts parsed is: %" G_GINT64_FORMAT , + currentPTS); + } + return ret; +} +/** + * @brief Check if cache empty for a media type + */ +bool InterfacePlayerRDK::IsCacheEmpty(int Type) +{ + GstMediaType mediaType = (GstMediaType)Type; + bool ret = true; + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[mediaType]; + if (stream->source) + { + guint64 cacheLevel = gst_app_src_get_current_level_bytes (GST_APP_SRC(stream->source)); /*Get the number of currently queued bytes inside stream->source)*/ + if(0 != cacheLevel) + { + MW_LOG_TRACE("InterfacePlayerRDK::Cache level %" G_GUINT64_FORMAT "", cacheLevel); + ret = false; + } + else + { + // Changed to MW_LOG_TRACE, to avoid log flooding + // We're seeing this logged frequently during live linear playback, despite no user-facing problem. + MW_LOG_TRACE("InterfacePlayerRDK::Cache level empty"); + if (interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun == true || + interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun == true) /* Interpret bufferUnderun as cachelevel being empty */ + { + MW_LOG_WARN("InterfacePlayerRDK::Received buffer underrun signal for video(%d) or audio(%d) previously",interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun, + interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun); + } + else + { + bool ptsChanged = CheckForPTSChangeWithTimeout(GST_MIN_PTS_UPDATE_INTERVAL); + if(!ptsChanged) + { + //PTS hasn't changed for the timeout value + MW_LOG_WARN("InterfacePlayerRDK: Appsrc cache is empty and PTS hasn't been updated for more than %dms and ret(%d)", + GST_MIN_PTS_UPDATE_INTERVAL, ret); + } + else + { + ret = false; /* Pts changing, conclude that cache is not empty */ + } + } + } + } + return ret; +} + +/** + * @brief Reset EOS SignalledFlag + */ +void InterfacePlayerRDK::ResetEOSSignalledFlag() +{ + interfacePlayerPriv->gstPrivateContext->eosSignalled = false; +} + +/** + * @brief Checks to see if the pipeline is configured for specified media type + */ +bool InterfacePlayerRDK::PipelineConfiguredForMedia(int type) +{ + bool pipelineConfigured = true; + + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[type]; + if (stream) + { + pipelineConfigured = stream->sourceConfigured; + } + return pipelineConfigured; +} + +bool InterfacePlayerRDK::GetBufferControlData(int iMediaType) +{ + bool GstWaitingForData = false; + GstState current; + GstState pending; + GstMediaType mediaType = (GstMediaType)iMediaType; + const gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[mediaType]; + gst_element_get_state(stream->sinkbin, ¤t, &pending, 0); + + /* Transitions to Paused can block due to lack of data + ** state should match player's target play/pause state*/ + bool pipelineShouldBePlaying = !interfacePlayerPriv->gstPrivateContext->paused; + GstWaitingForData = ((pending == GST_STATE_PAUSED) || + (pipelineShouldBePlaying && (current != GST_STATE_PLAYING))); + if (GstWaitingForData) + { + MW_LOG_WARN("BufferControlExternalData %s GStreamer (current %s, %s, should be %s))", + gstGetMediaTypeName(mediaType), gst_element_state_get_name(current), + gst_element_state_get_name(pending), + pipelineShouldBePlaying ? "GST_STATE_PLAYING" : "GST_STATE_PAUSED"); + } + return GstWaitingForData; +} +bool InterfacePlayerRDK::IsStreamReady(int mediaType) +{ + bool StreamReady = false; + + const gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[mediaType]; + + StreamReady = stream->sinkbin && stream->sourceConfigured; + return StreamReady; +} +/** + * @brief Signal trick mode discontinuity to gstreamer pipeline + */ +void InterfacePlayerRDK::SignalTrickModeDiscontinuity() +{ + + gst_media_stream* stream = &interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_VIDEO]; + if (stream && (interfacePlayerPriv->gstPrivateContext->rate != GST_NORMAL_PLAY_RATE) ) + { + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(stream->source), "src"); + int vodTrickplayFPS = m_gstConfigParam->vodTrickModeFPS; + GstStructure * eventStruct = gst_structure_new("aamp-tm-disc", "fps", G_TYPE_UINT, (guint)vodTrickplayFPS, NULL); + if (!gst_pad_push_event(sourceEleSrcPad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, eventStruct))) + { + MW_LOG_WARN("Error on sending player tm-disc"); + } + else + { + MW_LOG_MIL("Sent player tm-disc event"); + } + gst_object_unref(sourceEleSrcPad); + } +} + +void InterfacePlayerRDK::EnableGstDebugLogging(std::string debugLevel) +{ + if (!debugLevel.empty()) + { + gst_debug_set_threshold_from_string(debugLevel.c_str(), 1); + } +} + +/** + * @brief Process discontinuity for a stream type + */ +bool InterfacePlayerRDK::CheckDiscontinuity(int mediaType, int streamFormat , bool codecChange, bool &unblockDiscProcess, bool &shouldHaltBuffering) +{ + bool ret = false; + GstMediaType type = (GstMediaType)mediaType; + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[type]; + + GstStreamOutputFormat Format = (GstStreamOutputFormat)streamFormat; + MW_LOG_MIL("Entering InterfacePlayerRDK: type(%d) format(%d) firstBufferProcessed(%d)", (int)type, stream->format, stream->firstBufferProcessed); + + /*Handle discontinuity only if at least one buffer is pushed*/ + if (stream->format != GST_FORMAT_INVALID && stream->firstBufferProcessed == false) + { + MW_LOG_WARN("Discontinuity received before first buffer - ignoring"); + } + else + { + MW_LOG_DEBUG("stream->format %d, stream->firstBufferProcessed %d", stream->format , stream->firstBufferProcessed); + if(m_gstConfigParam->enablePTSReStamp && (Format == GST_FORMAT_ISO_BMFF) && ( !codecChange )) + { + unblockDiscProcess = true; + ret = true; + } + else + { + if (m_gstConfigParam->enablePTSReStamp && codecChange) + { + MW_LOG_WARN("PTS-RESTAMP ENABLED, but we have codec change, so Signal EOS (%s).",gstGetMediaTypeName(type)); + } + GstPlayer_SignalEOS(stream); + // We are in buffering, but we received discontinuity, un-pause pipeline + shouldHaltBuffering = true; + ret = true; + + //If we have an audio discontinuity, signal subtec as well + if ((type == eGST_MEDIATYPE_AUDIO) && (interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].source)) + { + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE]); + } + } + } + return ret; +} + +/** + * @brief TimerAdd - add a new glib timer in thread safe manner + */ +void InterfacePlayerRDK::TimerAdd(GSourceFunc funcPtr, int repeatTimeout, guint& taskId, gpointer user_data, const char* timerName) +{ + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + if (funcPtr && user_data) + { + if (0 == taskId) + { + /* Sets the function pointed by functPtr to be called at regular intervals of repeatTimeout, supplying user_data to the function */ + taskId = g_timeout_add(repeatTimeout, funcPtr, user_data); + MW_LOG_INFO("InterfacePlayerRDK: Added timer '%.50s', %d", (nullptr!=timerName) ? timerName : "unknown" , taskId); + } + else + { + MW_LOG_INFO("InterfacePlayerRDK: Timer '%.50s' already added, taskId=%d", (nullptr!=timerName) ? timerName : "unknown", taskId); + } + } + else + { + MW_LOG_ERR("Bad pointer. funcPtr = %p, user_data=%p",funcPtr,user_data); + } +} + +/* + * @brief TimerIsRunning - Check whether timer is currently running + */ +bool InterfacePlayerRDK::TimerIsRunning(guint& taskId) +{ + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + + return (PLAYER_TASK_ID_INVALID != taskId); +} + +/** + * @brief IdleTaskClearFlags - clear async task id and pending flag in a thread safe manner + * e.g. called when the task executes + */ +void InterfacePlayerRDK::IdleTaskClearFlags(GstTaskControlData& taskDetails) +{ + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + if ( 0 != taskDetails.taskID ) + { + MW_LOG_INFO("InterfacePlayerRDK: Clear task control flags <%.50s> with ID %d", taskDetails.taskName.c_str(), taskDetails.taskID); + } + else + { + MW_LOG_TRACE("InterfacePlayerRDK: Task control flags were already cleared <%.50s> with ID %d", taskDetails.taskName.c_str(), taskDetails.taskID); + } + taskDetails.taskIsPending = false; + taskDetails.taskID = 0; +} + +/** + * @brief IdleTaskAdd - add an async/idle task in a thread safe manner, assuming it is not queued + */ +bool InterfacePlayerRDK::IdleTaskAdd(GstTaskControlData& taskDetails, BackgroundTask funcPtr) +{ + bool ret = false; + std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->TaskControlMutex); + + if (0 == taskDetails.taskID) + { + taskDetails.taskIsPending = false; + taskDetails.taskID = mScheduler.ScheduleTask(PlayerAsyncTaskObj(funcPtr, (void *)this)); + // Wait for scheduler response , if failed to create task for wrong state , not to make pending flag as true + if(0 != taskDetails.taskID) + { + taskDetails.taskIsPending = true; + ret = true; + MW_LOG_INFO("Task '%.50s' was added with ID = %d.", taskDetails.taskName.c_str(), taskDetails.taskID); + } + else + { + MW_LOG_INFO("Task '%.50s' was not added or already ran.", taskDetails.taskName.c_str()); + } + } + else + { + MW_LOG_WARN("Task '%.50s' was already pending.", taskDetails.taskName.c_str()); + } + return ret; +} + + +void InterfacePlayerRDK::FirstFrameCallback(std::function callback) +{ + notifyFirstFrameCallback = std::move(callback); +} + +void InterfacePlayerRDK::StopCallback(std::function callback) +{ + stopCallback = std::move(callback); +} +void InterfacePlayerRDK::TearDownCallback(std::function callback) +{ + tearDownCb = std::move(callback); +} + +/** + * @brief Notify first Audio and Video frame through an idle function + */ +void InterfacePlayerRDK::NotifyFirstFrame(int mediaType) +{ + bool notifyFirstBuffer = false; + bool audioOnly = false; + bool requireFirstVideoFrameDisplay = false; + if (!interfacePlayerPriv->gstPrivateContext->firstFrameReceived && (interfacePlayerPriv->gstPrivateContext->firstVideoFrameReceived + || (1 == interfacePlayerPriv->gstPrivateContext->NumberOfTracks && (interfacePlayerPriv->gstPrivateContext->firstAudioFrameReceived || interfacePlayerPriv->gstPrivateContext->firstVideoFrameReceived)))) + { + interfacePlayerPriv->gstPrivateContext->firstFrameReceived = true; + notifyFirstBuffer = true; + PlayerLogManager::setLogLevel(mLOGLEVEL_WARN); //Align with player LogTuneComplete once the first frame starts, required for prod builds + } + if(notifyFirstFrameCallback) + { + notifyFirstFrameCallback(mediaType, notifyFirstBuffer, (!interfacePlayerPriv->gstPrivateContext->decoderHandleNotified && PipelineSetToReady), requireFirstVideoFrameDisplay, audioOnly); + } + + if (eGST_MEDIATYPE_VIDEO == mediaType) + { + MW_LOG_MIL("OnFirstVideoFrame. got First Video Frame"); + + if (!interfacePlayerPriv->gstPrivateContext->decoderHandleNotified) + { + interfacePlayerPriv->gstPrivateContext->decoderHandleNotified = true; + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending = false; + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId = mScheduler.ScheduleTask(PlayerAsyncTaskObj(IdleCallbackOnFirstFrame, (void *)this, "FirstFrameCallback")); + // Wait for scheduler response , if failed to create task for wrong state , not to make pending flag as true + if(interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId != PLAYER_TASK_ID_INVALID) + { + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending = true; + } + } + + IdleTaskAdd(interfacePlayerPriv->gstPrivateContext->firstProgressCallbackIdleTask, IdleCallback); + + if (requireFirstVideoFrameDisplay) + { + if ( !IdleTaskAdd(interfacePlayerPriv->gstPrivateContext->firstVideoFrameDisplayedCallbackTask, IdleCallbackFirstVideoFrameDisplayed)) + { + MW_LOG_WARN("IdleCallbackFirstVideoFrameDisplayed was not added."); + } + } + PipelineSetToReady = false; + } + else if (eGST_MEDIATYPE_AUDIO == mediaType) + { + MW_LOG_MIL("OnFirstAudioFrame. got First Audio Frame"); + if (audioOnly) + { + if (!interfacePlayerPriv->gstPrivateContext->decoderHandleNotified) + { + interfacePlayerPriv->gstPrivateContext->decoderHandleNotified = true; + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending = false; + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId = mScheduler.ScheduleTask(PlayerAsyncTaskObj(IdleCallbackOnFirstFrame, (void *)this, "FirstFrameCallback")); + // Wait for scheduler response , if failed to create task for wrong state , not to make pending flag as true + if(interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskId != PLAYER_TASK_ID_INVALID) + { + interfacePlayerPriv->gstPrivateContext->firstFrameCallbackIdleTaskPending = true; + } + } + IdleTaskAdd(interfacePlayerPriv->gstPrivateContext->firstProgressCallbackIdleTask, IdleCallback); + } + } + +} + +void InterfacePlayerRDK::TriggerEvent(InterfaceCB event) +{ + auto it = callbackMap.find(event); + if (it != callbackMap.end()) + { + it->second(); + } + else + { + MW_LOG_ERR("Unknown event - No callback registered!"); + } +} +void InterfacePlayerRDK::TriggerEvent(InterfaceCB event, int data) +{ + auto it = setupStreamCallbackMap.find(event); + if (it != setupStreamCallbackMap.end()) + { + it->second(data); + } + else + { + MW_LOG_ERR("Unknown event - No callback registered!"); + } +} +/* @brief Check if string start with a prefix + * + * @retval TRUE if substring is found in bigstring + */ +bool gst_StartsWith( const char *inputStr, const char *prefix ) +{ + bool rc = true; + while( *prefix ) + { + if( *inputStr++ != *prefix++ ) + { + rc = false; + break; + } + } + return rc; +} + +/** + * @brief Check if gstreamer element is audio decoder + * @param[in] name Name of the element + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval TRUE if element name is that of audio or video decoder + */ +bool GstPlayer_isVideoOrAudioDecoder(const char *name, InterfacePlayerRDK *pInterfacePlayerRDK) +{ + // The idea is to identify video or audio decoder plugin created at runtime by playbin and register to its first-frame/pts-error callbacks + // This support is available in plugins in RDK builds and hence checking only for such plugin instances here + // For platforms that doesnt support callback, we use GST_STATE_PLAYING state change of playbin to notify first frame to app + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + return privatePlayer->socInterface->IsAudioOrVideoDecoder(name); +} + +/** + * @brief Check if gstreamer element is video decoder + * @param[in] name Name of the element + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval TRUE if element name is that of the decoder + */ +bool GstPlayer_isVideoDecoder(const char* name, InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + return privatePlayer->socInterface->IsVideoDecoder(name); +} + +/** + * @brief GstPlayer_HandleInstantRateChangeSeekProbe + * @param[in] pad pad element + * @param[in] info Pad information + * @param[in] data pointer to data + */ +static GstPadProbeReturn GstPlayer_HandleInstantRateChangeSeekProbe(GstPad* pad, GstPadProbeInfo* info, gpointer data) +{ + GstEvent *event = GST_PAD_PROBE_INFO_EVENT(info); + GstSegment *segment = reinterpret_cast(data); + + switch ( GST_EVENT_TYPE(event) ) + { + case GST_EVENT_SEEK: + break; + case GST_EVENT_SEGMENT: + gst_event_copy_segment(event, segment); //intentional fall through as the variable segment is used to persist data + default: + MW_LOG_INFO("In default case of GST_EVENT_TYPE in padprobeReturn"); + return GST_PAD_PROBE_OK; + }; + + gdouble rate = 1.0; + GstSeekFlags flags = GST_SEEK_FLAG_NONE; + gst_event_parse_seek (event, &rate, nullptr, &flags, nullptr, nullptr, nullptr, nullptr); + MW_LOG_TRACE("rate %f segment->rate %f segment->format %d %d", rate, segment->rate, segment->format, GST_FORMAT_TIME); + + if (!!(flags & GST_SEEK_FLAG_INSTANT_RATE_CHANGE)) + { + gdouble rateMultiplier = rate / segment->rate; + GstEvent *rateChangeEvent = + gst_event_new_instant_rate_change(rateMultiplier, static_cast(flags)); + + gst_event_set_seqnum (rateChangeEvent, gst_event_get_seqnum (event)); + GstPad *peerPad = gst_pad_get_peer(pad); + + if ( gst_pad_send_event (peerPad, rateChangeEvent) != TRUE ) + GST_PAD_PROBE_INFO_FLOW_RETURN(info) = GST_FLOW_NOT_SUPPORTED; + + gst_object_unref(peerPad); + gst_event_unref(event); + return GST_PAD_PROBE_HANDLED; + } + return GST_PAD_PROBE_OK; +} + +/** + * @brief Check if gstreamer element is video sink + * @param[in] name Name of the element + * @param[in] pInterfacePlayerRDK pointer to INterfacePlayerRDK instance + * @retval TRUE if element name is that of video sink + */ +bool GstPlayer_isVideoSink(const char* name, InterfacePlayerRDK* pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + return privatePlayer->socInterface->IsVideoSink(name); +} + +/** + * @brief Creates a GStreamer pipeline. + + * @param pipelineName Name of the pipeline. + * @param PipelinePriority Priority of the pipeline. + + * @return True if the pipeline is created successfully, false otherwise. + */ +bool InterfacePlayerRDK::CreatePipeline(const char *pipelineName, int PipelinePriority) +{ + bool ret = false; + /* Destroy any existing pipeline before creating a new one */ + if (interfacePlayerPriv->gstPrivateContext->pipeline || interfacePlayerPriv->gstPrivateContext->bus) + { + DestroyPipeline(); + } + MW_LOG_MIL("Creating gstreamer pipeline %s priority %d", pipelineName, PipelinePriority); + interfacePlayerPriv->gstPrivateContext->pipeline = gst_pipeline_new(pipelineName); //get it from app + + if (interfacePlayerPriv->gstPrivateContext->pipeline) + { + interfacePlayerPriv->gstPrivateContext->bus = gst_pipeline_get_bus(GST_PIPELINE(interfacePlayerPriv->gstPrivateContext->pipeline)); + if(PipelinePriority >= 0) + { + interfacePlayerPriv->gstPrivateContext->task_pool = (GstTaskPool*)g_object_new (GST_TYPE_PLAYER_TASKPOOL, NULL); + } + if(interfacePlayerPriv->gstPrivateContext->bus) + { + interfacePlayerPriv->gstPrivateContext->aSyncControl.enable(); + guint busWatchId = gst_bus_add_watch(interfacePlayerPriv->gstPrivateContext->bus, (GstBusFunc) bus_message, this); + (void)busWatchId; + interfacePlayerPriv->gstPrivateContext->syncControl.enable(); + gst_bus_set_sync_handler(interfacePlayerPriv->gstPrivateContext->bus, (GstBusSyncHandler) bus_sync_handler, this, NULL); + interfacePlayerPriv->gstPrivateContext->buffering_enabled = m_gstConfigParam->gstreamerBufferingBeforePlay; + interfacePlayerPriv->gstPrivateContext->buffering_in_progress = false; + interfacePlayerPriv->gstPrivateContext->buffering_timeout_cnt = DEFAULT_BUFFERING_MAX_CNT; + interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_NULL; + MW_LOG_MIL("%s buffering_enabled %u", GST_ELEMENT_NAME(interfacePlayerPriv->gstPrivateContext->pipeline), interfacePlayerPriv->gstPrivateContext->buffering_enabled); + if (interfacePlayerPriv->gstPrivateContext->positionQuery == NULL) + { + + interfacePlayerPriv->gstPrivateContext->positionQuery = gst_query_new_position(GST_FORMAT_TIME); + /* Construct a new position query that will used to query the 'current playback position' when needed. + The time base specified is in nanoseconds */ + } + /* Use to enable the timing synchronization with gstreamer */ + interfacePlayerPriv->gstPrivateContext->enableSEITimeCode = m_gstConfigParam->seiTimeCode; + ret = true; + } + else + { + MW_LOG_ERR("InterfacePlayerRDK - gst_pipeline_get_bus failed"); + } + } + else + { + MW_LOG_ERR("InterfacePlayerRDK - gst_pipeline_new failed"); + } + return ret; +} + +/** + * @brief Gets Video PTS + */ +long long InterfacePlayerRDK::GetVideoPTS(void) +{ + gint64 currentPTS = 0; + currentPTS = interfacePlayerPriv->socInterface->GetVideoPts(interfacePlayerPriv->gstPrivateContext->video_sink, interfacePlayerPriv->gstPrivateContext->video_dec, interfacePlayerPriv->gstPrivateContext->using_westerossink); + return (long long)currentPTS; +} + +/** + * @brief Notifies EOS if video decoder pts is stalled + * @param[in] user_data pointer to InterfacePlayerRDK instance + * @retval G_SOURCE_REMOVE, if the source should be removed + */ +static gboolean VideoDecoderPtsCheckerForEOS(gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *) user_data; + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + gint64 currentPTS = pInterfacePlayerRDK->GetVideoPTS(); /* Gets the currentPTS from the 'video-pts' property of the element */ + + if (currentPTS == privatePlayer->gstPrivateContext->lastKnownPTS) + { + MW_LOG_MIL("PTS not changed"); + pInterfacePlayerRDK->NotifyEOS(); /* Notify EOS if the PTS has not changed */ + } + else + { + MW_LOG_MIL("Video PTS still moving lastKnownPTS %" G_GUINT64_FORMAT " currentPTS %" G_GUINT64_FORMAT " ##", privatePlayer->gstPrivateContext->lastKnownPTS, currentPTS); + } + privatePlayer->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId = GST_TASK_ID_INVALID; + return G_SOURCE_REMOVE; +} + +/** + * @brief Check if gstreamer element is audio sink or audio decoder + * @param[in] name Name of the element + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval TRUE if element name is that of audio sink or audio decoder + */ +bool GstPlayer_isAudioSinkOrAudioDecoder(const char* name, InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + return privatePlayer->socInterface->IsAudioSinkOrAudioDecoder(name); +} + + +/** + * @brief Callback invoked when facing an underflow + * @param[in] object pointer to element raising the callback + * @param[in] arg0 number of arguments + * @param[in] arg1 array of arguments + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + */ +static void GstPlayer_OnGstBufferUnderflowCb(GstElement* object, guint arg0, gpointer arg1, + InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + if (pInterfacePlayerRDK->m_gstConfigParam->disableUnderflow) + { // optionally ignore underflow + MW_LOG_WARN("## [WARN] Ignored underflow, disableUnderflow config enabled ##"); + } + else + { + //TODO - Handle underflow + GstMediaType type = eGST_MEDIATYPE_DEFAULT; //CID:89173 - Resolve Uninit + bool isVideo = false; + + if (privatePlayer->socInterface->IsVideoSinkHandleErrors()) + { + isVideo = GstPlayer_isVideoSink(GST_ELEMENT_NAME(object), pInterfacePlayerRDK); + } + else + { + isVideo = GstPlayer_isVideoDecoder(GST_ELEMENT_NAME(object), pInterfacePlayerRDK); + } + + if (isVideo) + { + type = eGST_MEDIATYPE_VIDEO; + } + else if (GstPlayer_isAudioSinkOrAudioDecoder(GST_ELEMENT_NAME(object), pInterfacePlayerRDK)) + { + type = eGST_MEDIATYPE_AUDIO; + } + else + { + MW_LOG_WARN("## WARNING!! Underflow message from %s not handled, unmapped underflow!", GST_ELEMENT_NAME(object)); + return; + } + + MW_LOG_WARN("## Got Underflow message from %s type %d ##", GST_ELEMENT_NAME(object), type); + privatePlayer->gstPrivateContext->stream[type].bufferUnderrun = true; + + if ((privatePlayer->gstPrivateContext->stream[type].eosReached) && (privatePlayer->gstPrivateContext->rate > 0)) + { + if (!privatePlayer->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId) + { + privatePlayer->gstPrivateContext->lastKnownPTS = pInterfacePlayerRDK->GetVideoPTS(); /* Gets the currentPTS from the 'video-pts' property of the element */ + privatePlayer->gstPrivateContext->ptsUpdatedTimeMS = NOW_STEADY_TS_MS; + privatePlayer->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId = g_timeout_add(GST_DELAY_BETWEEN_PTS_CHECK_FOR_EOS_ON_UNDERFLOW, VideoDecoderPtsCheckerForEOS, pInterfacePlayerRDK); + /*g_timeout_add - Sets the function VideoDecoderPtsCheckerForEOS to be called at regular intervals*/ + } + else + { + MW_LOG_WARN("ptsCheckForEosOnUnderflowIdleTask ID %d already running, ignore underflow", (int)privatePlayer->gstPrivateContext->ptsCheckForEosOnUnderflowIdleTaskId); + } + } + else + { + MW_LOG_WARN("Mediatype %d underrun, when eosReached is %d", type, privatePlayer->gstPrivateContext->stream[type].eosReached); + } + if(pInterfacePlayerRDK->OnGstBufferUnderflowCb) + { + pInterfacePlayerRDK->OnGstBufferUnderflowCb(static_cast(type)); + } + else + { + MW_LOG_ERR("underflow callback not registered"); + } +#ifdef USE_EXTERNAL_STATS + INC_RETUNE_COUNT(type); // Increment the retune count for low level AV metric +#endif + } +} + +/** + * @brief Callback invoked a PTS error is encountered + * @param[in] object pointer to element raising the callback + * @param[in] arg0 number of arguments + * @param[in] arg1 array of arguments + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + */ +static void GstPlayer_OnGstPtsErrorCb(GstElement *object, guint arg0, gpointer arg1, + InterfacePlayerRDK *pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + MW_LOG_ERR("Got PTS error message from %s", GST_ELEMENT_NAME(object)); + bool isVideo = false; + bool isAudioSink = false; + if (privatePlayer->socInterface->IsVideoSinkHandleErrors()) + { + isVideo = GstPlayer_isVideoSink(GST_ELEMENT_NAME(object), pInterfacePlayerRDK); + } + else + { + isVideo = GstPlayer_isVideoDecoder(GST_ELEMENT_NAME(object), pInterfacePlayerRDK); + } + if (GstPlayer_isAudioSinkOrAudioDecoder(GST_ELEMENT_NAME(object), pInterfacePlayerRDK)) + { + isAudioSink = true; + } + pInterfacePlayerRDK->OnGstPtsErrorCb(isVideo, isAudioSink); +} + +/** + * @brief Callback invoked a Decode error is encountered + * @param[in] object pointer to element raising the callback + * @param[in] arg0 number of arguments + * @param[in] arg1 array of arguments + * @param[in] pInterfacePlayerRDK pointer to GstPlayer instance + */ +static void GstPlayer_OnGstDecodeErrorCb(GstElement* object, guint arg0, gpointer arg1, + InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + long long deltaMS = NOW_STEADY_TS_MS - privatePlayer->gstPrivateContext->decodeErrorMsgTimeMS; + privatePlayer->gstPrivateContext->decodeErrorCBCount += 1; + if (deltaMS >= GST_MIN_DECODE_ERROR_INTERVAL) + { + pInterfacePlayerRDK->OnGstDecodeErrorCb(privatePlayer->gstPrivateContext->decodeErrorCBCount); + privatePlayer->gstPrivateContext->decodeErrorMsgTimeMS = NOW_STEADY_TS_MS; + MW_LOG_ERR("Got Decode Error message from %s total_cb=%d timeMs=%d", GST_ELEMENT_NAME(object), privatePlayer->gstPrivateContext->decodeErrorCBCount, GST_MIN_DECODE_ERROR_INTERVAL); + privatePlayer->gstPrivateContext->decodeErrorCBCount = 0; +#ifdef USE_EXTERNAL_STATS + INC_DECODE_ERROR(); // Increment the decoder error for low level AV metric +#endif + + } +} + + +/** + * @brief Called from the mainloop when a message is available on the bus + * @param[in] bus the GstBus that sent the message + * @param[in] msg the GstMessage + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval FALSE if the event source should be removed. + */ +static gboolean bus_message(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER( privatePlayer->gstPrivateContext->aSyncControl, FALSE); + GError *error; + gchar *dbg_info; + bool isPlaybinStateChangeEvent; + BusEventData busEvent; + busEvent.firstBufferProcessed = false; + busEvent.setPlaybackRate = false; + busEvent.receivedFirstFrame = false; + switch (GST_MESSAGE_TYPE(msg)) + { + case GST_MESSAGE_ERROR: + gst_message_parse_error(msg, &error, &dbg_info); + MW_LOG_ERR("GST_MESSAGE_ERROR %s: %s\n", GST_OBJECT_NAME(msg->src), error->message); + busEvent.msgType = MESSAGE_ERROR; + busEvent.msg = error->message; + if(dbg_info) + { + busEvent.dbg_info = dbg_info; + } + else + { + busEvent.dbg_info[0] = '\0'; + } + pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); + MW_LOG_ERR("Debug Info: %s\n", (dbg_info) ? dbg_info : "none"); + g_clear_error(&error); + g_free(dbg_info); + break; + + case GST_MESSAGE_WARNING: + gst_message_parse_warning(msg, &error, &dbg_info); + MW_LOG_ERR("GST_MESSAGE_WARNING %s: %s\n", GST_OBJECT_NAME(msg->src), error->message); + busEvent.msgType = MESSAGE_WARNING; + busEvent.msg = error->message; + if(dbg_info) + { + busEvent.dbg_info = dbg_info; + } + else + { + busEvent.dbg_info[0] = '\0'; + } + pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); + MW_LOG_ERR("Debug Info: %s\n", (dbg_info) ? dbg_info : "none"); + g_clear_error(&error); + g_free(dbg_info); + break; + + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state, new_state, pending_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); + isPlaybinStateChangeEvent = (GST_MESSAGE_SRC(msg) == GST_OBJECT(privatePlayer->gstPrivateContext->pipeline)); + const gchar *srcName = GST_OBJECT_NAME(msg->src); + + busEvent.msg = srcName ? srcName : "Unknown source"; + busEvent.dbg_info = "N/A"; + busEvent.msgType = MESSAGE_STATE_CHANGE; + + if(isPlaybinStateChangeEvent || pInterfacePlayerRDK->m_gstConfigParam->gstLogging) + { + MW_LOG_MIL("%s %s -> %s (pending %s)", + GST_OBJECT_NAME(msg->src), + gst_element_state_get_name(old_state), + gst_element_state_get_name(new_state), + gst_element_state_get_name(pending_state)); + + if(isPlaybinStateChangeEvent && privatePlayer->gstPrivateContext->pauseOnStartPlayback && (new_state == GST_STATE_PAUSED)) + { + GstElement *video_sink = privatePlayer->gstPrivateContext->video_sink; + const char *frame_step_on_preroll_prop = "frame-step-on-preroll"; + privatePlayer->gstPrivateContext->pauseOnStartPlayback = false; + + if(video_sink && (g_object_class_find_property(G_OBJECT_GET_CLASS(video_sink), frame_step_on_preroll_prop) != NULL)) + { + MW_LOG_INFO("Setting %s property and sending step", frame_step_on_preroll_prop); + g_object_set(G_OBJECT(video_sink), frame_step_on_preroll_prop,1, NULL); + + if(!gst_element_send_event(video_sink, gst_event_new_step(GST_FORMAT_BUFFERS, 1, 1.0, FALSE, FALSE))) + { + MW_LOG_ERR("error sending step event"); + } + g_object_set(G_OBJECT(video_sink), frame_step_on_preroll_prop,0, NULL); + + if(privatePlayer->gstPrivateContext->usingRialtoSink) + { + privatePlayer->gstPrivateContext->firstVideoFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_VIDEO); + } + } + else + { + MW_LOG_WARN("%s property not present on video_sink", frame_step_on_preroll_prop); + privatePlayer->gstPrivateContext->firstVideoFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_VIDEO); + } + } + if(isPlaybinStateChangeEvent && new_state == GST_STATE_PLAYING) + { + privatePlayer->gstPrivateContext->pauseOnStartPlayback = false; + + busEvent.setPlaybackRate = privatePlayer->socInterface->SetPlatformPlaybackRate(); + if(pInterfacePlayerRDK->m_gstConfigParam->audioOnlyMode && !privatePlayer->gstPrivateContext->firstAudioFrameReceived && privatePlayer->gstPrivateContext->NumberOfTracks==1) + { + gst_media_stream *stream = &privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO]; + bool ret = privatePlayer->socInterface->AudioOnlyMode(stream->sinkbin); + if(ret) + { + MW_LOG_MIL("Audio only playback detected, hence notify first frame"); + privatePlayer->gstPrivateContext->firstAudioFrameReceived = ret; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_AUDIO); + } + } + if(pInterfacePlayerRDK->m_gstConfigParam->media == eGST_MEDIAFORMAT_PROGRESSIVE) + { + pInterfacePlayerRDK->IdleTaskAdd(privatePlayer->gstPrivateContext->firstProgressCallbackIdleTask, pInterfacePlayerRDK->IdleCallback); + // application needs to NotifyFirstBufferProcessed + busEvent.firstBufferProcessed = true; + + } + if(privatePlayer->gstPrivateContext->usingRialtoSink) + { + privatePlayer->gstPrivateContext->firstVideoFrameReceived = true; + privatePlayer->gstPrivateContext->firstAudioFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_VIDEO); + } + else if(privatePlayer->gstPrivateContext->firstTuneWithWesterosSinkOff && privatePlayer->socInterface->NotifyVideoFirstFrame()) + { + privatePlayer->gstPrivateContext->firstTuneWithWesterosSinkOff = false; + privatePlayer->gstPrivateContext->firstVideoFrameReceived = true; + privatePlayer->gstPrivateContext->firstAudioFrameReceived = true; + pInterfacePlayerRDK->NotifyFirstFrame(eGST_MEDIATYPE_VIDEO); + } + else if(privatePlayer->socInterface->IsSimulatorFirstFrame()) + { + if(!privatePlayer->gstPrivateContext->firstFrameReceived) + { + privatePlayer->gstPrivateContext->firstFrameReceived = true; + busEvent.receivedFirstFrame = true; + } + pInterfacePlayerRDK->TriggerEvent(InterfaceCB::firstVideoFrameReceived); + //Note: Progress event should be sent after the decoderAvailable event only. + //BRCM platform sends progress event after InterfacePlayerRDK_OnFirstVideoFrameCallback. + pInterfacePlayerRDK->IdleTaskAdd(privatePlayer->gstPrivateContext->firstProgressCallbackIdleTask, pInterfacePlayerRDK->IdleCallback); + } + if (pInterfacePlayerRDK->m_gstConfigParam->gstLogging) + { + GST_DEBUG_BIN_TO_DOT_FILE((GstBin *)privatePlayer->gstPrivateContext->pipeline, GST_DEBUG_GRAPH_SHOW_ALL, "myplayer"); + // output graph to .dot format which can be visualized with Graphviz tool if: + // gstreamer is configured with --gst-enable-gst-debug + // and "gst" is enabled in player cfg + // and environment variable GST_DEBUG_DUMP_DOT_DIR is set to a basepath(e.g. /opt). + } + // First Video Frame Displayed callback for westeros-sink is initialized + // via OnFirstVideoFrameCallback()->NotifyFirstFrame() which is more accurate + if((!privatePlayer->gstPrivateContext->using_westerossink) && pInterfacePlayerRDK->mFirstFrameRequired) + { + pInterfacePlayerRDK->IdleTaskAdd(privatePlayer->gstPrivateContext->firstVideoFrameDisplayedCallbackTask, pInterfacePlayerRDK->IdleCallbackFirstVideoFrameDisplayed); + } + + } + } + //this code should be handled as part of IARM modification + if ((NULL != msg->src) && GstPlayer_isVideoOrAudioDecoder(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { + // This is the video decoder, send this to the output protection module + // so it can get the source width/height + if (GstPlayer_isVideoDecoder(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { + if(PlayerExternalsInterface::IsPlayerExternalsInterfaceInstanceActive()) + { + std::shared_ptr pInstance = PlayerExternalsInterface::GetPlayerExternalsInterfaceInstance(); + pInstance->setGstElement((GstElement *)(msg->src)); + } + } + } + else if(NULL != msg->src) + { + if((old_state == GST_STATE_NULL && new_state == GST_STATE_READY)) + { + if(gst_StartsWith(GST_OBJECT_NAME(msg->src), "source")) + { + GstPad* sourceEleSrcPad = privatePlayer->socInterface->GetSourcePad(GST_ELEMENT(msg->src)); + if(sourceEleSrcPad) + { + gst_pad_add_probe ( + sourceEleSrcPad, + GST_PAD_PROBE_TYPE_EVENT_BOTH, + GstPlayer_HandleInstantRateChangeSeekProbe, + gst_segment_new(), + reinterpret_cast(gst_segment_free)); + gst_object_unref(sourceEleSrcPad); + } + } + } + + } + if((NULL != msg->src) && ((privatePlayer->socInterface->IsVideoSinkHandleErrors() && GstPlayer_isVideoSink(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) || (!privatePlayer->socInterface->IsVideoSinkHandleErrors() && GstPlayer_isVideoOrAudioDecoder(GST_OBJECT_NAME(msg->src),pInterfacePlayerRDK))) && (!privatePlayer->gstPrivateContext->usingRialtoSink)) + { + if (old_state == GST_STATE_NULL && new_state == GST_STATE_READY) + { + privatePlayer->SignalConnect(msg->src, "buffer-underflow-callback", + G_CALLBACK(GstPlayer_OnGstBufferUnderflowCb), pInterfacePlayerRDK); + privatePlayer->SignalConnect(msg->src, "pts-error-callback", + G_CALLBACK(GstPlayer_OnGstPtsErrorCb), pInterfacePlayerRDK); + if (!privatePlayer->socInterface->IsVideoSinkHandleErrors() && GstPlayer_isVideoDecoder(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { + privatePlayer->SignalConnect(msg->src, "decode-error-callback", + G_CALLBACK(GstPlayer_OnGstDecodeErrorCb), pInterfacePlayerRDK); + } + } + } + if((NULL != msg->src) && + ((gst_StartsWith(GST_OBJECT_NAME(msg->src), "rialtomsevideosink") == true) || + (gst_StartsWith(GST_OBJECT_NAME(msg->src), "rialtomseaudiosink") == true))) + { + if(old_state == GST_STATE_NULL && new_state == GST_STATE_READY) + { + privatePlayer->SignalConnect(msg->src, "buffer-underflow-callback", + G_CALLBACK(GstPlayer_OnGstBufferUnderflowCb), pInterfacePlayerRDK); + } + } + pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); + } + + break; + + case GST_MESSAGE_EOS: + busEvent.msgType = MESSAGE_EOS; + //strncpy(busEvent.dbg_info, "N/A", GST_ERROR_DESCRIPTION_LENGTH - 1); + //busEvent.dbg_info[GST_ERROR_DESCRIPTION_LENGTH - 1] = '\0'; + //strncpy(busEvent.msg, "N/A", GST_ERROR_DESCRIPTION_LENGTH - 1); + //busEvent.msg[GST_ERROR_DESCRIPTION_LENGTH - 1] = '\0'; + busEvent.msg = "N/A"; + busEvent.dbg_info = "N/A"; + pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); + MW_LOG_MIL("GST_MESSAGE_EOS"); + pInterfacePlayerRDK->NotifyEOS(); + break; + + case GST_MESSAGE_QOS: + { + gboolean live; + guint64 running_time; + guint64 stream_time; + guint64 timestamp; + guint64 duration; + gst_message_parse_qos(msg, &live, &running_time, &stream_time, ×tamp, &duration); + break; + + } + case GST_MESSAGE_CLOCK_LOST: + /* In this case, the current clock as selected by the pipeline has become unusable. The pipeline will select a new clock on the next PLAYING state change. + As per the gstreamer.desktop org, the application should set the pipeline to PAUSED and back to PLAYING when GST_MESSAGE_CLOCK_LOST is received. + During DASH playback (e.g. when the pipeline is torn down on transition to trickplay), this is done elsewhere. */ + MW_LOG_WARN("GST_MESSAGE_CLOCK_LOST"); + if(eGST_MEDIAFORMAT_DASH != static_cast(pInterfacePlayerRDK->m_gstConfigParam->media)) + { + SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PAUSED); + SetStateWithWarnings(privatePlayer->gstPrivateContext->pipeline, GST_STATE_PLAYING); + } + break; + + case GST_MESSAGE_TAG: + break; + + case GST_MESSAGE_RESET_TIME: + GstClockTime running_time; + gst_message_parse_reset_time (msg, &running_time); + MW_LOG_TRACE("GST_MESSAGE_RESET_TIME %llu\n", (unsigned long long)running_time); + break; + + case GST_MESSAGE_STREAM_STATUS: + case GST_MESSAGE_ELEMENT: // can be used to collect pts, dts, pid + case GST_MESSAGE_DURATION: + case GST_MESSAGE_LATENCY: + break; + case GST_MESSAGE_NEW_CLOCK: + MW_LOG_DEBUG("GST_MESSAGE_NEW_CLOCK element:%s", GST_OBJECT_NAME(msg->src)); + break; + + case GST_MESSAGE_APPLICATION: + { + const GstStructure *msgS; + msgS = gst_message_get_structure (msg); + gchar *structureStr = gst_structure_to_string(msgS); + if(structureStr) + { + busEvent.msg = structureStr; + g_free(structureStr); + } + else + { + busEvent.msg[0] = '\0'; + } + busEvent.msgType = MESSAGE_APPLICATION; + //strncpy(busEvent.dbg_info, "N/A", sizeof(busEvent.dbg_info) - 1); + //busEvent.dbg_info[sizeof(busEvent.dbg_info) - 1] = '\0'; + busEvent.dbg_info = "N/A"; + pInterfacePlayerRDK->busMessageCallback(std::move(busEvent)); + } + break; + default: + MW_LOG_WARN("msg type %s not supported", gst_message_type_get_name(msg->type)); + break; + } + return TRUE; +} + +/** + *@brief Set playback rate to audio/video sinks + */ + +bool InterfacePlayerRDK::SetPlayBackRate(double rate) +{ + bool ret = false; + std::vector sources; + MW_LOG_TRACE("InterfacePlayerRDK: gst_event_new_instant_rate_change: %f ...V6", rate); + for (int iTrack = 0; iTrack < GST_TRACK_COUNT; iTrack++) + { + if (iTrack != static_cast(eGST_MEDIATYPE_SUBTITLE) && interfacePlayerPriv->gstPrivateContext->stream[iTrack].source != nullptr) + { + sources.push_back(interfacePlayerPriv->gstPrivateContext->stream[iTrack].source); + } + } + ret = interfacePlayerPriv->socInterface->SetPlaybackRate(sources, interfacePlayerPriv->gstPrivateContext->pipeline, rate, interfacePlayerPriv->gstPrivateContext->video_dec,interfacePlayerPriv->gstPrivateContext->audio_dec); + return ret; +} + +/** + * @brief Set audio volume + */ +void InterfacePlayerRDK::SetAudioVolume(int volume) +{ + interfacePlayerPriv->gstPrivateContext->audioVolume = static_cast(volume) / 100.0; +} + +/** + * @brief Set audio volume or mute + */ +void InterfacePlayerRDK::SetVolumeOrMuteUnMute(void) +{ + const std::lock_guard lock(interfacePlayerPriv->gstPrivateContext->volumeMuteMutex); + GstElement *gSource = nullptr; + const char *mutePropertyName = nullptr; + const char *volumePropertyName = nullptr; + bool isSinkBinVolume = false; + if (interfacePlayerPriv->gstPrivateContext->usingRialtoSink) + { + gSource = interfacePlayerPriv->gstPrivateContext->audio_sink; + mutePropertyName = "mute"; + volumePropertyName = "volume"; + } + + else + { + interfacePlayerPriv->socInterface->SetAudioProperty(volumePropertyName, mutePropertyName, isSinkBinVolume); + if(isSinkBinVolume) + { + //some platforms sets volume/mute property on sinkbin rather then audio sink + gSource = interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].sinkbin; + } + if (nullptr == gSource) + { + gSource = interfacePlayerPriv->gstPrivateContext->audio_sink; + } + } + MW_LOG_MIL("volume == %lf muted == %s", interfacePlayerPriv->gstPrivateContext->audioVolume,interfacePlayerPriv->gstPrivateContext->audioMuted ? "true" : "false"); + if (nullptr != gSource) + { + if (nullptr != mutePropertyName) + { + /* Muting the audio decoder in general to avoid audio passthrough in expert mode for locked channel */ + if (0 == interfacePlayerPriv->gstPrivateContext->audioVolume) + { + MW_LOG_MIL("Audio Muted"); + g_object_set(gSource, mutePropertyName, true, NULL); + interfacePlayerPriv->gstPrivateContext->audioMuted = true; + } + else if (interfacePlayerPriv->gstPrivateContext->audioMuted) + { + MW_LOG_MIL("Audio Unmuted after a Mute"); + g_object_set(gSource, mutePropertyName, false, NULL); + interfacePlayerPriv->gstPrivateContext->audioMuted = false; + } + else + { + // Deliberately left empty + } + } + if ((nullptr != volumePropertyName) && (false == interfacePlayerPriv->gstPrivateContext->audioMuted)) + { + MW_LOG_MIL("Setting Volume %f using \"%s\" property",interfacePlayerPriv->gstPrivateContext->audioVolume, volumePropertyName); + g_object_set(gSource, volumePropertyName, interfacePlayerPriv->gstPrivateContext->audioVolume, NULL); + } + } + else + { + MW_LOG_WARN("No element to set volume/mute"); + } +} + +void type_check_instance(const char * str, GstElement * elem) +{ + MW_LOG_MIL("%s %p type_check %d", str, elem, G_TYPE_CHECK_INSTANCE (elem)); +} + +static gboolean buffering_timeout (gpointer data) +{ + InterfacePlayerRDK * pInterfacePlayerRDK = (InterfacePlayerRDK *) data; + InterfacePlayerPriv* privatePlayer = nullptr; + if(pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + } + bool isBufferingTimeoutConditionMet = false; + bool isRateCorrectionDefaultOnPlaying = false; + bool isPlayerReady = false; + + if(pInterfacePlayerRDK && privatePlayer->gstPrivateContext) + { + if (privatePlayer->gstPrivateContext->buffering_in_progress) + { + int frames = -1; + if (privatePlayer->gstPrivateContext->video_dec) + { + g_object_get(privatePlayer->gstPrivateContext->video_dec,"queued_frames",(uint*)&frames,NULL); + MW_LOG_DEBUG("queued_frames: %d", frames); + } + GstMediaFormat mediaFormatRet; + mediaFormatRet = (GstMediaFormat)pInterfacePlayerRDK->m_gstConfigParam->media; + /* Disable re-tune on buffering timeout for DASH as unlike HLS, + * DRM key acquisition can end after injection, and buffering is not expected + * to be completed by the 1 second timeout + */ + if (G_UNLIKELY(((mediaFormatRet != eGST_MEDIAFORMAT_DASH) && (mediaFormatRet != eGST_MEDIAFORMAT_PROGRESSIVE) && (mediaFormatRet != eGST_MEDIAFORMAT_HLS_MP4)) + && (privatePlayer->gstPrivateContext->buffering_timeout_cnt == 0) && (privatePlayer->gstPrivateContext->numberOfVideoBuffersSent > 0))) + { + MW_LOG_WARN("numberOfVideoBuffersSent %d frames %i", privatePlayer->gstPrivateContext->numberOfVideoBuffersSent, frames); + isBufferingTimeoutConditionMet = true; + privatePlayer->gstPrivateContext->buffering_in_progress = false; + // application can schedule a retune based on isBufferingTimeoutConditionMet + } + else if (frames == -1 || frames >= pInterfacePlayerRDK->m_gstConfigParam->framesToQueue || privatePlayer->gstPrivateContext->buffering_timeout_cnt-- == 0) + { + uint32_t original_buffering_timeout_cnt = privatePlayer->gstPrivateContext->buffering_timeout_cnt; + MW_LOG_MIL("Set pipeline state to %s - buffering_timeout_cnt %u frames %i", + gst_element_state_get_name(privatePlayer->gstPrivateContext->buffering_target_state), original_buffering_timeout_cnt, frames); + SetStateWithWarnings (privatePlayer->gstPrivateContext->pipeline, privatePlayer->gstPrivateContext->buffering_target_state); + isRateCorrectionDefaultOnPlaying = privatePlayer->socInterface->SetRateCorrection(); + + privatePlayer->gstPrivateContext->buffering_in_progress = false; + isPlayerReady = true; + } + } + if (!privatePlayer->gstPrivateContext->buffering_in_progress) + { + //reset timer id after buffering operation is completed + privatePlayer->gstPrivateContext->bufferingTimeoutTimerId = GST_TASK_ID_INVALID; + } + + if(pInterfacePlayerRDK->OnBuffering_timeoutCb) + { + pInterfacePlayerRDK->OnBuffering_timeoutCb(isBufferingTimeoutConditionMet, isRateCorrectionDefaultOnPlaying, isPlayerReady); + } + return privatePlayer->gstPrivateContext->buffering_in_progress; + } + else + { + return false; + } +} + +/** + * @brief Set video zoom + */ +void InterfacePlayerRDK::SetVideoZoom(int zoom_mode) +{ + MW_LOG_MIL(" SetVideoZoom :: ZoomMode %d, video_sink =%p",zoom_mode, interfacePlayerPriv->gstPrivateContext->video_sink); + + interfacePlayerPriv->gstPrivateContext->zoom = static_cast(zoom_mode); + if ((interfacePlayerPriv->gstPrivateContext->video_sink) && (!interfacePlayerPriv->gstPrivateContext->usingRialtoSink)) + { + g_object_set(interfacePlayerPriv->gstPrivateContext->video_sink, "zoom-mode", zoom_mode, NULL); + } + else + { + MW_LOG_WARN("InterfacePlayerRDK not setting video zoom"); + } +} + +/** + * @brief Set video mute + */ +void InterfacePlayerRDK::SetVideoMute(bool muted) +{ + MW_LOG_INFO("muted=%d video_sink =%p", muted, interfacePlayerPriv->gstPrivateContext->video_sink); + interfacePlayerPriv->gstPrivateContext->videoMuted = muted; + if (interfacePlayerPriv->gstPrivateContext->video_sink) + { + g_object_set(interfacePlayerPriv->gstPrivateContext->video_sink, "show-video-window", !interfacePlayerPriv->gstPrivateContext->videoMuted, NULL); /* videoMuted to true implies setting the 'show-video-window' to false */ + } + else + { + MW_LOG_INFO("InterfacePlayerRDK not setting video mute"); + } +} + +/** + * @brief Set the text style of the subtitle to the options passed + */ +bool InterfacePlayerRDK::SetTextStyle(const std::string &options) +{ + bool ret = false; + if (interfacePlayerPriv->gstPrivateContext->subtitle_sink) + { + TextStyleAttributes textStyleAttributes; + uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + if (textStyleAttributes.getAttributes(options, attributesValues, attributesMask) == 0) + { + if (attributesMask) + { + GstStructure *attributes = gst_structure_new ("Attributes", + "font_color", G_TYPE_UINT, attributesValues[TextStyleAttributes::FONT_COLOR_ARR_POSITION], + "background_color", G_TYPE_UINT, attributesValues[TextStyleAttributes::BACKGROUND_COLOR_ARR_POSITION], + "font_opacity", G_TYPE_UINT, attributesValues[TextStyleAttributes::FONT_OPACITY_ARR_POSITION], + "background_opacity", G_TYPE_UINT, attributesValues[TextStyleAttributes::BACKGROUND_OPACITY_ARR_POSITION], + "font_style", G_TYPE_UINT, attributesValues[TextStyleAttributes::FONT_STYLE_ARR_POSITION], + "font_size", G_TYPE_UINT, attributesValues[TextStyleAttributes::FONT_SIZE_ARR_POSITION], + "window_color", G_TYPE_UINT, attributesValues[TextStyleAttributes::WIN_COLOR_ARR_POSITION], + "window_opacity", G_TYPE_UINT, attributesValues[TextStyleAttributes::WIN_OPACITY_ARR_POSITION], + "edge_type", G_TYPE_UINT, attributesValues[TextStyleAttributes::EDGE_TYPE_ARR_POSITION], + "edge_color", G_TYPE_UINT, attributesValues[TextStyleAttributes::EDGE_COLOR_ARR_POSITION], + "attribute_mask", G_TYPE_UINT, attributesMask, + NULL); + g_object_set(interfacePlayerPriv->gstPrivateContext->subtitle_sink, "attribute-values", attributes, NULL); + gst_structure_free (attributes); + } + } + ret = true; + } + else + { + MW_LOG_INFO("InterfacePlayerRDK: subtitle sink not set"); + } + return ret; +} + +/** + * @brief Callback invoked after receiving the SEI Time Code information + * @param[in] object pointer to element raising the callback + * @param[in] hours Hour value of the SEI Timecode + * @param[in] minutes Minute value of the SEI Timecode + * @param[in] seconds Second value of the SEI Timecode + * @param[in] user_data pointer to InterfacePlayerRDK instance + */ +static void GstPlayer_redButtonCallback(GstElement* object, guint hours, guint minutes, guint seconds, gpointer user_data) +{ + InterfacePlayerRDK *pInterfacePlayerRDK = (InterfacePlayerRDK *)user_data; + InterfacePlayerPriv* privatePlayer = nullptr; + if (pInterfacePlayerRDK) + { + privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER_CALLBACK_VOID(); + char buffer[16]; + snprintf(buffer,16,"%d:%d:%d",hours,minutes,seconds); + if(pInterfacePlayerRDK->OnHandleRedButtonCallback) + { + pInterfacePlayerRDK->OnHandleRedButtonCallback(buffer); + } + } +} + +/** + * @brief Invoked synchronously when a message is available on the bus + * @param[in] bus the GstBus that sent the message + * @param[in] msg the GstMessage + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @retval GST_BUS_PASS to pass the message to the async queue + */ +static GstBusSyncReply bus_sync_handler(GstBus * bus, GstMessage * msg, InterfacePlayerRDK * pInterfacePlayerRDK) +{ + InterfacePlayerPriv* privatePlayer = pInterfacePlayerRDK->GetPrivatePlayer(); + HANDLER_CONTROL_HELPER( privatePlayer->gstPrivateContext->syncControl, GST_BUS_PASS); + switch(GST_MESSAGE_TYPE(msg)) + { + case GST_MESSAGE_STATE_CHANGED: + GstState old_state, new_state; + gst_message_parse_state_changed(msg, &old_state, &new_state, NULL); + + if (GST_MESSAGE_SRC(msg) == GST_OBJECT(privatePlayer->gstPrivateContext->pipeline)) + { + privatePlayer->gstPrivateContext->pipelineState = new_state; + } + /* Moved the below code block from bus_message() async handler to bus_sync_handler() + * to avoid a timing case crash when accessing wrong video_sink element after it got deleted during pipeline reconfigure on codec change in mid of playback. + */ + if (new_state == GST_STATE_PAUSED && old_state == GST_STATE_READY) + { + if (GstPlayer_isVideoSink(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { // video scaling patch + /* + brcmvideosink doesn't sets the rectangle property correct by default + gst-inspect-1.0 brcmvideosink + g_object_get(pInterfacePlayerRDK->privateContext->pipeline, "video-sink", &videoSink, NULL); - reports NULL + note: alternate "window-set" works as well + */ + gst_object_replace((GstObject **)&privatePlayer->gstPrivateContext->video_sink, msg->src); + + if (privatePlayer->gstPrivateContext->usingRialtoSink) + { + if (pInterfacePlayerRDK->m_gstConfigParam->enableRectPropertyCfg) + { + MW_LOG_MIL("InterfacePlayerRDK - using %s, setting cached rectangle(%s)", + GST_OBJECT_NAME(msg->src), privatePlayer->gstPrivateContext->videoRectangle); + g_object_set(msg->src, "rectangle", privatePlayer->gstPrivateContext->videoRectangle, NULL); + } + MW_LOG_MIL("InterfacePlayerRDK - using %s, setting cached video mute(%d)", + GST_OBJECT_NAME(msg->src), privatePlayer->gstPrivateContext->videoMuted); + g_object_set(msg->src, "show-video-window", !privatePlayer->gstPrivateContext->videoMuted, NULL); + } + else if ((privatePlayer->gstPrivateContext->using_westerossink) && !(pInterfacePlayerRDK->m_gstConfigParam->enableRectPropertyCfg)) + { + MW_LOG_MIL("InterfacePlayerRDK - using westerossink, setting cached video mute(%d) and zoom(%d)", + privatePlayer->gstPrivateContext->videoMuted, privatePlayer->gstPrivateContext->zoom); + g_object_set(msg->src, "zoom-mode", privatePlayer->gstPrivateContext->zoom, NULL ); + g_object_set(msg->src, "show-video-window", !privatePlayer->gstPrivateContext->videoMuted, NULL); + } + else + { + MW_LOG_MIL("InterfacePlayerRDK setting cached rectangle(%s), video mute(%d) and zoom(%d)", + privatePlayer->gstPrivateContext->videoRectangle, + privatePlayer->gstPrivateContext->videoMuted, + privatePlayer->gstPrivateContext->zoom); + g_object_set(msg->src, "rectangle", privatePlayer->gstPrivateContext->videoRectangle, NULL); + g_object_set(msg->src, "zoom-mode", privatePlayer->gstPrivateContext->zoom, NULL ); + g_object_set(msg->src, "show-video-window", !privatePlayer->gstPrivateContext->videoMuted, NULL); + } + } + else + { + if((gst_StartsWith(GST_OBJECT_NAME(msg->src), "rialtomseaudiosink"))) + { + gst_object_replace((GstObject **)&privatePlayer->gstPrivateContext->audio_sink, msg->src); + pInterfacePlayerRDK->SetVolumeOrMuteUnMute(); + } + else + { + bool status = privatePlayer->socInterface->ConfigureAudioSink(&privatePlayer->gstPrivateContext->audio_sink, msg->src, pInterfacePlayerRDK->m_gstConfigParam->audioDecoderStreamSync); + if(status) + { + pInterfacePlayerRDK->SetVolumeOrMuteUnMute(); + } + } + } + + } + if (old_state == GST_STATE_NULL && new_state == GST_STATE_READY) + { + if ((NULL != msg->src) && GstPlayer_isVideoOrAudioDecoder(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { + if (GstPlayer_isVideoDecoder(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK)) + { // video + gst_object_replace((GstObject **)&privatePlayer->gstPrivateContext->video_dec, msg->src); + type_check_instance("bus_sync_handle: video_dec ", privatePlayer->gstPrivateContext->video_dec); + privatePlayer->SignalConnect(privatePlayer->gstPrivateContext->video_dec, "first-video-frame-callback", + G_CALLBACK(GstPlayer_OnFirstVideoFrameCallback), pInterfacePlayerRDK); + privatePlayer->socInterface->SetDecodeError(msg->src); + } + else + { // audio + gst_object_replace((GstObject **)&privatePlayer->gstPrivateContext->audio_dec, msg->src); + type_check_instance("bus_sync_handle: audio_dec ", privatePlayer->gstPrivateContext->audio_dec); + + if(privatePlayer->socInterface->HasFirstAudioFrameCallback()) + { + privatePlayer->SignalConnect(msg->src, "first-audio-frame-callback", + G_CALLBACK(GstPlayer_OnAudioFirstFrameAudDecoder), pInterfacePlayerRDK); + } + int trackId = privatePlayer->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].trackId; + if (trackId >= 0) /** AC4 track selected **/ + { + privatePlayer->socInterface->SetAC4Tracks(GST_ELEMENT(msg->src), trackId); + } + + } + } + if ((NULL != msg->src) && GstPlayer_isVideoSink(GST_OBJECT_NAME(msg->src), pInterfacePlayerRDK) && (!privatePlayer->gstPrivateContext->usingRialtoSink)) + { + if(privatePlayer->gstPrivateContext->enableSEITimeCode) + { + g_object_set(msg->src, "enable-timecode", 1, NULL); + privatePlayer->SignalConnect(msg->src, "timecode-callback", + G_CALLBACK(GstPlayer_redButtonCallback), pInterfacePlayerRDK); + } + privatePlayer->socInterface->SetFreerunThreshold(msg->src); + } + if(!privatePlayer->socInterface->HasFirstAudioFrameCallback()) + { + if ((NULL != msg->src) && gst_StartsWith(GST_OBJECT_NAME(msg->src), "rtkaudiosink")) + { + privatePlayer->SignalConnect(msg->src, "first-audio-frame", + G_CALLBACK(GstPlayer_OnAudioFirstFrameAudDecoder), pInterfacePlayerRDK); + } + } + if ((NULL != msg->src) && + (gst_StartsWith(GST_OBJECT_NAME(msg->src), GstPluginNamePR) == true || + gst_StartsWith(GST_OBJECT_NAME(msg->src), GstPluginNameWV) == true || + gst_StartsWith(GST_OBJECT_NAME(msg->src), GstPluginNameCK) == true || + gst_StartsWith(GST_OBJECT_NAME(msg->src), GstPluginNameVMX) == true)) + { + MW_LOG_MIL("InterfacePlayerRDK setting encrypted player (%p) instance for %s decryptor", pInterfacePlayerRDK->mEncrypt, GST_OBJECT_NAME(msg->src)); + GValue val = { 0, }; + g_value_init(&val, G_TYPE_POINTER); + + g_value_set_pointer(&val, (gpointer) pInterfacePlayerRDK->mDRMSessionManager); // encryption is being passed by player + + g_object_set_property(G_OBJECT(msg->src), privatePlayer->mPlayerName.c_str(), &val); + GValue val_drm = { 0, }; + g_value_init(&val_drm, G_TYPE_POINTER); + g_value_set_pointer(&val_drm, (gpointer) pInterfacePlayerRDK->mEncrypt); + g_object_set_property(G_OBJECT(msg->src), "drm-session-manager", &val_drm); + } + } + break; + + case GST_MESSAGE_NEED_CONTEXT: + + const gchar* contextType; + gst_message_parse_context_type(msg, &contextType); + if (!g_strcmp0(contextType, "drm-preferred-decryption-system-id")) + { + MW_LOG_MIL("Setting %s as preferred drm",pInterfacePlayerRDK->mDrmSystem); + GstContext* context = gst_context_new("drm-preferred-decryption-system-id", FALSE); + GstStructure* contextStructure = gst_context_writable_structure(context); + gst_structure_set(contextStructure, "decryption-system-id", G_TYPE_STRING, pInterfacePlayerRDK->mDrmSystem, NULL); + gst_element_set_context(GST_ELEMENT(GST_MESSAGE_SRC(msg)), context); + } + + break; + case GST_MESSAGE_ASYNC_DONE: + + MW_LOG_INFO("Received GST_MESSAGE_ASYNC_DONE message"); + if (privatePlayer->gstPrivateContext->buffering_in_progress) + { + privatePlayer->gstPrivateContext->bufferingTimeoutTimerId = g_timeout_add_full(BUFFERING_TIMEOUT_PRIORITY, DEFAULT_BUFFERING_TO_MS, buffering_timeout, pInterfacePlayerRDK, NULL); + } + + break; + + case GST_MESSAGE_STREAM_STATUS: + + if(privatePlayer->gstPrivateContext->task_pool) + { + GstStreamStatusType type; + GstElement *owner; + const GValue *val; + GstTask *task = NULL; + gst_message_parse_stream_status (msg, &type, &owner); + val = gst_message_get_stream_status_object (msg); + if (G_VALUE_TYPE (val) == GST_TYPE_TASK) + { + task =(GstTask*) g_value_get_object (val); + } + switch (type) + { + case GST_STREAM_STATUS_TYPE_CREATE: + if (task && privatePlayer->gstPrivateContext->task_pool) + { + gst_task_set_pool(task, privatePlayer->gstPrivateContext->task_pool); + } + break; + + default: + break; + } + } + + break; + + default: + break; + + } + return GST_BUS_PASS; /* pass the message to the async queue */ +} + + +/** + * @brief Notify EOS to core player asynchronously if required. + * @note Used internally by InterfacePlayer + */ +void InterfacePlayerRDK::NotifyEOS() +{ + if (!interfacePlayerPriv->gstPrivateContext->eosSignalled) + { + if (!interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending) + { + /*Scheduling and executed async task immediately without returing the task id. + Which is leading to set the task pending always true when SLE is reached END_OF_LIST. + Due to this 30 tick is reported. changing the logic to set task pending to true before adding the task in notifyEOS function + and making it pending task to false if task id is invalid and eoscallback is pending.*/ + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending = true; + // eosSignalled is reset once the async task is completed either in Configure/Flush/ResetEOSSignalled, so set the flag before scheduling the task + interfacePlayerPriv->gstPrivateContext->eosSignalled = true; + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId = mScheduler.ScheduleTask(PlayerAsyncTaskObj(IdleCallbackOnEOS, (void *)this, "IdleCallbackOnEOS")); + if (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId == PLAYER_TASK_ID_INVALID && true == interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending) + { + interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending = false; + MW_LOG_MIL("eosCallbackIdleTaskPending(%d),eosCallbackIdleTaskId(%d)", + (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending ? 1 : 0),interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + } + else + { + MW_LOG_MIL("eosCallbackIdleTask scheduled eosCallbackIdleTaskPending(%d),eosCallbackIdleTaskId(%d)", + (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending ? 1 : 0),interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + } + } + else + { + MW_LOG_WARN("IdleCallbackOnEOS already registered previously, hence skip! eosCallbackIdleTaskPending(%d),eosCallbackIdleTaskId(%d)", + (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending ? 1 : 0),interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + } + } + else + { + MW_LOG_WARN("EOS already signaled, hence skip! eosCallbackIdleTaskPending(%d),eosCallbackIdleTaskId(%d)", + (interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskPending ? 1 : 0),interfacePlayerPriv->gstPrivateContext->eosCallbackIdleTaskId); + } +} + +/** + * @brief Set pipeline to PLAYING state once fragment caching is complete + */ +void InterfacePlayerRDK::NotifyFragmentCachingComplete() +{ + if(interfacePlayerPriv->gstPrivateContext->pendingPlayState) + { + MW_LOG_MIL("InterfacePlayer: Setting pipeline to PLAYING state "); + interfacePlayerPriv->gstPrivateContext->buffering_target_state = GST_STATE_PLAYING; + if (SetStateWithWarnings(interfacePlayerPriv->gstPrivateContext->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) + { + MW_LOG_ERR("InterfacePlayer_Configure GST_STATE_PLAYING failed"); + } + interfacePlayerPriv->gstPrivateContext->pendingPlayState = false; + } + else + { + MW_LOG_MIL("InterfacePlayer: No pending PLAYING state"); + } +} + +/** + * @brief Starts processing EOS for a particular stream type + */ +void InterfacePlayerRDK::EndOfStreamReached(int mediaType, bool &shouldHaltBuffering) +{ + MW_LOG_MIL("entering InterfacePlayer_EndOfStreamReached type %d", mediaType); + GstMediaType type = static_cast(mediaType); + + gst_media_stream *stream = &interfacePlayerPriv->gstPrivateContext->stream[type]; + stream->eosReached = true; + if ((stream->format != GST_FORMAT_INVALID) && stream->firstBufferProcessed == false) + { + MW_LOG_MIL("EOS received as first buffer "); + NotifyEOS(); + } + else + { + NotifyFragmentCachingComplete(); /*Set pipeline to PLAYING state once fragment caching is complete*/ + GstPlayer_SignalEOS(stream); + + /*For trickmodes, give EOS to audio source*/ + if (GST_NORMAL_PLAY_RATE != interfacePlayerPriv->gstPrivateContext->rate) + { + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO]); + if (interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].source) + { + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE]); + } + } + else + { + if ((interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_AUDIO].eosReached) && + (!interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].eosReached) && + (interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].source)) + { + interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE].eosReached = true; + GstPlayer_SignalEOS(interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE]); + } + } + + // We are in buffering, but we received end of stream, un-pause pipeline + shouldHaltBuffering = true; + } +} + +/** + * @brief Setup pipeline for a particular stream type + * @param[in] pInterfacePlayerRDK pointer to InterfacePlayerRDK instance + * @param[in] streamId stream type + * @retval 0, if setup successfully. -1, for failure + */ +int InterfacePlayerRDK::InterfacePlayer_SetupStream(int streamId, std::string manifestUrl) +{ + int retvalue = 0; + GstMediaType mediaType = static_cast(streamId); + this->TriggerEvent(InterfaceCB::startNewSubtitleStream, mediaType); + retvalue = this->SetupStream(mediaType, (void*)this, std::move(manifestUrl)); + + return retvalue; +} + +void InterfacePlayerRDK::DisableDecoderHandleNotified() +{ + interfacePlayerPriv->gstPrivateContext->decoderHandleNotified = false; +} + +/** + * @fn SignalSubtitleClock + * @brief Signal the new clock to subtitle module + * @return - true indicating successful operation in sending the clock update + */ +bool InterfacePlayerRDK::SignalSubtitleClock(gint64 vPTS, bool state) +{ + bool signalSent=false; + bool underflowState = state; + gint64 videoPTS = vPTS; + gst_media_stream* stream = &interfacePlayerPriv->gstPrivateContext->stream[eGST_MEDIATYPE_SUBTITLE]; + if ( stream && (stream->format != GST_FORMAT_INVALID) ) + { + if (!stream->source) + { + MW_LOG_ERR("subtitle appsrc is NULL"); + } + else if (!GST_IS_APP_SRC(stream->source)) + { + MW_LOG_ERR("subtitle appsrc is invalid"); + } + else + { + //Check if pipeline is in playing/paused state. + GstState current, pending; + GstStateChangeReturn ret; + ret = gst_element_get_state(interfacePlayerPriv->gstPrivateContext->pipeline, ¤t, &pending, 0); + if ( (current == GST_STATE_PLAYING) && (ret != GST_STATE_CHANGE_FAILURE) && (underflowState != true) ) + { + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(stream->source), "src"); /* Retrieves the src pad */ + if (sourceEleSrcPad != NULL) + { + if (videoPTS > 0) + { + //GetVideoPTS returns PTS in 90KHz clock, convert it to nanoseconds for max precision + GstClockTime pts = ((double)videoPTS / 90000.0) * GST_SECOND; + GstStructure * eventStruct = gst_structure_new("sub_clock_sync", "current-pts", G_TYPE_UINT64, pts, NULL); + if (!gst_pad_push_event(sourceEleSrcPad, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, eventStruct))) + { + MW_LOG_ERR("Error on sending sub_clock_sync event"); + MW_LOG_ERR("Got VideoPTS: %" G_GINT64_FORMAT " and converted pts: %" G_GUINT64_FORMAT " , state = %d, pending = %d", videoPTS, pts, current, pending); + } + else + { + MW_LOG_DEBUG("Sent sub_clock_sync event, pts = %" G_GUINT64_FORMAT ", pts from sink was %" G_GUINT64_FORMAT "", pts, videoPTS); + signalSent=true; + } + } + else + { + MW_LOG_INFO("Got invalid video PTS: %" G_GINT64_FORMAT ". Clock not sent.", videoPTS); + } + gst_object_unref(sourceEleSrcPad); + } + else + { + MW_LOG_ERR("sourceEleSrcPad is NULL. Failed to send subtec clock event"); + } + } + else + { + MW_LOG_TRACE("Not sending clock event in non-play state to avoid gstreamer lockup, state = %d, pending = %d, underflow = %d.", + current, pending, underflowState); + } + } + } + else + { + if (stream) + { + MW_LOG_WARN("Invalid stream->format = %d)", stream->format); + } + else + { + MW_LOG_ERR("stream invalid)"); + } + } + //MW_LOG_TRACE("Exit SignalSubtitleClock"); + return signalSent; +} +/** + * @brief Increase the rank of Player decryptor plugins + */ +void InterfacePlayerRDK::InitializePlayerGstreamerPlugins() +{ + // Ensure GST is initialized + if (!gst_init_check(nullptr, nullptr, nullptr)) { + MW_LOG_ERR("gst_init_check() failed"); + } + +#define PLUGINS_TO_LOWER_RANK_MAX 2 + static const char *plugins_to_lower_rank[PLUGINS_TO_LOWER_RANK_MAX] = { + "aacparse", + "ac3parse", + }; + GstRegistry* registry = gst_registry_get(); + + GstPluginFeature* pluginFeature = gst_registry_lookup_feature(registry, GstPluginNamePR); + + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK: %s plugin feature not available; reloading player's plugin", GstPluginNamePR); + GstPlugin * plugin = gst_plugin_load_by_name ("aamp"); + if(plugin) + { + gst_object_unref(plugin); + } + pluginFeature = gst_registry_lookup_feature(registry, GstPluginNamePR); + if(pluginFeature == NULL) + MW_LOG_ERR("InterfacePlayerRDK: %s plugin feature not available", GstPluginNamePR); + } + if(pluginFeature) + { + // CID:313773 gst_registry_remove_feature() will unref pluginFeature internally and + // gst_registry_add_feature() will ref it again. So to maintain the refcount we do a ref and unref here + // gst_registry_lookup_feature() will return pluginFeature after incrementing refcount which is unref at the end + gst_object_ref(pluginFeature); + gst_registry_remove_feature (registry, pluginFeature); + gst_registry_add_feature (registry, pluginFeature); + gst_object_unref(pluginFeature); + + MW_LOG_MIL("InterfacePlayerRDK: %s plugin priority set to GST_RANK_PRIMARY + 111", GstPluginNamePR); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY + 111); + gst_object_unref(pluginFeature); + } + + pluginFeature = gst_registry_lookup_feature(registry, GstPluginNameWV); + + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK: %s plugin feature not available", GstPluginNameWV); + } + else + { + MW_LOG_MIL("InterfacePlayerRDK: %s plugin priority set to GST_RANK_PRIMARY + 111", GstPluginNameWV); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY + 111); + gst_object_unref(pluginFeature); + } + + pluginFeature = gst_registry_lookup_feature(registry, GstPluginNameCK); + + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK: %s plugin feature not available", GstPluginNameCK); + } + else + { + MW_LOG_MIL("InterfacePlayerRDK: %s plugin priority set to GST_RANK_PRIMARY + 111", GstPluginNameCK); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY + 111); + gst_object_unref(pluginFeature); + } + + pluginFeature = gst_registry_lookup_feature(registry, GstPluginNameVMX); + + if (pluginFeature == NULL) + { + MW_LOG_ERR("InterfacePlayerRDK %s plugin feature not available", GstPluginNameVMX); + } + else + { + MW_LOG_MIL("InterfacePlayerRDK %s plugin priority set to GST_RANK_PRIMARY + 111", GstPluginNameVMX); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_PRIMARY + 111); + gst_object_unref(pluginFeature); + } + for (int i=0; i(mediaType); + + MW_LOG_MIL("Entering InterfacePlayerRDK::FlushTrack() type[%d] pipeline state %s pos %lf",(int)type, + gst_element_state_get_name(GST_STATE(interfacePlayerPriv->gstPrivateContext->pipeline)), pos); + gst_media_stream *stream = &this->interfacePlayerPriv->gstPrivateContext->stream[type]; + double rate = (double)GST_NORMAL_PLAY_RATE; + + if(eGST_MEDIATYPE_AUDIO == type) + { + interfacePlayerPriv->socInterface->SetSeamlessSwitch(this->interfacePlayerPriv->gstPrivateContext->audio_sink, TRUE); + interfacePlayerPriv->gstPrivateContext->filterAudioDemuxBuffers = true; + pos = pos + audioDelta; + + } + else + { + pos = pos + subDelta; + } + gst_element_seek_simple (GST_ELEMENT(stream->source), + GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, + pos * GST_SECOND); + + startPosition = pos; + MW_LOG_MIL("Exiting InterfacePlayerRDK::FlushTrack() type[%d] pipeline state: %s startPosition: %lf Delta %lf",(int)type, gst_element_state_get_name(GST_STATE(interfacePlayerPriv->gstPrivateContext->pipeline)), startPosition, (int)type==eGST_MEDIATYPE_AUDIO?audioDelta:subDelta); + + return rate; +} diff --git a/middleware/InterfacePlayerRDK.h b/middleware/InterfacePlayerRDK.h new file mode 100644 index 000000000..be891ac1d --- /dev/null +++ b/middleware/InterfacePlayerRDK.h @@ -0,0 +1,781 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INTERFACE_PLAYER_H +#define INTERFACE_PLAYER_H + +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" +#include "PlayerScheduler.h" +#include +#include +#include +#include +#include +#include +#include +#include "SocUtils.h" +#include "GstUtils.h" + +class InterfacePlayerPriv; + +struct MonitorAVState +{ + int64_t tLastReported; + int64_t tLastSampled; + const char *description; + signed long av_position[2]; + bool happy; + + MonitorAVState() : tLastReported(0), tLastSampled(0), description(nullptr), happy(false) + { + av_position[0] = 0; // Video position + av_position[1] = 0; // Audio position + } +}; + +/** + * @brief Function pointer for the idle task + * @param[in] arg - Arguments + * @return Idle task status + */ +typedef int (*BackgroundTask)(void *arg); + +struct GstTaskControlData +{ + guint taskID; + bool taskIsPending; + std::string taskName; + GstTaskControlData(const char *taskIdent) : taskID(0), taskIsPending(false), taskName(taskIdent ? taskIdent : "undefined") {}; +}; + +/* + *@enum msgType + */ +enum MsgType +{ + MESSAGE_ERROR, + MESSAGE_WARNING, + MESSAGE_EOS, + MESSAGE_APPLICATION, + MESSAGE_STATE_CHANGE, + MESSAGE_CLOCK_LOST +}; + +struct BusEventData +{ + MsgType msgType; + std::string msg; + std::string dbg_info; + bool setPlaybackRate; + bool firstBufferProcessed; + bool receivedFirstFrame; +}; + +struct Configs +{ + std::string networkProxyValue; + bool gstreamerSubsEnabled; + bool tcpServerSink; + int tcpPort; + bool appSrcForProgressivePlayback; + bool seamlessAudioSwitch; + bool enablePTSReStamp; + std::string manifestUrl; + int media; + int videoBufBytes; + bool enableDisconnectSignals; + int eosInjectionMode; + bool enablePtsSync; + int framesToQueue; + int vodTrickModeFPS; + int enableGstPosQuery; + int audioBufBytes; + std::string networkProxy; + float progressTimer; + bool audioOnlyMode; + bool enableRectPropertyCfg; + bool audioDecoderStreamSync; + bool gstreamerBufferingBeforePlay; + bool seiTimeCode; + bool useWesterosSink; + bool useRialtoSink; + bool gstLogging; + bool progressLogging; + bool monitorAV; + bool disableUnderflow; + int monitorAvsyncThresholdPositiveMs; + int monitorAvsyncThresholdNegativeMs; + int monitorAvJumpThresholdMs; + bool useMp4Demux; +}; + + +typedef struct GstPlaybackQualityData +{ + long long rendered; + long long dropped; +} GstPlaybackQualityStruct; + +enum class InterfaceCB +{ + firstVideoFrameDisplayed, + idleCb, + progressCb, + firstVideoFrameReceived, + notifyEOS, + startNewSubtitleStream // Add more events here if needed +}; + +// Class to encapsulate GStreamer-related functionality +class InterfacePlayerRDK +{ + private: + bool trickTeardown; + std::mutex mMutex; + std::map configMap; + + public: + Configs *m_gstConfigParam; + char *mDrmSystem; + void *mEncrypt; + void *mDRMSessionManager; + std::map> callbackMap; + std::map> setupStreamCallbackMap; + + PlayerScheduler mScheduler; + InterfacePlayerRDK(); + ~InterfacePlayerRDK(); + InterfacePlayerPriv* GetPrivatePlayer(); + /** + * @brief Clears the flags for an idle task. + * @param[in] taskDetails The details of the task to clear flags for. + */ + void IdleTaskClearFlags(GstTaskControlData &taskDetails); + /** + * @brief Removes an idle task from the GStreamer pipeline. + * @param[in] taskDetails The details of the task to be removed. + * @return true if the task is successfully removed, false otherwise. + */ + bool IdleTaskRemove(GstTaskControlData &taskDetails); + /** + * @brief Adds an idle task. + * @param[in] taskDetails The details of the task to be added. + * @param[in] funcPtr The function pointer for the background task. + * @return True if the task was added successfully, false otherwise. + */ + bool IdleTaskAdd(GstTaskControlData &taskDetails, BackgroundTask funcPtr); + /** + * @brief Idle callback for the first frame. + * + * @param[in] user_data User data passed to the callback. + * @return TRUE if the callback should be called again, FALSE otherwise. + */ + static gboolean IdleCallbackOnFirstFrame(gpointer user_data); + /** + * @brief Idle callback for end of stream (EOS). + * + * @param[in] user_data User data passed to the callback. + * @return TRUE if the callback should be called again, FALSE otherwise. + */ + static gboolean IdleCallbackOnEOS(gpointer user_data); + /** + * @brief Progress callback on timeout. + * + * @param[in] user_data User data passed to the callback. + * @return TRUE if the callback should be called again, FALSE otherwise. + */ + static gboolean ProgressCallbackOnTimeout(gpointer user_data); + /** + * @brief General idle callback. + * + * @param[in] user_data User data passed to the callback. + * @return TRUE if the callback should be called again, FALSE otherwise. + */ + static gboolean IdleCallback(gpointer user_data); + /** + * @brief Idle callback for the first video frame displayed. + * + * @param[in] user_data User data passed to the callback. + * @return TRUE if the callback should be called again, FALSE otherwise. + */ + static gboolean IdleCallbackFirstVideoFrameDisplayed(gpointer user_data); + /*Callback function types for various GStreamer events*/ + using BusMessageCallback = std::function; + using HandleOnGstBufferUnderflowCb = std::function; + using HandleOnGstDecodeErrorCb = std::function; + using HandleOnGstPtsErrorCb = std::function; + using HandleBuffering_timeoutCb = std::function; + using HandleRedButtonCallback = std::function; + using HandleNeedDataCb = std::function; + using HandleEnoughDataCb = std::function; + /** + * @brief Checks if the pipeline is currently in paused state + * @return true if pipeline is paused, false otherwise + */ + bool IsPipelinePaused(); + /** + * @brief Sets a flag indicating that pipeline transition to PLAYING state is pending + */ + void EnablePendingPlayState(); + /* + *@brief Registers need data callback from application + */ + void RegisterNeedDataCb(const HandleNeedDataCb &callback) + { + NeedDataCb = callback; + } + /* + *@brief Registers enough data callback from application + */ + void RegisterEnoughDataCb(const HandleEnoughDataCb &callback) + { + EnoughDataCb = callback; + } + /* + *@brief register registerHandleBuffering_timeoutCb + */ + void RegisterBufferingTimeoutCb(const HandleBuffering_timeoutCb &callback) + { + OnBuffering_timeoutCb = callback; + } + /* + *@brief register HandleOnGstPtsErrorCb + */ + void RegisterGstPtsErrorCb(const HandleOnGstPtsErrorCb &callback) + { + OnGstPtsErrorCb = callback; + } + /* + *@brief register handleOnGstDecodeErrorCb + */ + void RegisterGstDecodeErrorCb(const HandleOnGstDecodeErrorCb &callback) + { + OnGstDecodeErrorCb = callback; + } + /* + *@brief register OnGstBufferUnderflowCb + */ + void RegisterBufferUnderflowCb(const HandleOnGstBufferUnderflowCb &callback) + { + OnGstBufferUnderflowCb = callback; + } + /* + *@brief register registerHandleRedButtonCallback + */ + void RegisterHandleRedButtonCallback(const HandleRedButtonCallback &callback) + { + OnHandleRedButtonCallback = callback; + } + /*callback declarations*/ + BusMessageCallback busMessageCallback; + HandleOnGstBufferUnderflowCb OnGstBufferUnderflowCb; + HandleOnGstDecodeErrorCb OnGstDecodeErrorCb; + HandleOnGstPtsErrorCb OnGstPtsErrorCb; + HandleBuffering_timeoutCb OnBuffering_timeoutCb; + HandleRedButtonCallback OnHandleRedButtonCallback; + HandleNeedDataCb NeedDataCb; + HandleEnoughDataCb EnoughDataCb; + std::function stopCallback; + std::function tearDownCb; + std::function notifyFirstFrameCallback; + std::function gstCbExportYUVFrame; + pthread_mutex_t mProtectionLock; + bool mFirstFrameRequired; + bool mPauseInjector; + bool mResumeInjector; + bool PipelineSetToReady; /**< To indicate the pipeline is set to ready forcefully */ + std::mutex mSourceSetupMutex; /**< Protects the source setup state>*/ + std::condition_variable mSourceSetupCV; /**< Conditional Variable to notify changes in the source setup status>*/ + bool mSchedulerStarted; + /** + * @brief Creates a GStreamer pipeline. + * @param pipelineName Name of the pipeline. + * @param PipelinePriority Priority of the pipeline. + * @return True if the pipeline is created successfully, false otherwise. + */ + bool CreatePipeline(const char *pipelineName, int PipelinePriority); + /** + * @brief Sets the player name. + * + * @param[in] name The name of the player. + */ + void SetPlayerName(std::string name); + /** + *@brief sets the preferred drm by app + *@param[in] drmID preferred drm + */ + void SetPreferredDRM(const char *drmID); + /** + * @brief Configures the GStreamer pipeline. + * @param format Video format. + * @param audioFormat Audio format. + * @param auxFormat Auxiliary format. + * @param subFormat Whether subtitle format is enabled. + * @param bESChangeStatus Whether ES change status is enabled. + * @param forwardAudioToAux Whether audio should be forwarded to the auxiliary output. + * @param setReadyAfterPipelineCreation Whether to set the player as ready after pipeline creation. + * @param isSubEnable Whether subtitles are enabled. + * @param trackId Track ID. + * @param rate Bitrate. + * @param pipelineName Pipeline name. + * @param PipelinePriority Pipeline priority. + */ + void ConfigurePipeline(int, int, int, int, bool, bool, bool, bool, int32_t, gint, const char *, int, bool, std::string url); + /** + * @brief Enables or disables pausing on playback start. + * @param enable True to enable pausing, false to disable. + */ + void SetPauseOnStartPlayback(bool enable); + /** + * @fn ForwardAudioBuffersToAux + * + * @return bool - true if audio to be forwarded + */ + bool ForwardAudioBuffersToAux(); + /** + * @brief Flush the track playbin + * @param[in] pos - position to seek to after flush + * @param[in] pos - audio delta + * @param[in] pos - subtitle delta + */ + double FlushTrack(int mediaType, double pos, double audioDelta, double subDelta); + /** + * @fn set video zoom + * + */ + void SetVideoZoom(int zoom_mode); + /** + * @fn set video mute + * + */ + void SetVideoMute(bool muted); + /** + * @fn set text style + * + */ + bool SetTextStyle(const std::string &options); + /** + * @fn GetVideoRectangle + * + */ + std::string GetVideoRectangle(); + /** + * @fn SetSubtitlePtsOffset + * @param[in] pts_offset pts offset for subs + */ + void SetSubtitlePtsOffset(std::uint64_t pts_offset); + /** + * @fn SetSubtitleMute + * @param[in] muted true to mute subtitle otherwise false + */ + void SetSubtitleMute(bool mute); + /** + * @fn GetPositionMilliseconds + * @retval playback position in MS + */ + long long GetPositionMilliseconds(void); + /** + * @fn GetVideoPlaybackQuality + * returns video playback quality data + */ + GstPlaybackQualityStruct *GetVideoPlaybackQuality(void); + /** + * @fn GetDurationMilliseconds + * @retval playback duration in MS + */ + long GetDurationMilliseconds(void); + /** + * @fn ResetFirstFrame + */ + void ResetFirstFrame(void); + /** + * @fn GetVideoSize + * @param[out] w width video width + * @param[out] h height video height + */ + void GetVideoSize(int &width, int &height); + /** + * @fn SetVideoRectangle + * @param[in] x x co-ordinate of display rectangle + * @param[in] y y co-ordinate of display rectangle + * @param[in] w width of display rectangle + * @param[in] h height of display rectangle + */ + void SetVideoRectangle(int x, int y, int w, int h); + /** + * @brief Un-pauses the pipeline and notifies the player of the buffer end event. + * @param[in] forceStop True to force stop buffering. + * @param[out] isPlaying Indicates whether playback is ongoing. + * @return True if buffering was stopped successfully, false otherwise. + */ + bool StopBuffering(bool forceStop, bool &isPlaying); + /** + * @brief Gets the CC decoder handle. + * @return The CC decoder handle. + */ + unsigned long GetCCDecoderHandle(void); + /** + * @brief Waits for the source setup. + * @param[in] mediaType The type of media stream. + * @return True if the source setup was successful, false otherwise. + */ + bool WaitForSourceSetup(int mediaType); + /** + * @brief Send PTS/DTS to downstream elements. + * @param[in] type The type of event. + * @param[in] ptr Pointer to the event data. + * @param[in] len Length of the event data. + * @param[in] fpts First PTS value. + * @param[in] fdts First DTS value. + * @param[in] fDuration Duration of the event. + * @param[in] fragmentPTSoffset Offset PTS value. + * @param[in] copy True to copy the event data. + * @param[in] initFragment True if this is an initialization fragment. + * @param[out] discontinuity Indicates whether there is a discontinuity. + * @param[out] notifyFirstBufferProcessed Indicates whether the first buffer was processed. + * @param[out] sendNewSegmentEvent Indicates whether to send a new segment event. + * @param[out] resetTrickUTC Indicates whether to reset the trick UTC. + * @param[out] firstBufferPushed Indicates whether the first buffer was pushed. + * @return True if the event was sent successfully, false otherwise. + */ + bool SendHelper(int type, const void *ptr, size_t len, double fpts, double fdts, double fDuration, double fragmentPTSoffset, bool copy, bool initFragment, bool &discontinuity, bool ¬ifyFirstBufferProcessed, bool &sendNewSegmentEvent, bool &resetTrickUTC, bool &firstBufferPushed); + /** + * @brief Pauses the injector. + */ + void PauseInjector(); + /** + * @brief Resumes the injector. + */ + void ResumeInjector(); + /** + * @brief Handles the video buffer sent event. + * @return True if the video buffer was handled successfully, false otherwise. + */ + bool HandleVideoBufferSent(); + /** + * @fn QueueProtectionEvent + * @param[in] formatType hls/dash format + * @param[in] protSystemId keysystem to be used + * @param[in] ptr initData DRM initialization data + * @param[in] len initDataSize DRM initialization data size + * @param[in] type Media type + */ + void QueueProtectionEvent(const std::string &formatType, const char *protSystemId, const void *initData, size_t initDataSize, int mediaType); + /** + * @brief Sets the playback rate in audio and video sink. + * @param[in] rate The playback rate to set. + * @return True if the playback rate was set successfully, false otherwise. + */ + bool SetPlayBackRate(double rate); + /** + * @brief Sets the audio volume or mute/unmute. + */ + void SetVolumeOrMuteUnMute(void); + /** + * @brief Sets the audio volume. + * @param[in] volume The volume level to set. + */ + void SetAudioVolume(int volume); + /** + * @brief Tears down the stream. + * @param[in] mediaType The type of media stream. + */ + void TearDownStream(int mediaType); + /** + * @brief Initializes the source for the player. + * @param[in] PlayerInstance The player instance. + * @param[in] source The source to initialize. + * @param[in] eMEDIATYPE_VIDEO The media type for video. + */ + void InitializeSourceForPlayer(void *PlayerInstance, void *source, int mediaType); + /** + * @brief Setup a Closed Caption control stream. + */ + void SetupClosedCaptionControlStream(); + /** + * @brief Sets up the stream. + * @param[in] streamId The ID of the stream to set up. + * @param[in] _this The instance of the player. + * @param[in] url The URL of the stream. + * @return An integer indicating the success or failure of the setup. + */ + int SetupStream(int streamId, void *_this, std::string url); + /** + * @brief Gets the video PTS value. + * @return The video PTS value. + */ + long long GetVideoPTS(void); + /** + * @brief Sets the callback for the first frame. + * @param[in] callback The callback function to be called on the first frame. + */ + void FirstFrameCallback(std::function callback); + /** + * @brief Sets the callback for stopping. + * @param[in] callback The callback function to be called when stopping. + */ + void StopCallback(std::function callback); + /** + * @brief Sets the callback for tearing down. + * @param[in] callback The callback function to be called when tearing down. + */ + void TearDownCallback(std::function callback); + /** + * @brief Notifies the first frame. + * @param[in] mediaType The type of media. + */ + void NotifyFirstFrame(int mediaType); + /** + * @brief Pauses GStreamer. + * @param[in] pause Indicates whether to pause or resume. + * @param[in] forceStopGstreamerPreBuffering Indicates whether to force stop GStreamer pre-buffering. + * @return True if the operation was successful, false otherwise. + */ + bool Pause(bool pause, bool forceStopGstreamerPreBuffering); + /** + * @brief Checks for PTS change with a timeout. + * @param[in] timeout The timeout value in milliseconds. + * @return True if a PTS change was detected within the timeout, false otherwise. + */ + bool CheckForPTSChangeWithTimeout(long timeout); + /** + * @brief Checks if the cache is empty for a given media type. + * @param[in] mediaType The type of media stream. + * @return True if the cache is empty, false otherwise. + */ + bool IsCacheEmpty(int mediaType); + /** + * @brief Resets the EOS signalled flag. + */ + void ResetEOSSignalledFlag(void); + /** + * @brief Checks if the pipeline is configured for a given media type. + * @param[in] type The type of media stream. + * @return True if the pipeline is configured, false otherwise. + */ + bool PipelineConfiguredForMedia(int type); + /** + * @brief Gets buffer control data for a given media type. + * @param[in] mediaType The type of media stream. + * @return True if the buffer control data was retrieved successfully, false otherwise. + */ + bool GetBufferControlData(int mediaType); + /** + * @brief Checks if the stream is ready for a given media type. + * @param[in] mediaType The type of media stream. + * @return True if the stream is ready, false otherwise. + */ + bool IsStreamReady(int mediaType); + /** + * @brief Signals a trick mode discontinuity. + */ + void SignalTrickModeDiscontinuity(); + /** + * @brief Processes discontinuity for a stream type. + * + * @param[in] mediaType The type of media stream. + * @param[in] streamFormat The format of the stream. + * @param[in] codecChange Indicates whether there is a codec change. + * @param[out] unblockDiscProcess Indicates whether to unblock the discontinuity process. + * @param[out] shouldHaltBuffering Indicates whether buffering should be halted. + * @return True if the discontinuity was processed successfully, false otherwise. + */ + bool CheckDiscontinuity(int mediaType, int streamFormat, bool codecChange, bool &unblockDiscProcess, bool &shouldHaltBuffering); + /** + * @brief Sets up the stream for the interface player. + * @param[in] streamId The ID of the stream to set up. + * @param[in] url The URL of the stream. + * @return An integer indicating the success or failure of the setup. + */ + int InterfacePlayer_SetupStream(int streamId, std::string url); + /** + * @brief Triggers an event. + * + * This function triggers the specified event with the given data. + * + * @param[in] event The event to trigger. + * @param[in] data The data associated with the event. + */ + void TriggerEvent(InterfaceCB event, int data); + /** + * @brief Triggers an event. + * @param[in] event The event to trigger. + */ + void TriggerEvent(InterfaceCB event); + /** + * @brief Disables the decoder handle notification. + */ + void DisableDecoderHandleNotified(); + /* + *@brief register busMessageCallback + */ + void RegisterBusEvent(const BusMessageCallback &callback) + { + busMessageCallback = callback; + } + /** + * @fn ClearProtectionEvent + */ + void ClearProtectionEvent(); + /** + * @brief Sets the seek position for the GStreamer pipeline. + * @param[in] positionSecs The position to seek to, in seconds. + */ + void SetSeekPosition(double positionSecs); + /** + * @brief Disconnects all signals associated with the GStreamer pipeline. + */ + void DisconnectSignals(); + /** + * @brief Removes a GStreamer timer task. + * @param[in,out] taskId Reference to the ID of the task to be removed. + * @param[in] timerName The name of the timer to be removed + */ + void TimerRemove(guint &taskId, const char *timerName); + /** + * @brief get the encryption from application to share it with PlayReadyDecryptor Plugin + */ + void setEncryption(void *mEncrypt, void *mDRMSessionManager); + /** + * @brief Removes all active GStreamer probes. + */ + void RemoveProbes(); + /** + * @fn RemoveProbe + * @brief Remove probe for a particular media type + * @param[in] mediaType The media type for which the probe should be removed + */ + void RemoveProbe(int mediaType); + /** + * @brief Destroys the GStreamer pipeline. + */ + void DestroyPipeline(); + /** + * @brief Stops the GStreamer pipeline. + * + * @param[in] keepLastFrame Whether to retain the last displayed frame. + */ + void Stop(bool keepLastFrame); + /** + * @brief Sets the pending seek state. + * @param[in] state True to enable pending seek, false to disable. + */ + void SetPendingSeek(bool state); + /** + *@brief returns true if using rialto sink + */ + bool IsUsingRialtoSink(); + /** + * @brief Resets GStreamer event states for all tracks. + * This function updates the internal states for each track, + * marking the position as reset, disabling pending seeks, + * and clearing EOS and buffer flags. + */ + void ResetGstEvents(); + /** + * @brief Retrieves the trick play teardown status + */ + bool GetTrickTeardown(); + /** + * @brief Sets the trick play teardown state. + * @param[in] state True to enable trick play teardown, false to disable. + */ + void SetTrickTearDown(bool state); + /** + * @brief Flushes the GStreamer pipeline. + * @param[in] position The position to flush to, in seconds. + * @param[in] rate The playback rate. + * @param[in] shouldTearDown Whether to tear down the pipeline. + * @param[in] GstState The desired GStreamer pipeline state. + * @param[in] gstMediaFormat The media format for the pipeline. + */ + bool Flush(double position, int rate, bool shouldTearDown, bool isAppSeek); + /** + * @fn TimerAdd + * @param[in] funcPtr function to execute on timer expiry + * @param[in] repeatTimeout timeout between calls in ms + * @param[in] user_data data to pass to the timer function + * @param[in] timerName name of the timer being added + * @param[out] taskId id of the timer to be returned + */ + void TimerAdd(GSourceFunc funcPtr, int repeatTimeout, guint &taskId, gpointer user_data, const char *timerName = nullptr); + /** + * @fn TimerIsRunning + * @param[in] taskId id of the timer to be removed + * @return true - timer is currently running + */ + bool TimerIsRunning(guint &taskId); + /** + * @fn NotifyEOS + */ + void NotifyEOS(); + /** + * @fn NotifyFragmentCachingComplete + */ + void NotifyFragmentCachingComplete(); + /** + * @fn EndOfStreamReached + * @param[in] type stream type + */ + void EndOfStreamReached(int mediaType, bool &shouldHaltBuffering); + /** + * @brief Signals the subtitle clock. + * + * @param[in] vPTS The presentation timestamp. + * @param[in] state The state to set for the subtitle clock. + * @return True if the operation was successful, false otherwise. + */ + bool SignalSubtitleClock(gint64 vPTS, bool state); + /** + * @brief Initializes Player GStreamer plugins. + * + * This function initializes the necessary plugins for Player GStreamer. + */ + static void InitializePlayerGstreamerPlugins(); + /** + * @brief Dumps diagnostic information. + */ + void DumpDiagnostics(); + /** + * @brief Enables GStreamer debug logging. + * @param[in] debugLevel The level of debug logging to enable. + */ + void EnableGstDebugLogging(std::string debugLevel); + /**Add commentMore actions + * @brief Gets the monitor AV state. + * @return A pointer to the MonitorAVState structure containing the AV status or nullptr. + */ + const MonitorAVState& GetMonitorAVState(); + + private: + InterfacePlayerPriv *interfacePlayerPriv; +}; +struct data +{ + bool StreamReady; + double ElapsedSeconds; + bool GstWaitingForData; +}; + +#endif // INTERFACE_PLAYER_H diff --git a/middleware/OSX/OSxSetup.md b/middleware/OSX/OSxSetup.md new file mode 100644 index 000000000..0c4c6b47d --- /dev/null +++ b/middleware/OSX/OSxSetup.md @@ -0,0 +1,61 @@ +--- + +# PLAYER on Mac OS X + +This document contains the instructions to setup and debug stand alone PLAYER (plyr-cli) on Mac OS X. + +## Install dependencies + +**1. Install XCode from the Apple Store** + +**2. Install XCode Command Line Tools** + +This is required for MacOS version < 10.15 + +``` +xcode-select --install +sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_.pkg -target / +``` + +For MacOS 10.15 & above, we can check the SDK install path as +``` +xcrun --sdk macosx --show-sdk-path +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk +``` + + +##Build and execute aamp-cli +**1. Open middleware.xcodeproj in Xcode** + +``` +git clone "https://code.rdkcentral.com/r/rdk/components/generic/aamp" -b dev_sprint +cd aamp/middleware/; bash install-middleware.sh +``` + +**2. Build the code** + +``` + Start XCode, open build/middleware/Middleware.xcodeproj project file + Product -> Build +``` +If you see the error 'No CMAKE_C_COMPILER could be found.' when running osxbuild.sh, check that your installed cmake version matches the minimum required version shown earlier. +Even after updating the CMake version if you still see the above error, then run "sudo xcode-select --reset" and then execute the "osxbuild.sh" this fixes the issue. + + +**3. Select target to execute** + +``` + Product -> Scheme -> Choose Scheme + player-cli +``` + +**4. Execute** + +While executing if you face the below MacOS warning then please follow the below steps to fix it. +"Example warning: Machine runs macOS 10.15.7, which is lower than player-cli's minimum deployment target of 11.1. Change your project's minimum deployment target or upgrade machine’s version of macOS." +Click "Middleware" project and lower the "macOS Deployment Target" version. For example: Change it to 10.11 then run the player-cli, it will work. + + +``` +Product -> Run +``` diff --git a/middleware/OSX/patches/0009-qtdemux-tm_gst-1.16.patch b/middleware/OSX/patches/0009-qtdemux-tm_gst-1.16.patch new file mode 100644 index 000000000..f1e75ba5d --- /dev/null +++ b/middleware/OSX/patches/0009-qtdemux-tm_gst-1.16.patch @@ -0,0 +1,168 @@ +From f4b2ed637af04b53f81db8fe961cfb646c610714 Mon Sep 17 00:00:00 2001 +From: Balaji Selvam +Date: Thu, 30 Jan 2020 15:01:08 +0000 +Subject: [PATCH] Subject: [PATCH] XRE-12038 - [AAMP] FF/REW support for DASH + content + +Add support for retimestamping in qtdemux based on a +custom event which is sent only when aamp is used. + +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. + +Signed-off-by: Christo Joseph +--- + gst/isomp4/qtdemux.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++-- + gst/isomp4/qtdemux.h | 6 ++++ + 2 files changed, 98 insertions(+), 3 deletions(-) + +diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c +index a99fcaa..1fee819 100644 +--- a/gst/isomp4/qtdemux.c ++++ b/gst/isomp4/qtdemux.c +@@ -678,6 +678,11 @@ gst_qtdemux_init (GstQTDemux * qtdemux) + GST_OBJECT_FLAG_SET (qtdemux, GST_ELEMENT_FLAG_INDEXABLE); + + gst_qtdemux_reset (qtdemux, TRUE); ++ ++ qtdemux->aamp_base_pts = GST_CLOCK_TIME_NONE; ++ qtdemux->aamp_base_pts_override = GST_CLOCK_TIME_NONE; ++ qtdemux->aamp_override_enabled = FALSE; ++ qtdemux->aamp_rate = 1.0; + } + + static void +@@ -2382,10 +2387,13 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + GST_DEBUG_OBJECT (demux, "Pushing newseg %" GST_SEGMENT_FORMAT, &segment); + + /* map segment to internal qt segments and push on each stream */ +- if (QTDEMUX_N_STREAMS (demux)) { ++ if (QTDEMUX_N_STREAMS (demux) && !demux->aamp_override_enabled) { + demux->need_segment = TRUE; + gst_qtdemux_check_send_pending_segment (demux); + } ++ else if(demux->aamp_override_enabled) { ++ GST_WARNING_OBJECT (demux, "ignore newsegment %" GST_SEGMENT_FORMAT, &segment); ++ } + + /* clear leftover in current segment, if any */ + gst_adapter_clear (demux->adapter); +@@ -2502,6 +2510,37 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + + goto drop; + } ++ case GST_EVENT_CUSTOM_DOWNSTREAM: ++ { ++ const GstStructure *structure = gst_event_get_structure(event); ++ if (gst_structure_has_name(structure, "aamp_override")) ++ { ++ demux->aamp_base_pts = GST_CLOCK_TIME_NONE; ++ demux->aamp_rate = g_value_get_float(gst_structure_get_value(structure, "rate")); ++ demux->aamp_override_enabled = g_value_get_boolean(gst_structure_get_value(structure, "enable")); ++ g_print("%s:%d - aamp_override - enabled = %d, rate %f \n", __FUNCTION__, __LINE__, demux->aamp_override_enabled, demux->aamp_rate); ++ if (demux->aamp_override_enabled) ++ { ++ const GValue * basePTSVal = gst_structure_get_value(structure, "basePTS"); ++ if (basePTSVal) ++ { ++ demux->aamp_base_pts_override = g_value_get_uint64( basePTSVal); ++ g_print("%s:%d - aamp_base_pts_override %" G_GUINT64_FORMAT "\n", __FUNCTION__, __LINE__, demux->aamp_base_pts_override); ++ } ++ else ++ { ++ demux->aamp_base_pts_override = GST_CLOCK_TIME_NONE; ++ } ++ } ++ else ++ { ++ demux->aamp_base_pts_override = GST_CLOCK_TIME_NONE; ++ } ++ gst_event_unref (event); ++ goto drop; ++ } ++ break; ++ } + default: + break; + } +@@ -6226,8 +6265,58 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, + goto exit; + } + +- GST_BUFFER_DTS (buf) = dts; +- GST_BUFFER_PTS (buf) = pts; ++ if (G_LIKELY (!qtdemux->aamp_override_enabled)) ++ { ++ GST_BUFFER_DTS (buf) = dts; ++ GST_BUFFER_PTS (buf) = pts; ++ } ++ else ++ { ++ gdouble rate = qtdemux->aamp_rate; ++ if(GST_CLOCK_TIME_NONE == qtdemux->aamp_base_pts) ++ { ++ GstSegment segment; ++ GST_BUFFER_DTS (buf) = dts; ++ GST_BUFFER_PTS (buf) = pts; ++ if (GST_CLOCK_TIME_NONE == qtdemux->aamp_base_pts_override) ++ { ++ qtdemux->aamp_base_pts = pts; ++ } ++ else ++ { ++ qtdemux->aamp_base_pts = qtdemux->aamp_base_pts_override; ++ } ++ g_print("%s:%d - aamp %f first pts %"G_GUINT64_FORMAT" ms\n", ++ __FUNCTION__, __LINE__, rate, GST_TIME_AS_MSECONDS(pts)); ++ gst_segment_init(&segment, GST_FORMAT_TIME); ++ segment.start = 0; ++ GstEvent* event = gst_event_new_segment(&segment); ++ if (!gst_pad_push_event(stream->pad, event)) ++ { ++ g_print("%s: gst_pad_push_event segment error\n", __FUNCTION__); ++ } ++ } ++ if ( rate > 0 ) ++ { ++ if ((pts < qtdemux->aamp_base_pts) || (dts < qtdemux->aamp_base_pts)) ++ { ++ gst_buffer_unref(buf); ++ goto exit; ++ } ++ GST_BUFFER_DTS (buf) = ( dts - qtdemux->aamp_base_pts)/rate; ++ GST_BUFFER_PTS (buf) = ( pts - qtdemux->aamp_base_pts)/rate; ++ GST_DEBUG_OBJECT (qtdemux, "aamp_trickmode %f orig pts %"G_GUINT64_FORMAT" restamped pts %"G_GUINT64_FORMAT" ms\n", ++ rate, GST_TIME_AS_MSECONDS(pts), GST_TIME_AS_MSECONDS(GST_BUFFER_PTS (buf))); ++ } ++ else ++ { ++ rate = -rate; ++ GST_BUFFER_DTS (buf) = ( qtdemux->aamp_base_pts - dts)/rate; ++ GST_BUFFER_PTS (buf) = ( qtdemux->aamp_base_pts - pts)/rate; ++ GST_DEBUG_OBJECT (qtdemux, "aamp_trickmode %f orig pts %"G_GUINT64_FORMAT" restamped pts %"G_GUINT64_FORMAT" ms\n", ++ -rate, GST_TIME_AS_MSECONDS(pts), GST_TIME_AS_MSECONDS(GST_BUFFER_PTS (buf))); ++ } ++ } + GST_BUFFER_DURATION (buf) = duration; + GST_BUFFER_OFFSET (buf) = -1; + GST_BUFFER_OFFSET_END (buf) = -1; +diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h +index 83a050a..66d79d1 100644 +--- a/gst/isomp4/qtdemux.h ++++ b/gst/isomp4/qtdemux.h +@@ -250,6 +250,12 @@ struct _GstQTDemux { + * fields. */ + gboolean received_seek; + gboolean first_moof_already_parsed; ++ ++ /*Trick play support based on custom event*/ ++ GstClockTime aamp_base_pts; ++ GstClockTime aamp_base_pts_override; ++ gdouble aamp_rate; ++ gboolean aamp_override_enabled; + }; + + struct _GstQTDemuxClass { +-- +2.14.2 diff --git a/middleware/OSX/patches/0013-qtdemux-remove-override-segment-event_gst-1.16.patch b/middleware/OSX/patches/0013-qtdemux-remove-override-segment-event_gst-1.16.patch new file mode 100644 index 000000000..a2e7cbbfe --- /dev/null +++ b/middleware/OSX/patches/0013-qtdemux-remove-override-segment-event_gst-1.16.patch @@ -0,0 +1,34 @@ +From fedced20429c446bd7ca208737a86d44114cf106 Mon Sep 17 00:00:00 2001 +From: Balaji Selvam +Date: Thu, 30 Jan 2020 15:06:24 +0000 +Subject: [PATCH] Subject: [PATCH]: 4K VOD AAMP PTS error after + seek + +qtdemux overrides segment.position value of a +received SEGMENT event. This was resulting in PTS error from brcmvideodecoder +since AAMP sends a GST_EVENT_SEGMENT right after flushing gst pipeline for seek. + +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. + +Signed-off-by: Vinish K B +--- + gst/isomp4/qtdemux.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c +index e1a6ebf..5750031 100644 +--- a/gst/isomp4/qtdemux.c ++++ b/gst/isomp4/qtdemux.c +@@ -2377,7 +2377,7 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + if (!demux->upstream_format_is_time) { + /* accept upstream's notion of segment and distribute along */ + segment.format = GST_FORMAT_TIME; +- segment.position = segment.time = segment.start; ++ segment.time = segment.start; + segment.duration = demux->segment.duration; + segment.base = gst_segment_to_running_time (&demux->segment, + GST_FORMAT_TIME, demux->segment.position); +-- +2.14.2 diff --git a/middleware/OSX/patches/0014-qtdemux-clear-crypto-info-on-trak-switch_gst-1.16.patch b/middleware/OSX/patches/0014-qtdemux-clear-crypto-info-on-trak-switch_gst-1.16.patch new file mode 100644 index 000000000..d7f5af820 --- /dev/null +++ b/middleware/OSX/patches/0014-qtdemux-clear-crypto-info-on-trak-switch_gst-1.16.patch @@ -0,0 +1,49 @@ +From ee54584250a3cf7045d7136bf4e722d213623b00 Mon Sep 17 00:00:00 2001 +From: Balaji Selvam +Date: Thu, 30 Jan 2020 16:10:18 +0000 +Subject: [PATCH] Subject: [PATCH] - [AAMP] Crash on switching from + encrypted period to clear period + +Flush crypto_info stored previous trak's senc on trak change + +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. + +Signed-off-by: Christo Joseph +--- + gst/isomp4/qtdemux.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c +index 5750031..e3d6b36 100644 +--- a/gst/isomp4/qtdemux.c ++++ b/gst/isomp4/qtdemux.c +@@ -2680,6 +2680,17 @@ gst_qtdemux_stream_flush_samples_data (QtDemuxStream * stream) + stream->duration_last_moof = 0; + } + ++static void gst_qtdemux_stream_flush_crypto_info (QtDemuxStream * stream) ++{ ++ QtDemuxCencSampleSetInfo *info = ++ (QtDemuxCencSampleSetInfo *) stream->protection_scheme_info; ++ if (info && info->crypto_info) { ++ //GST_WARNING_OBJECT (demux, "Flush existing crypto_info"); ++ g_ptr_array_free (info->crypto_info, TRUE); ++ info->crypto_info = NULL; ++ } ++} ++ + static void + gst_qtdemux_stream_clear (QtDemuxStream * stream) + { +@@ -2727,6 +2738,7 @@ gst_qtdemux_stream_clear (QtDemuxStream * stream) + g_queue_clear (&stream->protection_scheme_event_queue); + gst_qtdemux_stream_flush_segments_data (stream); + gst_qtdemux_stream_flush_samples_data (stream); ++ gst_qtdemux_stream_flush_crypto_info (stream); + } + + static void +-- +2.14.2 diff --git a/middleware/OSX/patches/0021-qtdemux-tm-multiperiod_gst-1.16.patch b/middleware/OSX/patches/0021-qtdemux-tm-multiperiod_gst-1.16.patch new file mode 100644 index 000000000..0a58a3c73 --- /dev/null +++ b/middleware/OSX/patches/0021-qtdemux-tm-multiperiod_gst-1.16.patch @@ -0,0 +1,124 @@ +From f6cc4ea70e98f2c062c3c1bb4c9f01375a90e6e8 Mon Sep 17 00:00:00 2001 +From: Balaji Selvam +Date: Thu, 30 Jan 2020 19:17:45 +0000 +Subject: [PATCH] Subject: [PATCH] [DASH] Support iframe trickmode + for multi period assets. + +Support smooth PTS restamping across period boundaries +by handling discontinuity by a new custom event. + +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. + +Signed-off-by: Christo Joseph +--- + gst/isomp4/qtdemux.c | 41 ++++++++++++++++++++++++++++++----------- + gst/isomp4/qtdemux.h | 2 ++ + 2 files changed, 32 insertions(+), 11 deletions(-) + +diff --git a/gst/isomp4/qtdemux.c b/gst/isomp4/qtdemux.c +index 70ba6fa..e0528f8 100644 +--- a/gst/isomp4/qtdemux.c ++++ b/gst/isomp4/qtdemux.c +@@ -684,6 +684,8 @@ gst_qtdemux_init (GstQTDemux * qtdemux) + qtdemux->aamp_override_enabled = FALSE; + qtdemux->aamp_player_enabled = FALSE; + qtdemux->aamp_rate = 1.0; ++ qtdemux->aamp_last_pts = GST_CLOCK_TIME_NONE; ++ qtdemux->aamp_pts_offset = 0; + } + + static void +@@ -2517,6 +2519,8 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + if (gst_structure_has_name(structure, "aamp_override")) + { + demux->aamp_base_pts = GST_CLOCK_TIME_NONE; ++ demux->aamp_last_pts = GST_CLOCK_TIME_NONE; ++ demux->aamp_pts_offset = 0; + demux->aamp_rate = g_value_get_float(gst_structure_get_value(structure, "rate")); + demux->aamp_override_enabled = g_value_get_boolean(gst_structure_get_value(structure, "enable")); + demux->aamp_player_enabled = g_value_get_boolean(gst_structure_get_value(structure, "aampplayer")); +@@ -2541,6 +2545,17 @@ gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent, + gst_event_unref (event); + goto drop; + } ++ else if (gst_structure_has_name(structure, "aamp-tm-disc")) ++ { ++ demux->aamp_base_pts = GST_CLOCK_TIME_NONE; ++ if (GST_CLOCK_TIME_NONE != demux->aamp_last_pts) ++ { ++ guint fps = g_value_get_uint(gst_structure_get_value(structure, "fps")); ++ demux->aamp_pts_offset = demux->aamp_last_pts + (1000*GST_MSECOND/fps); ++ } ++ gst_event_unref (event); ++ goto drop; ++ } + break; + } + default: +@@ -6302,14 +6317,17 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, + { + qtdemux->aamp_base_pts = qtdemux->aamp_base_pts_override; + } +- g_print("%s:%d - aamp %f first pts %"G_GUINT64_FORMAT" ms\n", +- __FUNCTION__, __LINE__, rate, GST_TIME_AS_MSECONDS(pts)); +- gst_segment_init(&segment, GST_FORMAT_TIME); +- segment.start = 0; +- GstEvent* event = gst_event_new_segment(&segment); +- if (!gst_pad_push_event(stream->pad, event)) ++ g_print("%s:%d - aamp rate %f first pts %" G_GUINT64_FORMAT " ms aamp_pts_offset %" G_GUINT64_FORMAT " ms\n", ++ __FUNCTION__, __LINE__, rate, GST_TIME_AS_MSECONDS(pts), GST_TIME_AS_MSECONDS(qtdemux->aamp_pts_offset)); ++ if (G_LIKELY (0 == qtdemux->aamp_pts_offset)) + { +- g_print("%s: gst_pad_push_event segment error\n", __FUNCTION__); ++ gst_segment_init(&segment, GST_FORMAT_TIME); ++ segment.start = 0; ++ GstEvent* event = gst_event_new_segment(&segment); ++ if (!gst_pad_push_event(stream->pad, event)) ++ { ++ g_print("%s: gst_pad_push_event segment error\n", __FUNCTION__); ++ } + } + } + if ( rate > 0 ) +@@ -6319,19 +6337,20 @@ gst_qtdemux_decorate_and_push_buffer (GstQTDemux * qtdemux, + gst_buffer_unref(buf); + goto exit; + } +- GST_BUFFER_DTS (buf) = ( dts - qtdemux->aamp_base_pts)/rate; +- GST_BUFFER_PTS (buf) = ( pts - qtdemux->aamp_base_pts)/rate; ++ GST_BUFFER_DTS (buf) = qtdemux->aamp_pts_offset + ( dts - qtdemux->aamp_base_pts)/rate; ++ GST_BUFFER_PTS (buf) = qtdemux->aamp_pts_offset + ( pts - qtdemux->aamp_base_pts)/rate; + GST_DEBUG_OBJECT (qtdemux, "aamp_trickmode %f orig pts %"G_GUINT64_FORMAT" restamped pts %"G_GUINT64_FORMAT" ms\n", + rate, GST_TIME_AS_MSECONDS(pts), GST_TIME_AS_MSECONDS(GST_BUFFER_PTS (buf))); + } + else + { + rate = -rate; +- GST_BUFFER_DTS (buf) = ( qtdemux->aamp_base_pts - dts)/rate; +- GST_BUFFER_PTS (buf) = ( qtdemux->aamp_base_pts - pts)/rate; ++ GST_BUFFER_DTS (buf) = qtdemux->aamp_pts_offset + ( qtdemux->aamp_base_pts - dts)/rate; ++ GST_BUFFER_PTS (buf) = qtdemux->aamp_pts_offset + ( qtdemux->aamp_base_pts - pts)/rate; + GST_DEBUG_OBJECT (qtdemux, "aamp_trickmode %f orig pts %"G_GUINT64_FORMAT" restamped pts %"G_GUINT64_FORMAT" ms\n", + -rate, GST_TIME_AS_MSECONDS(pts), GST_TIME_AS_MSECONDS(GST_BUFFER_PTS (buf))); + } ++ qtdemux->aamp_last_pts = GST_BUFFER_PTS (buf); + } + GST_BUFFER_DURATION (buf) = duration; + GST_BUFFER_OFFSET (buf) = -1; +diff --git a/gst/isomp4/qtdemux.h b/gst/isomp4/qtdemux.h +index ed37c52..906d442 100644 +--- a/gst/isomp4/qtdemux.h ++++ b/gst/isomp4/qtdemux.h +@@ -257,6 +257,8 @@ struct _GstQTDemux { + GstClockTime aamp_base_pts_override; + gdouble aamp_rate; + gboolean aamp_override_enabled; ++ GstClockTime aamp_last_pts; ++ GstClockTime aamp_pts_offset; + }; + + struct _GstQTDemuxClass { +-- +2.14.2 diff --git a/middleware/OSX/patches/JsonHelper.patch b/middleware/OSX/patches/JsonHelper.patch new file mode 100644 index 000000000..46794d019 --- /dev/null +++ b/middleware/OSX/patches/JsonHelper.patch @@ -0,0 +1,38 @@ +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. +--- a/JsonHelper.cpp 2024-03-07 16:33:38.158962188 +0000 ++++ b/JsonHelper.cpp 2024-03-07 17:03:08.693947151 +0000 +@@ -400,17 +400,17 @@ + + template void JsonHelper::put(std::string const&, std::string const&); + template void JsonHelper::put(std::string const&, std::uint32_t const&); +-template void JsonHelper::put(std::string const&, std::uint64_t const&); ++template <> void JsonHelper::put(std::string const&, std::uint64_t const&); + template void JsonHelper::put(std::string const&, std::int32_t const&); +-template void JsonHelper::put(std::string const&, std::int64_t const&); ++template <> void JsonHelper::put(std::string const&, std::int64_t const&); + template void JsonHelper::put(std::string const&, double const&); + template void JsonHelper::put(std::string const&, bool const&); + + template void JsonHelper::appendArrayElem(std::string const&); + template void JsonHelper::appendArrayElem(std::uint32_t const&); +-template void JsonHelper::appendArrayElem(std::uint64_t const&); ++template <> void JsonHelper::appendArrayElem(std::uint64_t const&); + template void JsonHelper::appendArrayElem(std::int32_t const&); +-template void JsonHelper::appendArrayElem(std::int64_t const&); ++template <> void JsonHelper::appendArrayElem(std::int64_t const&); + template void JsonHelper::appendArrayElem(double const&); + template void JsonHelper::appendArrayElem(bool const&); + +@@ -431,8 +431,8 @@ + template bool JsonHelper::getArrayElem(JsonPath const&, int) const; + + template void JsonHelper::putArray(std::string const&, std::vector const&); +-template void JsonHelper::putArray(std::string const&, std::vector const&); +-template void JsonHelper::putArray(std::string const&, std::vector const&); ++template <> void JsonHelper::putArray(std::string const&, std::vector const&); ++template <> void JsonHelper::putArray(std::string const&, std::vector const&); + template void JsonHelper::putArray(std::string const&, std::vector const&); + template void JsonHelper::putArray(std::string const&, std::vector const&); + template void JsonHelper::putArray(std::string const&, std::vector const&); diff --git a/middleware/OSX/patches/RDKLogo.png b/middleware/OSX/patches/RDKLogo.png new file mode 100644 index 000000000..fc49a06bf Binary files /dev/null and b/middleware/OSX/patches/RDKLogo.png differ diff --git a/middleware/OSX/patches/RDKLogoBlack.png b/middleware/OSX/patches/RDKLogoBlack.png new file mode 100644 index 000000000..97ab75813 Binary files /dev/null and b/middleware/OSX/patches/RDKLogoBlack.png differ diff --git a/middleware/OSX/patches/RDKLogoGreen.png b/middleware/OSX/patches/RDKLogoGreen.png new file mode 100644 index 000000000..cf266988f Binary files /dev/null and b/middleware/OSX/patches/RDKLogoGreen.png differ diff --git a/middleware/OSX/patches/subttxrend-app-packet.patch b/middleware/OSX/patches/subttxrend-app-packet.patch new file mode 100644 index 000000000..a8f57b2ac --- /dev/null +++ b/middleware/OSX/patches/subttxrend-app-packet.patch @@ -0,0 +1,39 @@ +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. +diff --git a/subttxrend-protocol/include/Packet.hpp b/subttxrend-protocol/include/Packet.hpp +index cc5d9ad..a1ff731 100644 +--- a/subttxrend-protocol/include/Packet.hpp ++++ b/subttxrend-protocol/include/Packet.hpp +@@ -65,11 +65,12 @@ public: + RESUME = 12, + MUTE = 13, + UNMUTE = 14, +- TTML_INFO = 15, +- WEBVTT_SELECTION = 16, +- WEBVTT_DATA = 17, +- WEBVTT_TIMESTAMP = 18, ++ WEBVTT_SELECTION = 15, ++ WEBVTT_DATA = 16, ++ WEBVTT_TIMESTAMP = 17, ++ SET_CC_ATTRIBUTES = 18, + FLUSH = 19, ++ TTML_INFO = 20, + + MAX, + INVALID = 0xFFFFFFFF, +@@ -252,11 +253,12 @@ const std::array(Packet::Type::MAX)> packetTypeStr + "RESUME", + "MUTE", + "UNMUTE", +- "TTML_INFO", + "WEBVTT_SELECTION", + "WEBVTT_DATA", + "WEBVTT_TIMESTAMP", +- "FLUSH" ++ "SET_CC_ATTRIBUTES", ++ "FLUSH", ++ "TTML_INFO" + }; + + inline std::ostream& operator<<(std::ostream& out, Packet::Type packetType) diff --git a/middleware/OSX/patches/subttxrend-app-ubuntu_24_04_build.patch b/middleware/OSX/patches/subttxrend-app-ubuntu_24_04_build.patch new file mode 100644 index 000000000..4851e6d90 --- /dev/null +++ b/middleware/OSX/patches/subttxrend-app-ubuntu_24_04_build.patch @@ -0,0 +1,86 @@ +Subject: [PATCH] Subject: [PATCH] RDKAAMP-2907 Fix compilation + warnings in Ubuntu 24.04 + +Fix compilation issues with subtec +and its dependent components + +Signed-off-by: Vinish K B +--- +diff --git a/dvbsubdecoder/include/dvbsubdecoder/BasicAllocator.hpp b/dvbsubdecoder/include/dvbsubdecoder/BasicAllocator.hpp +index 7fcdba3..f0218da 100644 +--- a/dvbsubdecoder/include/dvbsubdecoder/BasicAllocator.hpp ++++ b/dvbsubdecoder/include/dvbsubdecoder/BasicAllocator.hpp +@@ -27,6 +27,7 @@ + #define DVBSUBDECODER_BASICALLOCATOR_HPP_ + + #include ++#include + + #include "Allocator.hpp" + +diff --git a/subttxrend-common/src/StringUtils.cpp b/subttxrend-common/src/StringUtils.cpp +index b295150..4e8aa02 100644 +--- a/subttxrend-common/src/StringUtils.cpp ++++ b/subttxrend-common/src/StringUtils.cpp +@@ -47,11 +47,11 @@ std::string StringUtils::trim(const std::string& value) + + trimmed.erase(trimmed.begin(), + std::find_if(trimmed.begin(), trimmed.end(), +- std::not1(std::ptr_fun(isSpace)))); ++ [](unsigned char c){ return !std::isspace(c); })); + + trimmed.erase( + std::find_if(trimmed.rbegin(), trimmed.rend(), +- std::not1(std::ptr_fun(isSpace))).base(), ++ [](unsigned char c){ return !std::isspace(c); }).base(), + trimmed.end()); + + return trimmed; +diff --git a/subttxrend-gfx/src/PrerenderedFontImpl.hpp b/subttxrend-gfx/src/PrerenderedFontImpl.hpp +index 63500f4..74618cc 100644 +--- a/subttxrend-gfx/src/PrerenderedFontImpl.hpp ++++ b/subttxrend-gfx/src/PrerenderedFontImpl.hpp +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + + #include + #include FT_FREETYPE_H +diff --git a/subttxrend-gfx/waylandcpp/src/waylandcpp-client/File.cpp b/subttxrend-gfx/waylandcpp/src/waylandcpp-client/File.cpp +index 27c6ae6..a41415d 100644 +--- a/subttxrend-gfx/waylandcpp/src/waylandcpp-client/File.cpp ++++ b/subttxrend-gfx/waylandcpp/src/waylandcpp-client/File.cpp +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + + namespace waylandcpp + { +diff --git a/subttxrend-ttml/src/DataDumper.h b/subttxrend-ttml/src/DataDumper.h +index c643eeb..207d7ab 100644 +--- a/subttxrend-ttml/src/DataDumper.h ++++ b/subttxrend-ttml/src/DataDumper.h +@@ -26,6 +26,7 @@ + + #include + #include ++#include + + namespace subttxrend + { +diff --git a/subttxrend-ttml/src/Parser/StyleSet.cpp b/subttxrend-ttml/src/Parser/StyleSet.cpp +index 92fd2f3..8d41db0 100644 +--- a/subttxrend-ttml/src/Parser/StyleSet.cpp ++++ b/subttxrend-ttml/src/Parser/StyleSet.cpp +@@ -32,6 +32,7 @@ + #include + #include + #include ++#include + + namespace subttxrend + { diff --git a/middleware/OSX/patches/subttxrend-app-xkbcommon.patch b/middleware/OSX/patches/subttxrend-app-xkbcommon.patch new file mode 100644 index 000000000..0e334baaa --- /dev/null +++ b/middleware/OSX/patches/subttxrend-app-xkbcommon.patch @@ -0,0 +1,161 @@ +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. +diff --git a/subttxrend-app/CMakeLists.txt b/subttxrend-app/CMakeLists.txt +index 5ef5616..b6180d8 100644 +--- a/subttxrend-app/CMakeLists.txt ++++ b/subttxrend-app/CMakeLists.txt +@@ -63,6 +63,9 @@ find_package(LibSubTtxRendTtxt REQUIRED) + find_package(LibSubTtxRendTtml REQUIRED) + find_package(LibSubTtxRendWebvtt REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) + + # + # Include directories +@@ -80,6 +83,7 @@ include_directories(${LIBSUBTTXRENDCC_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDTTXT_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDTTML_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDWEBVTT_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Definitions (flags etc.) +diff --git a/subttxrend-cc/CMakeLists.txt b/subttxrend-cc/CMakeLists.txt +index 949921f..33f6cf8 100644 +--- a/subttxrend-cc/CMakeLists.txt ++++ b/subttxrend-cc/CMakeLists.txt +@@ -50,6 +50,9 @@ ENDIF(CMAKE_COMPILER_IS_GNUCXX) + find_package(LibSubTtxRendGfx REQUIRED) + find_package(LibSubTtxRendProtocol REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) + + # + # Include directories +@@ -57,6 +60,7 @@ find_package(Ipp2Utils REQUIRED CONFIG) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDPROTOCOL_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Public headers +diff --git a/subttxrend-dvbsub/CMakeLists.txt b/subttxrend-dvbsub/CMakeLists.txt +index a405d1b..8234a5d 100644 +--- a/subttxrend-dvbsub/CMakeLists.txt ++++ b/subttxrend-dvbsub/CMakeLists.txt +@@ -50,6 +50,10 @@ ENDIF(CMAKE_COMPILER_IS_GNUCXX) + find_package(LibSubTtxRendGfx REQUIRED) + find_package(LibDvbSubDecoder REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) ++ + + # + # Include directories +@@ -57,6 +61,7 @@ find_package(Ipp2Utils REQUIRED CONFIG) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) + include_directories(${LIBDVBSUBDECODER_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Documentation +diff --git a/subttxrend-scte/CMakeLists.txt b/subttxrend-scte/CMakeLists.txt +index 958bc5d..89f182b 100644 +--- a/subttxrend-scte/CMakeLists.txt ++++ b/subttxrend-scte/CMakeLists.txt +@@ -52,6 +52,10 @@ find_package(LibSubTtxRendGfx REQUIRED) + find_package(LibSubTtxRendProtocol REQUIRED) + find_package(ZLIB) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) ++ + + # + # Include directories +@@ -61,6 +65,7 @@ include_directories(${LIBSUBTTXRENDCOMMON_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDPROTOCOL_INCLUDE_DIRS}) + include_directories(${ZLIB_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Public headers +diff --git a/subttxrend-ttml/CMakeLists.txt b/subttxrend-ttml/CMakeLists.txt +index 6ecc66b..db7ff8a 100644 +--- a/subttxrend-ttml/CMakeLists.txt ++++ b/subttxrend-ttml/CMakeLists.txt +@@ -51,6 +51,10 @@ find_package(LibSubTtxRendCommon REQUIRED) + find_package(LibSubTtxRendGfx REQUIRED) + find_package(LibXml2 REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) ++ + + # + # Include directories +@@ -60,6 +64,7 @@ include_directories(${LIBSUBTTXRENDCOMMON_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) + include_directories(${LIBXML2_INCLUDE_DIRS}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Documentation +diff --git a/subttxrend-ttxt/CMakeLists.txt b/subttxrend-ttxt/CMakeLists.txt +index 7873230..9370671 100644 +--- a/subttxrend-ttxt/CMakeLists.txt ++++ b/subttxrend-ttxt/CMakeLists.txt +@@ -51,6 +51,9 @@ find_package(LibSubTtxRendCommon REQUIRED) + find_package(LibSubTtxRendGfx REQUIRED) + find_package(LibTtxDecoder REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) + + # + # Include directories +@@ -59,6 +62,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + include_directories(${LIBSUBTTXRENDCOMMON_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) + include_directories(${LIBTTXDECODER_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Documentation +diff --git a/subttxrend-webvtt/CMakeLists.txt b/subttxrend-webvtt/CMakeLists.txt +index 407c4c3..09ccd60 100644 +--- a/subttxrend-webvtt/CMakeLists.txt ++++ b/subttxrend-webvtt/CMakeLists.txt +@@ -37,6 +37,10 @@ ENDIF(CMAKE_COMPILER_IS_GNUCXX) + find_package(LibSubTtxRendCommon REQUIRED) + find_package(LibSubTtxRendGfx REQUIRED) + find_package(Ipp2Utils REQUIRED CONFIG) ++find_package(PkgConfig REQUIRED) ++ ++pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) ++ + + # + # Include directories +@@ -45,6 +49,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/Parser/include) + include_directories(${LIBSUBTTXRENDCOMMON_INCLUDE_DIRS}) + include_directories(${LIBSUBTTXRENDGFX_INCLUDE_DIRS}) ++include_directories(${XKBCOMMON_INCLUDE_DIRS}) + + # + # Documentation diff --git a/middleware/OSX/patches/websocket-ipplayer2-link.patch b/middleware/OSX/patches/websocket-ipplayer2-link.patch new file mode 100644 index 000000000..b6fd0157f --- /dev/null +++ b/middleware/OSX/patches/websocket-ipplayer2-link.patch @@ -0,0 +1,22 @@ +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. +diff --git a/src/ipp2/CMakeLists.txt b/src/ipp2/CMakeLists.txt +index 05cc1a9..bf25e95 100644 +--- a/src/ipp2/CMakeLists.txt ++++ b/src/ipp2/CMakeLists.txt +@@ -183,10 +183,10 @@ else(ASIO_PATH) + endif(ASIO_PATH) + + target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC pthread) +-target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${CURL_LIBRARIES}) +-target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${JSONCPP_LIBRARIES}) +-target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${LIBGIO_LIBRARIES}) +-target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${TINYXML2_LIBRARIES}) ++target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${CURL_LINK_LIBRARIES}) ++target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${JSONCPP_LINK_LIBRARIES}) ++target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${LIBGIO_LINK_LIBRARIES}) ++target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${TINYXML2_LINK_LIBRARIES}) + + if (NOT BUILD_PC) + target_link_libraries(${IPP2_UTILS_LIBNAME} PUBLIC ${LIBRDKLOGGER_LIBRARIES}) diff --git a/middleware/OSX/patches/websocket-ipplayer2-typescpp.patch b/middleware/OSX/patches/websocket-ipplayer2-typescpp.patch new file mode 100644 index 000000000..cb468f42a --- /dev/null +++ b/middleware/OSX/patches/websocket-ipplayer2-typescpp.patch @@ -0,0 +1,16 @@ +Source: COMCAST +Upstream-Status: Pending +Notice: Code in patch files takes the license of the source which is being patched. +diff --git a/src/ipp2/Types.cpp b/src/ipp2/Types.cpp +index 05ff8e2..8aa2db5 100644 +--- a/src/ipp2/Types.cpp ++++ b/src/ipp2/Types.cpp +@@ -28,6 +28,7 @@ + #include "EnumSerializer.h" + #include "Serializer.h" + #include "Utils.h" ++#include + + namespace ipp2 { + + diff --git a/middleware/OSX/patches/websocket-ipplayer2-ubuntu_24_04_build.patch b/middleware/OSX/patches/websocket-ipplayer2-ubuntu_24_04_build.patch new file mode 100644 index 000000000..22b696b8c --- /dev/null +++ b/middleware/OSX/patches/websocket-ipplayer2-ubuntu_24_04_build.patch @@ -0,0 +1,20 @@ +Subject: [PATCH] Subject: [PATCH] RDKAAMP-2907 Fix compilation + warnings in Ubuntu 24.04 + +Fix compilation issues with subtec +and its dependent components + +Signed-off-by: Vinish K B +--- +diff --git a/src/ipp2/clients/IpAddress.h b/src/ipp2/clients/IpAddress.h +index 3ff9a2f..4e0b925 100644 +--- a/src/ipp2/clients/IpAddress.h ++++ b/src/ipp2/clients/IpAddress.h +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + struct sockaddr_storage; + diff --git a/middleware/PlayerMetadata.hpp b/middleware/PlayerMetadata.hpp new file mode 100644 index 000000000..5c7035aad --- /dev/null +++ b/middleware/PlayerMetadata.hpp @@ -0,0 +1,54 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerMetadata.hpp + * @brief Context-free common utility functions. + */ +#ifndef PLAYER_METADATA_HPP +#define PLAYER_METADATA_HPP + +#include +#include "PlayerLogManager.h" + +// Global variable for player name +static std::string gPlayerName; + +/* + * @brief To set the player's name + * + */ +void SetPlayerName(const std::string& name) { + if(name != gPlayerName) + { + MW_LOG_INFO("Set Player Name[%s]",name.c_str()); + gPlayerName = name; + } +} + +/* + * @brief To get the player's name + * + */ +std::string GetPlayerName() { + MW_LOG_INFO("Get Player Name[%s]",gPlayerName.c_str()); + return gPlayerName; +} + +#endif // PLAYER_METADATA_HPP \ No newline at end of file diff --git a/middleware/PlayerScheduler.cpp b/middleware/PlayerScheduler.cpp new file mode 100644 index 000000000..d66fdde65 --- /dev/null +++ b/middleware/PlayerScheduler.cpp @@ -0,0 +1,249 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerScheduler.cpp + * @brief Class to schedule commands for async execution + */ + +#include "PlayerScheduler.h" +#include "PlayerLogManager.h" + +/** + * @brief PlayerScheduler Constructor + */ +PlayerScheduler::PlayerScheduler() : mTaskQueue(), mQMutex(), mQCond(), + mSchedulerRunning(false), mSchedulerThread(), mExMutex(), + mExLock(mExMutex, std::defer_lock), mNextTaskId(PLAYER_SCHEDULER_ID_DEFAULT), + mCurrentTaskId(PLAYER_TASK_ID_INVALID), mLockOut(false) +{ +} + +/** + * @brief PlayerScheduler Destructor + */ +PlayerScheduler::~PlayerScheduler() +{ + if (mSchedulerRunning) + { + StopScheduler(); + } +} + +static std::hash std_thread_hasher; + +std::size_t GetPlayerThreadID( const std::thread &t ) +{ + return std_thread_hasher( t.get_id() ); +} + +/** + * @brief To start scheduler thread + */ +void PlayerScheduler::StartScheduler() +{ + //Turn on thread for processing async operations + std::lock_guardlock(mQMutex); + mSchedulerThread = std::thread(std::bind(&PlayerScheduler::ExecuteAsyncTask, this)); + mSchedulerRunning = true; + MW_LOG_INFO("Thread created Async Worker [%zx]", GetPlayerThreadID(mSchedulerThread)); +} + +/** + * @brief To schedule a task to be executed later + */ +int PlayerScheduler::ScheduleTask(PlayerAsyncTaskObj obj) +{ + int id = PLAYER_TASK_ID_INVALID; + if (mSchedulerRunning) + { + std::lock_guardlock(mQMutex); + if (!mLockOut) + { + id = mNextTaskId++; + // Upper limit check + if (mNextTaskId >= PLAYER_SCHEDULER_ID_MAX_VALUE) + { + mNextTaskId = PLAYER_SCHEDULER_ID_DEFAULT; + } + obj.mId = id; + mTaskQueue.push_back(obj); + mQCond.notify_one(); + } + else + { + // Operation is skipped here, this might happen due to race conditions during normal operation, hence setting as info log + MW_LOG_INFO("Warning: Attempting to schedule a task when scheduler is locked out, skipping operation %s!!", obj.mTaskName.c_str()); + } + } + else + { + MW_LOG_ERR("Attempting to schedule a task when scheduler is not running, undefined behavior, task ignored:%s",obj.mTaskName.c_str()); + } + return id; +} + +/** + * @brief Executes scheduled tasks - invoked by thread + */ +void PlayerScheduler::ExecuteAsyncTask() +{ + std::unique_lockqueueLock(mQMutex); + while (mSchedulerRunning) + { + if (mTaskQueue.empty()) + { + mQCond.wait(queueLock); + } + else + { + /* + Take the execution lock before taking a task from the queue + otherwise this function could hold a task, out of the queue, + that cannot be deleted by RemoveAllTasks()! + Allow the queue to be modified while waiting.*/ + queueLock.unlock(); + std::lock_guardexecutionLock(mExMutex); + queueLock.lock(); + + //note: mTaskQueue could have been modified while waiting for execute permission + if (!mTaskQueue.empty()) + { + PlayerAsyncTaskObj obj = mTaskQueue.front(); + mTaskQueue.pop_front(); + if (obj.mId != PLAYER_TASK_ID_INVALID) + { + mCurrentTaskId = obj.mId; + MW_LOG_INFO("Found entry in function queue!!, task:%s. CurrentTaskId:%d ",obj.mTaskName.c_str(),mCurrentTaskId); + //Unlock so that new entries can be added to queue while function executes + queueLock.unlock(); + + MW_LOG_WARN("SchedulerTask Execution:%s taskId:%d",obj.mTaskName.c_str(),obj.mId); + //Execute function + obj.mTask(obj.mData); + //May be used in a wait() in future loops, it needs to be locked + queueLock.lock(); + } + else + { + MW_LOG_INFO("Scheduler found a task with invalid ID, skip task!"); + } + } + } + } + MW_LOG_INFO("Exited Async Worker Thread"); +} + +/** + * @brief To remove all scheduled tasks and prevent further tasks from scheduling + */ +void PlayerScheduler::RemoveAllTasks() +{ + std::lock_guardlock(mQMutex); + if(!mLockOut) + { + MW_LOG_WARN("The scheduler is active. An active task may continue to execute after this function exits. Call SuspendScheduler() prior to this function to prevent this."); + } + if (!mTaskQueue.empty()) + { + MW_LOG_WARN("Clearing up %d entries from mFuncQueue", (int)mTaskQueue.size()); + mTaskQueue.clear(); + } +} + +/** + * @brief To stop scheduler and associated resources + */ +void PlayerScheduler::StopScheduler() +{ + MW_LOG_WARN("Stopping Async Worker Thread"); + // Clean up things in queue + mSchedulerRunning = false; + + //allow StopScheduler() to be called without warning from a nonsuspended state and + //not cause an error in ResumeScheduler() below due to trying to unlock an unlocked lock + if(!mLockOut) + { + SuspendScheduler(); + } + + RemoveAllTasks(); + + //prevent possible deadlock where mSchedulerThread is waiting for mExLock/mExMutex + ResumeScheduler(); + mQCond.notify_one(); + if (mSchedulerThread.joinable()) + mSchedulerThread.join(); +} + +/** + * @brief To acquire execution lock for synchronisation purposes + */ +void PlayerScheduler::SuspendScheduler() +{ + mExLock.lock(); + std::lock_guardlock(mQMutex); + mLockOut = true; +} + +/** + * @brief To release execution lock + */ +void PlayerScheduler::ResumeScheduler() +{ + mExLock.unlock(); + std::lock_guardlock(mQMutex); + mLockOut = false; +} + +/** + * @brief To remove a scheduled tasks with ID + */ +bool PlayerScheduler::RemoveTask(int id) +{ + bool ret = false; + std::lock_guardlock(mQMutex); + // Make sure its not currently executing/executed task + if (id != PLAYER_TASK_ID_INVALID && mCurrentTaskId != id) + { + for (auto it = mTaskQueue.begin(); it != mTaskQueue.end(); ) + { + if (it->mId == id) + { + mTaskQueue.erase(it); + ret = true; + break; + } + else + { + it++; + } + } + } + return ret; +} + +/** + * @brief To enable scheduler to queue new tasks + */ +void PlayerScheduler::EnableScheduleTask() +{ + std::lock_guardlock(mQMutex); + mLockOut = false; +} diff --git a/middleware/PlayerScheduler.h b/middleware/PlayerScheduler.h new file mode 100644 index 000000000..c5009298d --- /dev/null +++ b/middleware/PlayerScheduler.h @@ -0,0 +1,168 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerScheduler.h + * @brief Class to schedule commands for async execution + */ + +#ifndef __PLAYER_SCHEDULER_H__ +#define __PLAYER_SCHEDULER_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLAYER_SCHEDULER_ID_MAX_VALUE INT_MAX // 10000 +#define PLAYER_SCHEDULER_ID_DEFAULT 1 //ID ranges from DEFAULT to MAX +#define PLAYER_TASK_ID_INVALID 0 + +typedef std::function AsyncTask; + +/** + * @brief Async task operations + */ +struct PlayerAsyncTaskObj +{ + AsyncTask mTask; + void * mData; + int mId; + std::string mTaskName; + + PlayerAsyncTaskObj(AsyncTask task, void *data, std::string tskName="", int id = PLAYER_TASK_ID_INVALID) : + mTask(std::move(task)), mData(data), mId(id),mTaskName(std::move(tskName)) + { + } + + PlayerAsyncTaskObj(const PlayerAsyncTaskObj &other) : mTask(other.mTask), mData(other.mData), mId(other.mId),mTaskName(other.mTaskName) + { + } + + PlayerAsyncTaskObj& operator=(const PlayerAsyncTaskObj& other) + { + mTask = other.mTask; + mData = other.mData; + mId = other.mId; + mTaskName = other.mTaskName; + return *this; + } +}; + +/** + * @brief Scheduler class for asynchronous operations + */ +class PlayerScheduler +{ +public: + /** + * @fn PlayerScheduler + */ + PlayerScheduler(); + + PlayerScheduler(const PlayerScheduler&) = delete; + PlayerScheduler& operator=(const PlayerScheduler&) = delete; + /** + * @fn ~PlayerScheduler + */ + virtual ~PlayerScheduler(); + + /** + * @fn ScheduleTask + * + * @param[in] obj - object to be scheduled + * @return int - scheduled task id + */ + int ScheduleTask(PlayerAsyncTaskObj obj); + + /** + * @fn RemoveAllTasks + * + * @return void + */ + void RemoveAllTasks(); + + /** + * @fn RemoveTask + * + * @param[in] id - ID of task to be removed + * @return bool true if removed, false otherwise + */ + bool RemoveTask(int id); + + /** + * @fn StartScheduler + * + * @return void + */ + void StartScheduler(); + + /** + * @fn StopScheduler + * + * @return void + */ + void StopScheduler(); + + /** + * @fn SuspendScheduler + * + * @return void + */ + void SuspendScheduler(); + + /** + * @fn ResumeScheduler + * + * @return void + */ + void ResumeScheduler(); + + /** + * @fn EnableScheduleTask + * + * @return void + */ + void EnableScheduleTask(); + +protected: + /** + * @fn ExecuteAsyncTask + * + * @return void + */ + void ExecuteAsyncTask(); + + std::deque mTaskQueue; /**< Queue for storing scheduled tasks */ + std::mutex mQMutex; /**< Mutex for accessing mTaskQueue */ + std::condition_variable mQCond; /**< To notify when a task is queued in mTaskQueue */ + bool mSchedulerRunning; /**< Flag denotes if scheduler thread is running */ + std::thread mSchedulerThread; /**< Scheduler thread */ + std::mutex mExMutex; /**< Execution mutex for synchronization */ + std::unique_lock mExLock; /**< Lock to be used by SuspendScheduler and ResumeScheduler */ + int mNextTaskId; /**< counter that holds ID value of next task to be scheduled */ + int mCurrentTaskId; /**< ID of current executed task */ + bool mLockOut; /**< flag indicates if the queue is locked out or not */ +}; + +#endif /* __PLAYER_SCHEDULER_H__ */ diff --git a/middleware/PlayerUtils.cpp b/middleware/PlayerUtils.cpp new file mode 100644 index 000000000..6755497ad --- /dev/null +++ b/middleware/PlayerUtils.cpp @@ -0,0 +1,267 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerUtils.cpp + * @brief Common utility functions + */ +#include "PlayerUtils.h" +#include "_base64.h" + +/** + * @brief Check if string start with a prefix + * + * @retval TRUE if substring is found in bigstring + */ +bool player_StartsWith( const char *inputStr, const char *prefix ) +{ + bool rc = true; + while( *prefix ) + { + if( *inputStr++ != *prefix++ ) + { + rc = false; + break; + } + } + return rc; +} + +/** + * @brief convert blob of binary data to ascii base64-URL-encoded equivalent + * @retval pointer to malloc'd cstring containing base64 URL encoded version of string + * @retval NULL if insufficient memory to allocate base64-URL-encoded copy + * @note caller responsible for freeing returned cstring + */ +char *base64_URL_Encode(const unsigned char *src, size_t len) +{ + char *rc = base64_Encode(src,len); + if( rc ) + { + char *dst = rc; + while( *dst ) + { + switch( *dst ) + { + case '+': + *dst = '-'; + break; + case '/': + *dst = '_'; + break; + case '=': + *dst = '\0'; + break; + default: + break; + } + dst++; + } + } + return rc; +} + +/** + * @brief decode base64 URL encoded data to binary equivalent + * @retval pointer to malloc'd memory containing decoded binary data + * @retval NULL if insufficient memory to allocate decoded data + * @note caller responsible for freeing returned data + */ +unsigned char *base64_URL_Decode(const char *src, size_t *len, size_t srcLen) +{ + unsigned char * rc = NULL; + char *temp = (char *)malloc(srcLen+3); + if( temp ) + { + temp[srcLen+2] = '\0'; + temp[srcLen+1] = '='; + temp[srcLen+0] = '='; + for( int iter = 0; iter < srcLen; iter++ ) + { + char c = src[iter]; + switch( c ) + { + case '_': + c = '/'; + break; + case '-': + c = '+'; + break; + default: + break; + } + temp[iter] = c; + } + rc = base64_Decode(temp, len ); + free(temp); + } + else + { + *len = 0; + } + return rc; +} + +static std::hash std_thread_hasher; + +std::size_t GetThreadID( const std::thread &t ) +{ + return std_thread_hasher( t.get_id() ); +} + +std::size_t GetThreadID( void ) +{ + return std_thread_hasher( std::this_thread::get_id() ); +} + +/** + * @brief support for POSIX threads + */ +std::size_t GetThreadID( const pthread_t &t ) +{ + static std::hash pthread_hasher; + return pthread_hasher( t ); +} + +/** + * @brief Get current time from epoch is milliseconds + * @retval - current time in milliseconds + */ +long long GetCurrentTimeMS(void) +{ + struct timeval t; + gettimeofday(&t, NULL); + return (long long)(t.tv_sec*1e3 + t.tv_usec*1e-3); +} + +/** + * @brief parse leading protocol from uri if present + * @param[in] uri manifest/ fragment uri + * @retval return pointer just past protocol (i.e. http://) if present (or) return NULL uri doesn't start with protcol + */ +static const char * ParseUriProtocol(const char *uri) +{ + if(NULL == uri) + { + //MW_LOG_ERR("Empty URI"); + return NULL; + } + for(;;) + { + char ch = *uri++; + if( ch ==':' ) + { + if (uri[0] == '/' && uri[1] == '/') + { + return uri + 2; + } + break; + } + else if (isalnum (ch) || ch == '.' || ch == '-' || ch == '+') // other valid (if unlikely) characters for protocol + { // legal characters for uri protocol - continue + continue; + } + else + { + break; + } + } + return NULL; +} +/** + * @brief Resolve file URL from the base and file path + */ +void ResolveURL(std::string& dst, std::string base, const char *uri , bool bPropagateUriParams) +{ + if( ParseUriProtocol(uri) ) + { + dst = uri; + } + else + { + if(base.empty()) + { + //MW_LOG_WARN("Empty base"); + return; + } + const char *baseStart = base.c_str(); + const char *basePtr = ParseUriProtocol(baseStart); + const char *baseEnd; + for(;;) + { + char c = *basePtr; + if( c==0 || c=='/' || c=='?' ) + { + baseEnd = basePtr; + break; + } + basePtr++; + } + + if( uri[0]!='/' && uri[0]!='\0' ) + { + for(;;) + { + char c = *basePtr; + if( c=='/' ) + { + baseEnd = basePtr; + } + else if( c=='?' || c==0 ) + { + break; + } + basePtr++; + } + } + dst = base.substr(0,baseEnd-baseStart); + if( uri[0]!='/' ) + { + dst += "/"; + } + dst += uri; + if( bPropagateUriParams ) + { + if (strchr(uri,'?') == 0) + { // uri doesn't have url parameters; copy from parents if present + const char *baseParams = strchr(basePtr,'?'); + if( baseParams ) + { + std::string params = base.substr(baseParams-baseStart); + dst.append(params); + } + } + } + } +} + + +/** + * @brief Trim a string + */ +void trim(std::string& src) +{ + size_t first = src.find_first_not_of(" \n\r\t\f\v"); + if (first != std::string::npos) + { + size_t last = src.find_last_not_of(" \n\r\t\f\v"); + std::string dst = src.substr(first, (last - first + 1)); + src = dst; + } +} diff --git a/middleware/PlayerUtils.h b/middleware/PlayerUtils.h new file mode 100644 index 000000000..fd975e504 --- /dev/null +++ b/middleware/PlayerUtils.h @@ -0,0 +1,103 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __PLAYER_UTILS_H__ +#define __PLAYER_UTILS_H__ + +/** + * @file PlayerUtils.h + * @brief Context-free common utility functions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +//Delete non-array object +#define MW_SAFE_DELETE(ptr) { delete(ptr); ptr = NULL; } +//Delete Array object +#define MW_SAFE_DELETE_ARRAY(ptr) { delete [] ptr; ptr = NULL; } + + +#define WRITE_HASCII( DST, BYTE ) \ +{ \ + *DST++ = "0123456789abcdef"[BYTE>>4]; \ + *DST++ = "0123456789abcdef"[BYTE&0xf]; \ +} + +/** + * @fn player_StartsWith + * + * @param[in] inputStr - Input string + * @param[in] prefix - substring to be searched + */ +bool player_StartsWith( const char *inputStr, const char *prefix); + +/** + * @fn base64_URL_Encode + * @param src pointer to first byte of binary data to be encoded + * @param len number of bytes to encode + */ +char *base64_URL_Encode(const unsigned char *src, size_t len); + +/** + * @fn base64_URL_Decode + * @param src pointer to cstring containing base64-URL-encoded data + * @param len receives byte length of returned pointer, or zero upon failure + * @param srcLen source data length + */ +unsigned char *base64_URL_Decode(const char *src, size_t *len, size_t srcLen); + +std::size_t GetThreadID( const std::thread &t ); +std::size_t GetThreadID( void ); +std::size_t GetThreadID( const pthread_t &t ); + +std::size_t GetPrintableThreadID( const pthread_t &t ); +std::size_t GetPrintableThreadID(); +/** + * @fn ResolveURL + * + * @param[out] dst - Created URL + * @param[in] base - Base URL + * @param[in] uri - File path + * @param[in] bPropagateUriParams - flag to use base uri params + * @retval void + */ +void ResolveURL(std::string& dst, std::string base, const char *uri , bool bPropagateUriParams); +/** + * @fn GetCurrentTimeMS + * @brief Get the current time in milliseconds + * + * @return The current time in milliseconds + */ +long long GetCurrentTimeMS(void); + +/** + * @fn trim + * @param[in][out] src Buffer containing string + */ +void trim(std::string& src); + +#endif /* __PLAYER_UTILS_H__ */ + diff --git a/middleware/ProcessHandler.cpp b/middleware/ProcessHandler.cpp new file mode 100644 index 000000000..6e0b55fe0 --- /dev/null +++ b/middleware/ProcessHandler.cpp @@ -0,0 +1,160 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2023 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ProcessHandler.cpp + * @brief Process utility functions + */ + + +#include "ProcessHandler.h" + +#include "PlayerUtils.h" +#include "PlayerLogManager.h" +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Parse the process info line and get the name of process + * + * @param proc status line + * @retval - process name + */ +std::string ProcessHandler::splitName(std::string& line ) +{ + std::string delimiter = ":"; + std::string token = line.substr(line.find(delimiter) + delimiter.length()); + trim(token); + return token; +} + +/** + * @brief convert the string pid value to long + * + * @param process pid as string + * @retval - pid ; if failed -1 + */ +long ProcessHandler::convertPid(std::string& name) +{ + char* p; + long pid = -1; + pid = strtol(name.c_str(), &p, BASE_NUMBER); + if( *p != 0) + pid = -1; + return pid; +} + +/** + * @brief Get the process name from Pid + * + * @param process pid as string + * @retval - true or false + */ +std::string ProcessHandler::GetProcessName(std::string& pid) +{ + std::fstream newFile; + std::string procPath = PROCESS_PROC_DIR+pid+PROCESS_PROC_STATUS; + std::string line; + std::string output = ""; + newFile.open(procPath, std::ios::in); + if (newFile.is_open()) + { + while(getline(newFile, line)) + { + if (line.find("Name:") != std::string::npos) + { + output = splitName(line); + break; + } + } + newFile.close(); + } + return output; +} + + +/** + * @brief kill the process by name + * + * @param process name to kill + * @retval - true or false + */ +bool ProcessHandler::KillProcess(std::string processName) +{ + bool status = false; + struct dirent *entry; + DIR *dir = opendir(PROCESS_PROC_DIR.c_str()); + if (dir == NULL) { + return status; + } + + while ((entry = readdir(dir)) != NULL) + { + std::string strPid = std::string(entry->d_name); + long lPid = convertPid(strPid); + if (lPid > 0) + { + std::string name = GetProcessName(strPid); + if (name == processName) + { + MW_LOG_INFO("Killing the process %s PID %ld", name.c_str(), lPid); + status = KillProcess(lPid); + break; /**< No need to move further*/ + } + } + } + closedir(dir); + return status; +} + +/** + * @brief self kill the process + * + * @retval - true or false + */ +bool ProcessHandler::SelfKill() +{ + return KillProcess(getpid()); +} + +/** + * @brief kill the process by pid number + * + * @param pid + * @retval - true or false + */ +bool ProcessHandler::KillProcess(long pid) +{ + bool ret = true; + if(kill((pid_t)pid, SIGKILL) < 0) + { + MW_LOG_WARN("Kill Failed = %d", errno); + ret = false; + } + return ret; +} + +/** + * EOF + */ diff --git a/middleware/ProcessHandler.h b/middleware/ProcessHandler.h new file mode 100644 index 000000000..8659c0782 --- /dev/null +++ b/middleware/ProcessHandler.h @@ -0,0 +1,98 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2023 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** +* @file ProcessHandler.h +* @brief Context-free process utility functions. +*/ + +#ifndef __PROCESS_HANDLER_H__ +#define __PROCESS_HANDLER_H__ + +#include +/** + * @class ProcessHandler + * @brief Class for controlling process from AAMP + */ +class ProcessHandler +{ + const std::string PROCESS_PROC_DIR = "/proc/"; + const std::string PROCESS_PROC_STATUS = "/status"; + const unsigned int BASE_NUMBER = 10; + + /** + * @fn splitName + */ + std::string splitName(std::string& line ); + + /** + * @fn convertPid + */ + long convertPid(std::string& name); + + public: + + /** + * @fn constructor - ProcessHandler + */ + ProcessHandler() + { + } + + /** + * @brief ProcessHandler Destructor function + */ + ~ProcessHandler(){}; + + /** + * @brief Copy constructor + */ + ProcessHandler(const ProcessHandler&) = delete; + + /** + * @brief ProcessHandler assignment operator overloading + */ + ProcessHandler& operator=(const ProcessHandler&) = delete; + + /** + * @fn GetProcessName + */ + std::string GetProcessName(std::string& pid); + + /** + * @fn KillProcess + */ + bool KillProcess(std::string processName); + + /** + * @fn SelfKill + */ + bool SelfKill(void); + + /** + * @fn KillProcess + */ + bool KillProcess(long pid); +}; + +#endif +/** + * EOF + */ diff --git a/middleware/SocUtils.cpp b/middleware/SocUtils.cpp new file mode 100644 index 000000000..e6967a805 --- /dev/null +++ b/middleware/SocUtils.cpp @@ -0,0 +1,128 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file SocUtils.cpp + */ +#include "SocUtils.h" +#include "SocInterface.h" +#include "InterfacePlayerRDK.h" +#include + +namespace SocUtils +{ + static std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + /** + * @brief Checks if AppSrc should be used for progressive playback. + * + * This function queries the SOC interface to determine whether AppSrc + * should be used for handling progressive playback. + * + * @return true if AppSrc is used, false otherwise. + */ + bool UseAppSrcForProgressivePlayback( void ) + { + return socInterface->UseAppSrc(); + } + + /** + * @brief Checks if Westeros sink is used. + * + * This function queries the SOC interface to determine whether the Westeros sink + * is enabled for rendering video. + * + * @return true if Westeros sink is used, false otherwise. + */ + bool UseWesterosSink( void ) + { + return socInterface->UseWesterosSink(); + } + + /** + * @brief Determines if audio fragment synchronization is supported. + * + * Queries the SOC interface to check if audio fragment sync is supported. + * + * @return true if audio fragment sync is supported, false otherwise. + */ + bool IsAudioFragmentSyncSupported( void ) + { + return socInterface->IsAudioFragmentSyncSupported(); + } + + /** + * @brief Checks if live latency correction is enabled. + * + * This function queries the SOC interface to determine whether live latency + * correction is enabled. + * + * @return true if live latency correction is enabled, false otherwise. + */ + bool EnableLiveLatencyCorrection( void ) + { + return socInterface->EnableLiveLatencyCorrection(); + } + + /** + * @brief Retrieves the number of required queued frames. + * + * Queries the SOC interface to get the required number of frames + * that should be queued for smooth playback. + * + * @return The required number of queued frames. + */ + int RequiredQueuedFrames( void ) + { + return socInterface->RequiredQueuedFrames(); + } + + /** + * @brief Checks if PTS (Presentation Timestamp) re-stamping is enabled. + * + * This function queries the SOC interface to determine whether + * PTS re-stamping is enabled. + * + * @return true if PTS re-stamping is enabled, false otherwise. + */ + bool EnablePTSRestamp(void) + { + return socInterface->EnablePTSRestamp(); + } + /** + * @brief Resets segment event flags during trickplay transitions. + * + * Manages segment event tracking for trickplay scenarios without disrupting seekplay or advertisements. + */ + bool ResetNewSegmentEvent() + { + return socInterface->ResetNewSegmentEvent(); + } + /** + * @brief Check if GST Subtec is enabled + */ + bool isGstSubtecEnabled() + { +#ifdef GST_SUBTEC_ENABLED + return true; +#else + return false; +#endif + } + +} diff --git a/middleware/SocUtils.h b/middleware/SocUtils.h new file mode 100644 index 000000000..892eb3d4f --- /dev/null +++ b/middleware/SocUtils.h @@ -0,0 +1,98 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file SocUtils.h + * @brief public apis for core code to access vendor capabilities at runtime + */ +#ifndef SOC_UTILS_H +#define SOC_UTILS_H + +namespace SocUtils +{ + /** + * @brief Checks if AppSrc should be used for progressive playback. + * + * This function queries the SOC interface to determine whether AppSrc + * should be used for handling progressive playback. + * + * @return true if AppSrc is used, false otherwise. + */ + bool UseAppSrcForProgressivePlayback( void ); + + /** + * @brief Checks if Westeros sink is used. + * + * This function queries the SOC interface to determine whether the Westeros sink + * is enabled for rendering video. + * + * @return true if Westeros sink is used, false otherwise. + */ + bool UseWesterosSink( void ); + + /** + * @brief Determines if audio fragment synchronization is supported. + * + * Queries the SOC interface to check if audio fragment sync is supported. + * + * @return true if audio fragment sync is supported, false otherwise. + */ + bool IsAudioFragmentSyncSupported( void ); + + /** + * @brief Checks if live latency correction is enabled. + * + * This function queries the SOC interface to determine whether live latency + * correction is enabled. + * + * @return true if live latency correction is enabled, false otherwise. + */ + bool EnableLiveLatencyCorrection( void ); + + /** + * @brief Retrieves the number of required queued frames. + * + * Queries the SOC interface to get the required number of frames + * that should be queued for smooth playback. + * + * @return The required number of queued frames. + */ + int RequiredQueuedFrames( void ); + + /** + * @brief Checks if PTS (Presentation Timestamp) re-stamping is enabled. + * + * This function queries the SOC interface to determine whether + * PTS re-stamping is enabled. + * + * @return true if PTS re-stamping is enabled, false otherwise. + */ + bool EnablePTSRestamp(void); + /** + * @brief Resets segment event flags during trickplay transitions. + * + * Manages segment event tracking for trickplay scenarios without disrupting seekplay or advertisements. + */ + bool ResetNewSegmentEvent(); + /** + * @brief Check if GST Subtec is enabled + */ + bool isGstSubtecEnabled(); +} +#endif // SOC_UTILS_H diff --git a/middleware/baseConversion/CMakeLists.txt b/middleware/baseConversion/CMakeLists.txt new file mode 100644 index 000000000..a33b5d35a --- /dev/null +++ b/middleware/baseConversion/CMakeLists.txt @@ -0,0 +1,36 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.5) + +project(baseconversion) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include_directories(..) + +set(BaseConversion_SRC _base64.cpp base16.cpp) + +add_library(baseconversion SHARED ${BaseConversion_SRC}) + +set_target_properties(baseconversion PROPERTIES PUBLIC_HEADER "_base64.h;base16.h") + +# Install the library and its headers +install(TARGETS baseconversion + DESTINATION lib + PUBLIC_HEADER DESTINATION include +) diff --git a/middleware/baseConversion/_base64.cpp b/middleware/baseConversion/_base64.cpp new file mode 100644 index 000000000..c1339bab5 --- /dev/null +++ b/middleware/baseConversion/_base64.cpp @@ -0,0 +1,128 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file _base64.cpp + * @brief optimized pair of base64 encode/decode implementations + */ + +#include "_base64.h" +#include +#include +#include + +/** + * @brief convert blob of binary data to ascii base64-encoded equivalent + * @retval pointer to malloc'd cstring containing base64 encoded version of string + * @retval NULL if insufficient memory to allocate base64-encoded copy + * @note caller responsible for freeing returned cstring + */ +char *base64_Encode(const unsigned char *src, size_t len) +{ + const unsigned char *fin = &src[len]; + const static char *encode = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // base64 + char *rc = (char *)malloc(((len+2)/3)*4+1); + if( rc ) + { + char *dst = rc; + unsigned int temp; + int pad = 0; + for( int i=0; i> 18]; + *dst++ = encode[(temp & 0x0003F000) >> 12]; + *dst++ = (pad>=2)?'=':encode[(temp & 0x00000FC0) >> 6 ]; + *dst++ = (pad>=1)?'=':encode[(temp & 0x0000003F)]; + } + *dst++ = 0x00; + } + return rc; +} + +/** + * @brief decode base64 encoded data to binary equivalent + * @retval return value from base64_Decode value + */ +unsigned char *base64_Decode(const char *src, size_t *outLen, size_t srcLen) +{ + static const signed char decode[256] = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 63, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + unsigned char *rc = (unsigned char *)malloc(srcLen*3/4); + *outLen = 0; // default + if( rc ) + { + unsigned char *dst = rc; + while( srcLen>0 && src[srcLen-1] == '=' ) + { // strip padding + srcLen--; + } + const char *fin = &src[srcLen]; + while( src=2 ) *dst++ = (buf>>(8*2))&0xff; + if( count>=3 ) *dst++ = (buf>>(8*1))&0xff; + if( count>=4 ) *dst++ = (buf>>(8*0))&0xff; + } + *outLen = dst-rc; + } + return rc; +} + +unsigned char *base64_Decode(const char *src, size_t *len) +{ + return base64_Decode(src, len, strlen(src)); +} diff --git a/middleware/baseConversion/_base64.h b/middleware/baseConversion/_base64.h new file mode 100644 index 000000000..6de716907 --- /dev/null +++ b/middleware/baseConversion/_base64.h @@ -0,0 +1,52 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef BASE64_H +#define BASE64_H + +/** + * @file _base64.h + * @brief base64 source Encoder/Decoder + */ + +#include + +/** + * @fn base64_Encode + * @param src pointer to first byte of binary data to be encoded + * @param len number of bytes to encode + */ +char *base64_Encode(const unsigned char *src, size_t len); + +/** + * @fn base64_Decode + * @param src pointer to cstring containing base64-encoded data + * @param len receives byte length of returned pointer, or zero upon failure + */ +unsigned char *base64_Decode(const char *src, size_t *len); + +/** + * @fn base64_Decode + * @param src pointer to cstring containing base64-encoded data + * @param len receives byte length of returned pointer, or zero upon failure + * @param srcLen string length of src + */ +unsigned char *base64_Decode(const char *src, size_t *len, size_t srcLen); + +#endif // BASE64_H diff --git a/middleware/baseConversion/base16.cpp b/middleware/baseConversion/base16.cpp new file mode 100644 index 000000000..5dd2f276d --- /dev/null +++ b/middleware/baseConversion/base16.cpp @@ -0,0 +1,102 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file base16.cpp + * @brief optimized pair of base16 encode/decode implementations + */ + + +#include "_base64.h" +#include +#include +#include +#include "PlayerUtils.h" + +/** + * @brief convert binary data to hascii-encoded equivalent + * @retval pointer to malloc'd cstring containing base16-encoded copy + * @retval NULL if unsufficient memory to allocate base16-encoded copy + * @note caller responsible for freeing returned cstring + * @note returned string will always contain an even number of characters + */ +char *base16_Encode(const unsigned char *src, size_t len) +{ + size_t outLen = len*2; + char *outData = (char *)malloc(1 + outLen); + if( outData ) + { + char *dst = outData; + const unsigned char *finish = src + len; + while (src < finish) + { + unsigned char c = *src++; + WRITE_HASCII( dst, c ); + } + *dst = 0x00; + } + return outData; +} + + +/** + * @brief decode base16 encoded data to binary equivalent + * @retval pointer to malloc'd memory containing decoded binary data. + * @retval NULL if insufficient memory to allocate base16-decoded data + * @note caller responsible for freeing returned data + */ +unsigned char *base16_Decode( const char *srcPtr, size_t srcLen, size_t *len ) +{ + static const signed char mBase16CharToIndex[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + size_t numChars = (srcLen/2); // assumes even + unsigned char *outData = (unsigned char *)malloc(numChars); + if (outData) + { + unsigned char *dst = outData; + const char *finish = srcPtr + srcLen; + while (srcPtr < finish) + { + *dst = mBase16CharToIndex[(unsigned char)*srcPtr++] << 4; + *dst++ |= mBase16CharToIndex[(unsigned char)*srcPtr++]; + } + *len = numChars; + } + else + { // insufficient memory + *len = 0; + } + return outData; +} diff --git a/middleware/baseConversion/base16.h b/middleware/baseConversion/base16.h new file mode 100644 index 000000000..ffd5d6fa4 --- /dev/null +++ b/middleware/baseConversion/base16.h @@ -0,0 +1,47 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef BASE16_H +#define BASE16_H + +/** + * @file base16.h + * @brief optimized way way base16 Encode/Decode operation + */ + +#include + + +/** + * @fn base16_Encode + * @param src pointer to first byte of binary data to be encoded + * @param len number of bytes to encode + */ +char *base16_Encode(const unsigned char *src, size_t len); + + +/** + * @fn base16_Decode + * @param srcPtr pointer to cstring containing base16-encoded data + * @param srcLen length of srcPtr (typically caller already knows, so saves call to strlen) + * @param len receives byte length of returned pointer, or zero upon failure + */ +unsigned char *base16_Decode(const char *srcPtr, size_t srcLen, size_t *len); + +#endif // BASE16_H diff --git a/middleware/closedcaptions/CCTrackInfo.h b/middleware/closedcaptions/CCTrackInfo.h new file mode 100644 index 000000000..d9e0ad56b --- /dev/null +++ b/middleware/closedcaptions/CCTrackInfo.h @@ -0,0 +1,39 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef __CC_TRACK_INFO_H__ +#define __CC_TRACK_INFO_H__ + +#include + +/** + * @brief Structure representing only required track informations for Closed Caption (CC). + */ +struct CCTrackInfo{ + std::string instreamId; + std::string language; + //@brief Default constructor for CCTrackInfo, Initializes instreamId and language to empty strings. + CCTrackInfo() + { + instreamId =""; + language = ""; + } +}; + +#endif // __CC_TRACK_INFO_H__ diff --git a/middleware/closedcaptions/PlayerCCManager.cpp b/middleware/closedcaptions/PlayerCCManager.cpp new file mode 100644 index 000000000..6cc79502a --- /dev/null +++ b/middleware/closedcaptions/PlayerCCManager.cpp @@ -0,0 +1,889 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerCCManager.cpp + * + * @brief Base class for ClosedCaption integration layer + * + */ + +#include "PlayerLogManager.h" // Included for MW_LOG +//TODO: Fix cyclic dependency btw GlobalConfig and PlayerLogManager + +#include "PlayerJsonObject.h" // For JSON parsing + +#include "PlayerUtils.h" // For player_StartsWith + +#include "PlayerCCManager.h" +#include "PlayerSubtecCCManager.h" +#include "PlayerRialtoCCManager.h" + + +#define CHAR_CODE_1 49 +#define CHAR_CODE_6 54 + +int IsCCOnFlag = 0; + +#if defined(SUBTITLE_SUPPORTED) +static mrcc_Error GetCapability(gsw_CcAttribType attribType, gsw_CcType ccType, void **values, unsigned int *size) +{ + return subtecConnector::ccMgrAPI::ccGetCapability(attribType, ccType, values, size); +} + +static mrcc_Error GetAttributes(gsw_CcAttributes * attrib, gsw_CcType ccType) +{ + return subtecConnector::ccMgrAPI::ccGetAttributes(attrib, ccType); +} + +static mrcc_Error SetAttributes(gsw_CcAttributes * attrib, short type, gsw_CcType ccType) +{ + return subtecConnector::ccMgrAPI::ccSetAttributes(attrib, type, ccType); +} +#else +// stubs for (simulator & RPI) builds without inband ccDataReader +static mrcc_Error GetCapability(gsw_CcAttribType attribType, gsw_CcType ccType, void **values, unsigned int *size) +{ + return 0; +} + +static mrcc_Error GetAttributes(gsw_CcAttributes * attrib, gsw_CcType ccType) +{ + return 0; +} + +static mrcc_Error SetAttributes(gsw_CcAttributes * attrib, short type, gsw_CcType ccType) +{ + return 0; +} +#endif +/** + * @brief Get color option from input string + * + * @param[in] attributeIndex - CC attribute + * @param[in] ccType - CC type + * @param[in] input - input color style + * @param[out] colorOut - color option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getColor(gsw_CcAttribType attributeIndex, gsw_CcType ccType, std::string input, gsw_CcColor *colorOut) +{ + static gsw_CcColor* ccColorCaps[GSW_CC_COLOR_MAX]; + static bool flagForMalloc = false; + unsigned int ccSizeOfCaps = 0; + MW_LOG_TRACE("input: %s", input.c_str()); + + if (!input.empty() && colorOut) + { + if (!flagForMalloc) + { + flagForMalloc = true; + for (int i = 0; i < GSW_CC_COLOR_MAX; i++) + { + ccColorCaps[i] = (gsw_CcColor*) malloc(sizeof(gsw_CcColor)); + memset(ccColorCaps[i], 0, sizeof(gsw_CcColor)); + } + } + GetCapability(attributeIndex, ccType, (void**) &ccColorCaps, &ccSizeOfCaps); + + bool found = false; + const char *inputStr = input.c_str(); + for (unsigned int i = 0; i < ccSizeOfCaps; i++) + { + MW_LOG_TRACE("color caps: %s", ccColorCaps[i]->name); + if (0 == strncasecmp(ccColorCaps[i]->name, inputStr, GSW_MAX_CC_COLOR_NAME_LENGTH)) + { + MW_LOG_TRACE("found match %s", ccColorCaps[i]->name); + memcpy(colorOut, ccColorCaps[i], sizeof (gsw_CcColor)); + found = true; + break; + } + } + if(!found) + { + MW_LOG_ERR("Unsupported color type %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL!"); + return -1; + } + return 0; +} + +/** + * @brief Get text style value from input string + * + * @param[in] input - input text style value + * @param[out] fontSizeOut - text style option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getTextStyle(std::string input, gsw_CcTextStyle *textStyleOut) +{ + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && textStyleOut) + { + const char *inputStr = input.c_str(); + if (0 == strncasecmp(inputStr, "false", strlen("false"))) + { + *textStyleOut = GSW_CC_TEXT_STYLE_FALSE; + } + else if (0 == strncasecmp(inputStr, "true", strlen("true"))) + { + *textStyleOut = GSW_CC_TEXT_STYLE_TRUE; + } + else if (0 == strncasecmp(inputStr, "auto", strlen("auto"))) + { + *textStyleOut = GSW_CC_TEXT_STYLE_EMBEDDED_TEXT; + } + else + { + MW_LOG_ERR("Unsupported text style %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL"); + return -1; + } + return 0; +} + +/** + * @brief Get edge type value from input string + * + * @param[in] input - input edge type value + * @param[out] fontSizeOut - edge type option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getEdgeType(std::string input, gsw_CcEdgeType *edgeTypeOut) +{ + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && edgeTypeOut) + { + const char *inputStr = input.c_str(); + if (0 == strncasecmp(inputStr, "none", strlen("none"))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_NONE; + } + else if (0 == strncasecmp(inputStr, "raised", strlen("raised"))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_RAISED; + } + else if (0 == strncasecmp(inputStr, "depressed", strlen("depressed"))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_DEPRESSED; + } + else if (0 == strncasecmp(inputStr, "uniform", strlen("uniform"))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_UNIFORM; + } + else if ((0 == strncasecmp(inputStr, "drop_shadow_left", strlen("drop_shadow_left"))) || + (0 == strncasecmp(inputStr, "Left drop shadow", strlen("Left drop shadow")))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_SHADOW_LEFT; + } + else if ((0 == strncasecmp(inputStr, "drop_shadow_right", strlen("drop_shadow_right"))) || + (0 == strncasecmp(inputStr, "Right drop shadow", strlen("Right drop shadow")))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_SHADOW_RIGHT; + } + else if (0 == strncasecmp(inputStr, "auto", strlen("auto"))) + { + *edgeTypeOut = GSW_CC_EDGE_TYPE_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported edge type %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL"); + return -1; + } + return 0; +} + +/** + * @brief Get font style value from input string + * + * @param[in] input - input font style value + * @param[out] fontSizeOut - font style option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getFontStyle(std::string input, gsw_CcFontStyle *fontStyleOut) +{ + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && fontStyleOut) + { + const char *inputStr = input.c_str(); + if (0 == strncasecmp(inputStr, "default", strlen("default"))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_DEFAULT, sizeof(GSW_CC_FONT_STYLE_DEFAULT)); + } + else if ((0 == strncasecmp(inputStr, "monospaced_serif", strlen("monospaced_serif"))) || + (0 == strncasecmp(inputStr, "Monospaced serif", strlen("Monospaced serif")))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_MONOSPACED_SERIF, sizeof(GSW_CC_FONT_STYLE_MONOSPACED_SERIF)); + } + else if ((0 == strncasecmp(inputStr, "proportional_serif", strlen("proportional_serif"))) || + (0 == strncasecmp(inputStr, "Proportional serif", strlen("Proportional serif")))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_PROPORTIONAL_SERIF, sizeof(GSW_CC_FONT_STYLE_PROPORTIONAL_SERIF)); + } + else if ((0 == strncasecmp(inputStr, "monospaced_sanserif", strlen("monospaced_sanserif"))) || + (0 == strncasecmp(inputStr, "Monospaced sans serif", strlen("Monospaced sans serif")))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_MONOSPACED_SANSSERIF, sizeof(GSW_CC_FONT_STYLE_MONOSPACED_SANSSERIF)); + } + else if ((0 == strncasecmp(inputStr, "proportional_sanserif", strlen("proportional_sanserif"))) || + (0 == strncasecmp(inputStr, "Proportional sans serif", strlen("Proportional sans serif")))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_PROPORTIONAL_SANSSERIF, sizeof(GSW_CC_FONT_STYLE_PROPORTIONAL_SANSSERIF)); + } + else if (0 == strncasecmp(inputStr, "casual", strlen("casual"))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_CASUAL, sizeof(GSW_CC_FONT_STYLE_CASUAL)); + } + else if (0 == strncasecmp(inputStr, "cursive", strlen("cursive"))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_CURSIVE, sizeof(GSW_CC_FONT_STYLE_CURSIVE)); + } + else if ((0 == strncasecmp(inputStr, "smallcaps", strlen("smallcaps"))) || + (0 == strncasecmp(inputStr, "small capital", strlen("small capital")))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_SMALL_CAPITALS, sizeof(GSW_CC_FONT_STYLE_SMALL_CAPITALS)); + } + else if (0 == strncasecmp(inputStr, "auto", strlen("auto"))) + { + memcpy(fontStyleOut, GSW_CC_FONT_STYLE_EMBEDDED, sizeof(GSW_CC_FONT_STYLE_EMBEDDED)); + } + else + { + MW_LOG_ERR("Unsupported font style type %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL"); + return -1; + } + return 0; +} + +/** + * @brief Get font size value from input string + * + * @param[in] input - input font size value + * @param[out] fontSizeOut - font size option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getFontSize(std::string input, gsw_CcFontSize *fontSizeOut) +{ + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && fontSizeOut) + { + const char *inputStr = input.c_str(); + if (0 == strncasecmp(inputStr, "small", strlen("small"))) + { + *fontSizeOut = GSW_CC_FONT_SIZE_SMALL; + } + else if ((0 == strncasecmp(inputStr, "standard", strlen("standard"))) || + (0 == strncasecmp(inputStr, "medium", strlen("medium")))) + { + *fontSizeOut = GSW_CC_FONT_SIZE_STANDARD; + } + else if (0 == strncasecmp(inputStr, "large", strlen("large"))) + { + *fontSizeOut = GSW_CC_FONT_SIZE_LARGE; + } + else if (0 == strncasecmp(inputStr, "extra_large", strlen("extra_large"))) + { + *fontSizeOut = GSW_CC_FONT_SIZE_EXTRALARGE; + } + else if (0 == strncasecmp(inputStr, "auto", strlen("auto"))) + { + *fontSizeOut = GSW_CC_FONT_SIZE_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported font size type %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL"); + return -1; + } + return 0; +} + +/** + * @brief Get opacity value from input string + * + * @param[in] input - input opacity style + * @param[out] opacityOut - opacity option for the input value + * @return int - 0 for success, -1 for failure + */ +static int getOpacity(std::string input, gsw_CcOpacity *opacityOut) +{ + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && opacityOut) + { + const char *inputStr = input.c_str(); + if (0 == strncasecmp(inputStr, "solid", strlen("solid"))) + { + *opacityOut = GSW_CC_OPACITY_SOLID; + } + else if (0 == strncasecmp(inputStr, "flash", strlen("flash"))) + { + *opacityOut = GSW_CC_OPACITY_FLASHING; + } + else if (0 == strncasecmp(inputStr, "translucent", strlen("translucent"))) + { + *opacityOut = GSW_CC_OPACITY_TRANSLUCENT; + } + else if (0 == strncasecmp(inputStr, "transparent", strlen("transparent"))) + { + *opacityOut = GSW_CC_OPACITY_TRANSPARENT; + } + else if (0 == strncasecmp(inputStr, "auto", strlen("auto"))) + { + *opacityOut = GSW_CC_OPACITY_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported opacity type %s", inputStr); + } + } + else + { + MW_LOG_ERR("Input is NULL"); + return -1; + } + return 0; +} + + +/** + * @brief Set CC styles for rendering + */ +int PlayerCCManagerBase::SetStyle(const std::string &options) +try +{ + EnsureRendererCommsInitialized(); + MW_LOG_WARN("PlayerCCManagerBase::"); + + int ret = -1; + /* + * Values received from JS PP: + * + * export interface IPlayerCCStyle { + * fontStyle?: string; + * textEdgeColor?: string; + * textEdgeStyle?: string; + * textForegroundColor?: string; + * textForegroundOpacity?: string; + * textBackgroundColor?: string; + * textBackgroundOpacity?: string; + * penItalicized?: string; + * penSize?: string; + * penUnderline?: string; + * windowBorderEdgeColor?: string; + * windowBorderEdgeStyle?: string; + * windowFillColor?: string; + * windowFillOpacity?: string; + * bottomInset?: string; + * } + * FYI - bottomInset unsupported in RDK CC module + */ + + if(mEnabled) + { + if (!options.empty()) + { + PlayerJsonObject inputOptions(options); + + std::string optionValue; + ret = 0; + mOptions = options; + + gsw_CcAttributes attribute; + /** Get the current attributes */ + GetAttributes (&attribute, GSW_CC_TYPE_DIGITAL); + + short attribsMask = 0; + + if (inputOptions.get("fontStyle", optionValue)) + { + getFontStyle(optionValue, &(attribute.fontStyle)); + attribsMask |=GSW_CC_ATTRIB_FONT_STYLE; + } + + if (inputOptions.get("textEdgeColor", optionValue)) + { + getColor(GSW_CC_ATTRIB_EDGE_COLOR, GSW_CC_TYPE_DIGITAL, optionValue, &(attribute.edgeColor)); + attribsMask |=GSW_CC_ATTRIB_EDGE_COLOR; + } + + if (inputOptions.get("textEdgeStyle", optionValue)) + { + getEdgeType(optionValue, &(attribute.edgeType)); + attribsMask |=GSW_CC_ATTRIB_EDGE_TYPE; + } + + if (inputOptions.get("textForegroundColor", optionValue)) + { + getColor(GSW_CC_ATTRIB_FONT_COLOR, GSW_CC_TYPE_DIGITAL, optionValue, &(attribute.charFgColor)); + attribsMask |=GSW_CC_ATTRIB_FONT_COLOR; + } + + if (inputOptions.get("textForegroundOpacity", optionValue)) + { + getOpacity(optionValue, &(attribute.charFgOpacity)); + attribsMask |=GSW_CC_ATTRIB_FONT_OPACITY; + } + + if (inputOptions.get("textBackgroundColor", optionValue)) + { + getColor(GSW_CC_ATTRIB_BACKGROUND_COLOR, GSW_CC_TYPE_DIGITAL, optionValue, &(attribute.charBgColor)); + attribsMask |=GSW_CC_ATTRIB_BACKGROUND_COLOR; + } + + if (inputOptions.get("textBackgroundOpacity", optionValue)) + { + getOpacity(optionValue, &(attribute.charBgOpacity)); + attribsMask |=GSW_CC_ATTRIB_BACKGROUND_OPACITY; + } + + if (inputOptions.get("penItalicized", optionValue)) + { + getTextStyle(optionValue, &(attribute.fontItalic)); + attribsMask |=GSW_CC_ATTRIB_FONT_ITALIC; + } + + if (inputOptions.get("penSize", optionValue)) + { + getFontSize(optionValue, &(attribute.fontSize)); + attribsMask |=GSW_CC_ATTRIB_FONT_SIZE; + } + + if (inputOptions.get("penUnderline", optionValue)) + { + getTextStyle(optionValue, &(attribute.fontUnderline)); + attribsMask |=GSW_CC_ATTRIB_FONT_UNDERLINE; + } + + if (inputOptions.get("windowBorderEdgeColor", optionValue)) + { + getColor(GSW_CC_ATTRIB_BORDER_COLOR, GSW_CC_TYPE_DIGITAL, optionValue, &(attribute.borderColor)); + attribsMask |=GSW_CC_ATTRIB_BORDER_COLOR; + } + + if (inputOptions.get("windowBorderEdgeStyle", optionValue)) + { + getEdgeType(optionValue, (gsw_CcEdgeType*) &(attribute.borderType)); + attribsMask |=GSW_CC_ATTRIB_BORDER_TYPE; + } + + if (inputOptions.get("windowFillColor", optionValue)) + { + getColor(GSW_CC_ATTRIB_WIN_COLOR, GSW_CC_TYPE_DIGITAL, optionValue, &(attribute.winColor)); + attribsMask |=GSW_CC_ATTRIB_WIN_COLOR; + } + + if (inputOptions.get("windowFillOpacity", optionValue)) + { + getOpacity(std::move(optionValue), &(attribute.winOpacity)); + attribsMask |=GSW_CC_ATTRIB_WIN_OPACITY; + } + + if(attribsMask != 0) + { + + SetAttributes(&attribute, attribsMask, GSW_CC_TYPE_DIGITAL); + SetAttributes(&attribute, attribsMask, GSW_CC_TYPE_ANALOG); + } + else + { + MW_LOG_WARN("PlayerCCManagerBase::received optionsJson but result attributeMask is 0"); + } + + } + } + else + { + MW_LOG_WARN("PlayerCCManagerBase::CC rendering not enabled"); + } + return ret; +} +catch(const PlayerJsonParseException& e) +{ + MW_LOG_ERR("PlayerCCManagerBase: PlayerJsonParseException - %s", e.what()); + return -1; +} + +/** + * @brief To stop CC rendering + */ +void PlayerCCManagerBase::Stop() +{ + EnsureRendererCommsInitialized(); + MW_LOG_WARN("PlayerCCManagerBase::mEnabled=%d",mEnabled); + StopRendering(); +} + +/** + * @brief To start CC rendering + */ +void PlayerCCManagerBase::Start() +{ + EnsureInitialized(); + MW_LOG_WARN("PlayerCCManagerBase:: mEnabled=%d", mEnabled); + StartRendering(); +} + +/** + * @brief Initialize CC resource. + */ +int PlayerCCManagerBase::Init(void *handle) +{ + if (handle == NULL) + { + MW_LOG_WARN("PlayerCCManagerBase:: NULL handle"); + return -1; + } + + if (Initialize(handle) != 0) + { + MW_LOG_WARN("PlayerCCManagerBase::Initialize failure"); + return -1; + } + + MW_LOG_WARN("PlayerCCManagerBase:: Start CC with video dec handle: %p and mEnabled: %d", handle, mEnabled); + + if (mEnabled) + { + Start(); + } + else + { + Stop(); + } + + return 0; +} + +/** + * @brief To enable/disable CC when trickplay starts/ends + */ +void PlayerCCManagerBase::SetTrickplayStatus(bool on) +{ + MW_LOG_WARN("PlayerCCManagerBase::trickplay status(%d)", on); + if (on) + { + // When trickplay starts, stop CC rendering + Stop(); + } + else if (mEnabled) + { + // When trickplay ends and CC rendering enabled by app + Start(); + } + mTrickplayStarted = on; +} + +/** + * @brief To enable/disable CC when parental control locked/unlocked + */ +void PlayerCCManagerBase::SetParentalControlStatus(bool locked) +{ + MW_LOG_WARN("PlayerCCManagerBase:: lock status(%s)", (locked)?"true":"false"); + if (locked) + { + // When parental control locked, stop CC rendering + Stop(); + } + else + { + if (mEnabled) + { + // When parental control unlocked, start CC rendering if already enabled by app + Start(); + } + } + mParentalCtrlLocked = locked; +} + +/** + * @brief Set CC track + */ +int PlayerCCManagerBase::SetTrack(const std::string &track, const CCFormat format) +{ + EnsureRendererCommsInitialized(); + int ret = -1; + unsigned int trackNum = 0; + CCFormat finalFormat = eCLOSEDCAPTION_FORMAT_DEFAULT; + mTrack = track; + + // Check if track is CCx or SERVICEx or track number + // Could be from 1 -> 63 + if (!track.empty() && track[0] >= CHAR_CODE_1 && track[0] <= CHAR_CODE_6) + { + trackNum = (unsigned int) std::stoul(track); + // This is slightly confusing as we don't know if its 608/708 + // more info might be available in format argument + } + else if (track.size() > 2 && player_StartsWith(track.c_str(), "CC")) + { + // Value between 1 - 4 + // Set as analog channel + finalFormat = eCLOSEDCAPTION_FORMAT_608; + trackNum = ((int)track[2] - 48); + } + else if (track.size() > 3 && player_StartsWith(track.c_str(), "TXT")) + { + // Value between 5 - 8 + // Set as analog channel + finalFormat = eCLOSEDCAPTION_FORMAT_608; + trackNum = ((int)track[3] - 48) + 4; + } + else if (track.size() > 4 && player_StartsWith(track.c_str(), "TEXT")) + { + // Value between 5 - 8 + // Set as analog channel + finalFormat = eCLOSEDCAPTION_FORMAT_608; + trackNum = ((int)track[4] - 48) + 4; + } + else if (track.size() > 7 && player_StartsWith(track.c_str(), "SERVICE")) + { + // Value between 1 - 63 + // Set as digital channel + finalFormat = eCLOSEDCAPTION_FORMAT_708; + trackNum = (unsigned int) std::stoul(track.substr(7)); + } + + // Format argument overrides whatever we infer from track + if (format != eCLOSEDCAPTION_FORMAT_DEFAULT && finalFormat != format) + { + if (format == eCLOSEDCAPTION_FORMAT_608) + { + //Force to 608 + finalFormat = eCLOSEDCAPTION_FORMAT_608; + if (trackNum > 8) + { + //Hope this will not happen in realtime + trackNum = 0; + } + MW_LOG_WARN("PlayerCCManagerBase:: Force CC track format to 608 and trackNum to %d!", trackNum); + } + else + { + //Force to 708 + finalFormat = eCLOSEDCAPTION_FORMAT_708; + MW_LOG_WARN("PlayerCCManagerBase::Force CC track format to 708!"); + } + } + + MW_LOG_WARN("PlayerCCManagerBase::Set CC InstreamId '%s' format(%d) trackNum(%d)", track.c_str(), finalFormat, trackNum); + + if (finalFormat == eCLOSEDCAPTION_FORMAT_708 && (trackNum > 0 && trackNum <= 63)) + { + ret = SetDigitalChannel(trackNum); + } + else if (finalFormat == eCLOSEDCAPTION_FORMAT_608 && (trackNum > 0 && trackNum <= 8)) + { + int analogChannel = GSW_CC_ANALOG_SERVICE_CC1 + (trackNum - 1); + ret = SetAnalogChannel(analogChannel); + } + else + { + MW_LOG_WARN("PlayerCCManagerBase::Invalid track number or format, ignoring it!"); + } + + if(0 != ret) + { + MW_LOG_WARN("PlayerCCManagerBase::Failed to set trackNum(%d) and format(%d) with ret - %d", trackNum, finalFormat, ret); + } + return ret; +} + +/** + * @brief To restore cc state after new tune + */ +void PlayerCCManagerBase::RestoreCC() +{ + MW_LOG_WARN("PlayerCCManagerBase::mEnabled: %d, mTrickplayStarted: %d, mParentalCtrlLocked: %d, mCCHandle: %s", + mEnabled, mTrickplayStarted, mParentalCtrlLocked, (CheckCCHandle()) ? "set" : "not set"); + + std::string trackId = GetTrack(); + + const auto& textTracks = getLastTextTracks(); + bool matchFound = false; + + if(!textTracks.empty()) + { + for(const auto& track : textTracks) + { + if(trackId == track.instreamId) + { + MW_LOG_WARN("PlayerCCManagerBase::matching id found in available tracks"); + matchFound = true; + break; + } + } + + if(!matchFound) + { + std::string defaultTrack = textTracks.front().instreamId; + trackId = defaultTrack.empty() ? "CC1" : defaultTrack; + MW_LOG_WARN("PlayerCCManagerBase::matching id not found, selecting %s as default", trackId.c_str()); + } + } + else + { + MW_LOG_WARN("PlayerCCManagerBase::tracklist empty selecting CC1 as default"); + trackId = "CC1"; + } + + SetTrack(trackId); +} + +/** + * @brief Enable/disable CC rendering + */ +int PlayerCCManagerBase::SetStatus(bool enable) +{ + int ret = 0; + mEnabled = enable; + MW_LOG_WARN("PlayerCCManagerBase::mEnabled: %d, mTrickplayStarted: %d, mParentalCtrlLocked: %d, mCCHandle: %s", + mEnabled, mTrickplayStarted, mParentalCtrlLocked, (CheckCCHandle()) ? "set" : "not set"); + if (mEnabled) + IsCCOnFlag = 1; + else + IsCCOnFlag = 0; + + if (!mTrickplayStarted && !mParentalCtrlLocked && CheckCCHandle()) + { + // Setting CC rendering to true before media_closeCaptionStart is not honoured + // by CC module. CC rendering status is saved in mEnabled and Start/Stop is + // called when the required operations are completed + if (mEnabled) + { + Start(); + } + else + { + Stop(); + } + } + return ret; +} + +/** + * @brief To check whether Out of Band Closed caption/subtitle rendering supported or not. + */ +bool PlayerCCManagerBase::IsOOBCCRenderingSupported() +{ + MW_LOG_TRACE("Subtec CC Mode Support: ON"); + return true; +} + +/** + * @brief Singleton instance + */ +PlayerCCManagerBase *PlayerCCManager::mInstance = NULL; + +/** + * @brief Indicates whether mInstance should be a Rialto or a Subtec class. + */ +bool PlayerCCManager::mIsRialto = false; + +/** + * @brief Get the singleton instance + */ +PlayerCCManagerBase *PlayerCCManager::GetInstance() +{ + if (mInstance == NULL) + { +#if defined(SUBTITLE_SUPPORTED) + if (mIsRialto) + { + MW_LOG_INFO("PlayerCCManager::Creating Rialto CC manager"); + mInstance = new PlayerRialtoCCManager(); + } + else + { + MW_LOG_INFO("PlayerCCManager::Creating Subtec CC manager"); + mInstance = new PlayerSubtecCCManager(); + } +#else + MW_LOG_WARN("No CC support on simulators. Creating a dummy instance!"); + mInstance = new PlayerFakeCCManager(); +#endif + } + return mInstance; +} + +/** + * @brief Reset the state. + */ +void PlayerCCManagerBase::ResetState() +{ + MW_LOG_INFO("PlayerCCManagerBase::Resetting"); + Stop(); + + mOptions = ""; + mTrack = ""; + mLastTextTracks.clear(); + mEnabled = false; + mTrickplayStarted = false; + mParentalCtrlLocked = false; +} + +/** + * @brief Set the variant required + */ +void PlayerCCManager::SetRialto(bool bIsRialto) +{ + if (mInstance == NULL) + { + MW_LOG_INFO("PlayerCCManager::IsRialto:%d", bIsRialto); + mIsRialto = bIsRialto; + } + else if (mIsRialto != bIsRialto) + { + MW_LOG_ERR("PlayerCCManager::IsRialto:%d while incompatible singleton instance exists", bIsRialto); + } +} + +/** + * @brief Destroy instance + */ +void PlayerCCManager::DestroyInstance() +{ + if (mInstance) + { + delete mInstance; + mInstance = NULL; + } +} + diff --git a/middleware/closedcaptions/PlayerCCManager.h b/middleware/closedcaptions/PlayerCCManager.h new file mode 100644 index 000000000..f8eb2d20b --- /dev/null +++ b/middleware/closedcaptions/PlayerCCManager.h @@ -0,0 +1,357 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerCCManager.h + * + * @brief Integration layer of ClosedCaption in MIDDLEWARE layer + * + */ + +#ifndef __PLAYER_CC_MANAGER_H__ +#define __PLAYER_CC_MANAGER_H__ + +#include +#include +#include "CCTrackInfo.h" +#include "PlayerLogManager.h" + +/** + * @enum CCFormat + * @brief Different CC formats + */ +enum CCFormat +{ + eCLOSEDCAPTION_FORMAT_608 = 0, + eCLOSEDCAPTION_FORMAT_708, + eCLOSEDCAPTION_FORMAT_DEFAULT +}; + +/** + * @class PlayerCCManagerBase + * @brief Handles closed caption operations + */ + +class PlayerCCManagerBase +{ +public: + /** + * @fn Init + * + * @param[in] handle - decoder handle + * @return int - 0 on success, -1 on failure + */ + int Init(void *handle); + + /** + * @brief Gets Handle or ID, Every client using subtec must call GetId in the beginning , save id, which is required for Release function. + * @return int - unique ID + */ + virtual int GetId() { return 0; }; + + /** + * @brief Release CC resources + * @param[in] id - returned from GetId function + */ + virtual void Release(int iID) = 0; + + /** + * @fn SetStatus + * + * @param[in] enable - true to enable CC rendering + * @return int - 0 on success, -1 on failure + */ + virtual int SetStatus(bool enable); + + /** + * @brief Get CC rendering status + * + * @return bool - true if enabled, false otherwise + */ + bool GetStatus() { return mEnabled; }; + + /** + * @brief Get current CC track + * + * @return std::string - current CC track + */ + const std::string &GetTrack() { return mTrack; } + + /** + * @fn SetTrack + * + * @param[in] track - CC track to be selected + * @param[in] format - force track to 608/708 or default + * @return int - 0 on success, -1 on failure + */ + virtual int SetTrack(const std::string &track, const CCFormat format = eCLOSEDCAPTION_FORMAT_DEFAULT); + + /** + * @fn SetStyle + * + * @param[in] options - rendering style options + * @return int - 0 on success, -1 on failure + */ + virtual int SetStyle(const std::string &options); + + /** + * @brief Get current CC styles + * + * @return std::string - current CC options + */ + //TODO: Default values can't be queried + const std::string &GetStyle() { return mOptions; } + + /** + * @fn SetTrickplayStatus + * + * @param[in] enable - true when trickplay starts, false otherwise + * @return void + */ + virtual void SetTrickplayStatus(bool enable); + + /** + * @fn SetParentalControlStatus + * + * @param[in] locked - true when parental control lock enabled, false otherwise + * @return void + */ + virtual void SetParentalControlStatus(bool locked); + + /** + * @fn RestoreCC + * + * @return void + */ + void RestoreCC(); + + virtual ~PlayerCCManagerBase(){ }; + + /** + * @brief update stored list of text tracks + * + * @param[in] newTextTracks - list of text track to store + * @return void + */ + virtual void updateLastTextTracks(const std::vector& newTextTracks) { mLastTextTracks = newTextTracks; } + /** + * @brief Get list of text track Ids + * + * @return const std::vector& - list of text tracks data + */ + const std::vector& getLastTextTracks() const { return mLastTextTracks;} + + /** + * @brief To check whether Out of Band Closed caption/Subtile rendering supported or not. + * + * @return bool, True if Out of Band Closed caption/subtitle rendering supported + */ + virtual bool IsOOBCCRenderingSupported(); + +protected: + /** + * @brief To start CC rendering + * + * @return void + */ + virtual void StartRendering() = 0; + + /** + * @brief To stop CC rendering + * + * @return void + */ + virtual void StopRendering() = 0; + + /** + * @brief Impl specific initialization code called before each public interface call + * @return void + */ + virtual void EnsureInitialized(){}; + + /** + * @brief Impl specific initialization code for HAL + * @return void + */ + virtual void EnsureHALInitialized(){}; + + /** + * @brief Impl specific initialization code for Communication with renderer + * @return void + */ + virtual void EnsureRendererCommsInitialized(){}; + + /** + * @brief Impl specific initialization code called once in Init() function + * + * @return 0 - success, -1 - failure + */ + virtual int Initialize(void *handle){return 0;} + + /** + * @brief set digital channel with specified id + * + * @return CC_VL_OS_API_RESULT + */ + virtual int SetDigitalChannel(unsigned int id) = 0; + + /** + * @brief set analog channel with specified id + * + * @return CC_VL_OS_API_RESULT + */ + virtual int SetAnalogChannel(unsigned int id) = 0; + + /** + * @brief validate mCCHandle + * + * @return bool + */ + virtual bool CheckCCHandle() const {return true;} + + /** + * @fn Start + * + * @return void + */ + void Start(); + + /** + * @fn Stop + * + * @return void + */ + void Stop(); + + /** + * @fn ResetState + * + * @return void + */ + virtual void ResetState(); + + /* NOTE WELL: The ResetState() method resets these member variables back to + ** their initial state. It should be updated if any of the following change + ** or are added to. */ + std::string mOptions{}; /**< CC rendering styles */ + std::string mTrack{}; /**< CC track */ + std::vector mLastTextTracks; + bool mEnabled{false}; /**< true if CC rendering enabled, false otherwise */ + bool mTrickplayStarted{false}; /**< If a trickplay is going on or not */ + bool mParentalCtrlLocked{false}; /**< If Parental Control lock enabled on not */ +}; + +/** + * @class PlayerCCManager + * @brief Handle the CC manager instance + */ + +class PlayerCCManager +{ +public: + /** + * @fn GetInstance + * + * @return PlayerCCManager - singleton instance + */ + static PlayerCCManagerBase * GetInstance(); + + /** + * @fn SetRialto + * + * @return void + */ + static void SetRialto(bool bIsRialto); + + /** + * @fn DestroyInstance + * + * @return void + */ + static void DestroyInstance(); + +private: + static PlayerCCManagerBase *mInstance; /**< Singleton instance */ + static bool mIsRialto; /**< Determines which class to instantiate */ +}; + +class PlayerFakeCCManager : public PlayerCCManagerBase +{ +public: + + void Release(int iID) override {}; + /** + * @fn PlayerFakeCCManager + */ + PlayerFakeCCManager() = default; + + /** + * @brief Destructor + */ + ~PlayerFakeCCManager() = default; + + PlayerFakeCCManager(const PlayerFakeCCManager&) = delete; + PlayerFakeCCManager& operator=(const PlayerFakeCCManager&) = delete; + +private: + /** + * @fn StartRendering + * + * @return void + */ + void StartRendering() override {}; + + /** + * @fn StopRendering + * + * @return void + */ + void StopRendering() override {}; + + /** + * @fn SetDigitalChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetDigitalChannel(unsigned int id) override { return 0; }; + /** + * @fn SetAnalogChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetAnalogChannel(unsigned int id) override { return 0; }; + + bool CheckCCHandle() const override{ return false; } + + void updateLastTextTracks(const std::vector& newTextTracks) override {}; + + void SetParentalControlStatus(bool locked) override {}; + + void SetTrickplayStatus(bool enable) override {}; + + int SetStatus(bool enable) override { return 0; }; + + int SetStyle(const std::string &options) override { return 0; }; + + int SetTrack(const std::string &track, const CCFormat format = eCLOSEDCAPTION_FORMAT_DEFAULT) override { return 0; }; + + bool IsOOBCCRenderingSupported() override { return false; }; +}; + +#endif /* __PLAYER_CC_MANAGER_H__ */ diff --git a/middleware/closedcaptions/rialto/PlayerRialtoCCManager.cpp b/middleware/closedcaptions/rialto/PlayerRialtoCCManager.cpp new file mode 100644 index 000000000..b05439a82 --- /dev/null +++ b/middleware/closedcaptions/rialto/PlayerRialtoCCManager.cpp @@ -0,0 +1,203 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerRialtoCCManager.cpp + * + * @brief Impl of Rialto ClosedCaption integration layer + * + */ +#include "PlayerRialtoCCManager.h" +#include "PlayerLogManager.h" // Included for MW_LOG +#include // Included for g_object_set + +/** + * @brief stores Handle + */ +int PlayerRialtoCCManager::Initialize(void * handle) +{ + MW_LOG_INFO("PlayerRialtoCCManager::Initialize(%p) called", handle); + + bool changedHandle = (handle != mSubtitleControlHandle); + + mSubtitleControlHandle = handle; + + if (GetTrack().empty()) + { + // Apps expect to render default CC as CC1, so set that here in case + // they do not explicitly call SetTrack(). + MW_LOG_INFO("PlayerRialtoCCManager::Setting default to \"CC1\""); + (void) SetTrack("CC1"); + } + else if (changedHandle) + { + // Configure the new handle. + (void) SetTrack(GetTrack()); + } + + return 0; +} + +/** + * @brief Gets Handle or ID, Every client using subtec must call GetId in the beginning, save id, which is required for Release function. + */ +int PlayerRialtoCCManager::GetId() +{ + std::lock_guard lock(mIdLock); + mId++; + mIdSet.insert(mId); + MW_LOG_INFO("PlayerRialtoCCManager::id:%d,users:%d", mId, mIdSet.size()); + return mId; +} + +/** + * @brief Reset internal state. + */ +void PlayerRialtoCCManager::ResetState() +{ + MW_LOG_INFO("PlayerRialtoCCManager::Resetting"); + PlayerCCManagerBase::ResetState(); + mSubtitleControlHandle = nullptr; +} + +/** + * @brief Release CC resources + */ +void PlayerRialtoCCManager::Release(int id) +{ + std::lock_guard lock(mIdLock); + if (mIdSet.erase(id) > 0) + { + int id_size = mIdSet.size(); + MW_LOG_INFO("PlayerRialtoCCManager::users:%d", id_size); + + if (0 == id_size) + { + // Last user has released. + // Note that this instance can be re-used later. + // Therefore, ensure the state is reset so that it is the same as a + // newly constructed instance. + ResetState(); + } + } + else + { + MW_LOG_WARN("PlayerRialtoCCManager::ID:%d not found", id); + } + + return; +} + +/** + * @brief Set CC track + */ +int PlayerRialtoCCManager::SetTrack(const std::string &track, const CCFormat format) +{ + mTrack = track; // For PlayerCCManager::GetTrack() + + MW_LOG_INFO("PlayerRialtoCCManager::set track \"%s\"", track.c_str()); + + if (nullptr != mSubtitleControlHandle) + { + g_object_set(mSubtitleControlHandle, "text-track-identifier", track.c_str(), NULL); + } + else + { + MW_LOG_INFO("PlayerRialtoCCManager::No current handle - track \"%s\" cached", track.c_str()); + } + + return 0; +} + +/** + * @brief To start CC rendering + */ +void PlayerRialtoCCManager::StartRendering() +{ + MW_LOG_INFO("PlayerRialtoCCManager::unmuting"); + + if (nullptr != mSubtitleControlHandle) + { + g_object_set(mSubtitleControlHandle, "mute", FALSE, NULL); + } + else + { + MW_LOG_INFO("PlayerRialtoCCManager::Failed to unmute"); + } + return; +} + +/** + * @brief To stop CC rendering + */ +void PlayerRialtoCCManager::StopRendering() +{ + MW_LOG_INFO("PlayerRialtoCCManager::muting"); + + if (nullptr != mSubtitleControlHandle) + { + g_object_set(mSubtitleControlHandle, "mute", TRUE, NULL); + } + else + { + MW_LOG_INFO("PlayerRialtoCCManager::Failed to mute"); + } + return; +} + +/* NOTE WELL: SetDigitalChannel() and SetAnalogChannel() should never be +** called as they are only called from the base class implementation of +** SetTrack(), which we override. +** +** However, they are declared pure virtual in the base class, so we need +** these stubs to satisfy that. +** Further, their return code is strictly an enum which is subtec-specific +** (CC_VL_OS_API_RESULT), so this should be moved from the base class to +** the subtec class. +*/ + +/** + * @fn SetDigitalChannel + * + * @return CC_VL_OS_API_RESULT + */ +int PlayerRialtoCCManager::SetDigitalChannel(unsigned int id) +{ + MW_LOG_ERR("PlayerRialtoCCManager::Should not be called! (%u)", id); + return 0; +} + +/** + * @fn SetAnalogChannel + * + * @return CC_VL_OS_API_RESULT + */ +int PlayerRialtoCCManager::SetAnalogChannel(unsigned int id) +{ + MW_LOG_ERR("PlayerRialtoCCManager::Should not be called! (%u)", id); + return 0; +} + +/** + * @brief Constructor + */ +PlayerRialtoCCManager::PlayerRialtoCCManager() +{ + return; +} diff --git a/middleware/closedcaptions/rialto/PlayerRialtoCCManager.h b/middleware/closedcaptions/rialto/PlayerRialtoCCManager.h new file mode 100644 index 000000000..bbbd98823 --- /dev/null +++ b/middleware/closedcaptions/rialto/PlayerRialtoCCManager.h @@ -0,0 +1,129 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerRialtoCCManager.h + * + * @brief Integration layer of Rialto ClosedCaption in Middleware + * + */ + +#ifndef __PLAYER_RIALTO_CC_MANAGER_H__ +#define __PLAYER_RIALTO_CC_MANAGER_H__ + +#include "PlayerCCManager.h" + +#include +#include +#include + +/** + * @class PlayerRialtoCCManager + * @brief Handling Rialto CC operation + */ + +class PlayerRialtoCCManager : public PlayerCCManagerBase +{ +public: + + /** + * @fn Release + * @param[in] id - returned from GetId function + */ + void Release(int iID) override; + + /** + * @fn GetId + * @return int - unique ID + */ + int GetId() override; + + /** + * @fn SetTrack + * + * @param[in] track - CC track to be selected + * @param[in] format - force track to 608/708 or default (not used) + * @return int - 0 on success, -1 on failure + */ + int SetTrack(const std::string &track, const CCFormat format = eCLOSEDCAPTION_FORMAT_DEFAULT) override; + + /** + * @fn PlayerRialtoCCManager + */ + PlayerRialtoCCManager(); + + /** + * @brief Destructor + */ + ~PlayerRialtoCCManager() = default; + + PlayerRialtoCCManager(const PlayerRialtoCCManager&) = delete; + PlayerRialtoCCManager& operator=(const PlayerRialtoCCManager&) = delete; + +private: + /** + * @fn StartRendering + * + * @return void + */ + void StartRendering() override; + + /** + * @fn StopRendering + * + * @return void + */ + void StopRendering() override; + + /** + * @brief Impl specific initialization code called once in Init() function + * + * @return 0 - success, -1 - failure + */ + int Initialize(void *handle) override; + + /** + * @fn SetDigitalChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetDigitalChannel(unsigned int id) override; + /** + * @fn SetAnalogChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetAnalogChannel(unsigned int id) override; + + /** + * @fn ResetState + * + * @return void + */ + void ResetState() override; + +private: + void *mSubtitleControlHandle{nullptr}; + + std::mutex mIdLock{}; + int mId{0}; + std::set mIdSet{}; +}; + +#endif /* __PLAYER_RIALTO_CC_MANAGER_H__ */ diff --git a/middleware/closedcaptions/subtec/CCDataController.cpp b/middleware/closedcaptions/subtec/CCDataController.cpp new file mode 100644 index 000000000..679e2b5d8 --- /dev/null +++ b/middleware/closedcaptions/subtec/CCDataController.cpp @@ -0,0 +1,332 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file CCDataController.cpp + * + * @brief Impl of subtec communication layer + * + */ + +#include +#include + +#include "CCDataController.h" + +#include "PlayerLogManager.h" + + +namespace subtecConnector +{ + +namespace +{ + gsw_CcAttributes createDefaultAttributes() + { + gsw_CcAttributes attribs; + memset(&attribs, 0, sizeof(struct gsw_CcAttributes)); + attribs.charBgColor.rgb = GSW_CC_EMBEDDED_COLOR; + attribs.charFgColor.rgb = GSW_CC_EMBEDDED_COLOR; + attribs.winColor.rgb = GSW_CC_EMBEDDED_COLOR; + attribs.charBgOpacity = GSW_CC_OPACITY_EMBEDDED; + attribs.charFgOpacity = GSW_CC_OPACITY_EMBEDDED; + attribs.winOpacity = GSW_CC_OPACITY_EMBEDDED; + attribs.fontSize = GSW_CC_FONT_SIZE_EMBEDDED; + + std::memcpy(attribs.fontStyle , GSW_CC_FONT_STYLE_EMBEDDED, sizeof(GSW_CC_FONT_STYLE_EMBEDDED)); + + attribs.fontItalic = GSW_CC_TEXT_STYLE_EMBEDDED_TEXT; + attribs.fontUnderline = GSW_CC_TEXT_STYLE_EMBEDDED_TEXT; + attribs.borderType = GSW_CC_BORDER_TYPE_EMBEDDED; + attribs.borderColor.rgb = GSW_CC_EMBEDDED_COLOR; + attribs.edgeType = GSW_CC_EDGE_TYPE_EMBEDDED; + attribs.edgeColor.rgb = GSW_CC_EMBEDDED_COLOR; + return attribs; + } +} + + +CCDataController* CCDataController::Instance() +{ + static CCDataController instance; + return &instance; +} + +void CCDataController::closedCaptionDataCb (int decoderIndex, VL_CC_DATA_TYPE eType, unsigned char* ccData, + unsigned dataLength, int sequenceNumber, long long localPts) +{ + channel.SendDataPacketWithPTS(localPts, ccData, dataLength); +} + +void CCDataController::closedCaptionDecodeCb(int decoderIndex, int event) +{ + MW_LOG_WARN("closedCaptionDecodeCb decoderIndex = %d, event = %d", decoderIndex, event); +} + +void CCDataController::sendMute() +{ + channel.SendMutePacket(); +} + +void CCDataController::sendUnmute() +{ + channel.SendUnmutePacket(); +} + +void CCDataController::sendPause() +{ + channel.SendPausePacket(); +} + +void CCDataController::sendResume() +{ + channel.SendResumePacket(); +} + +void CCDataController::sendResetChannelPacket() +{ + channel.SendResetChannelPacket(); +} + +CCDataController::CCDataController() + : channel{} + , currentAttributes{createDefaultAttributes()} +{ +} + +void CCDataController::ccSetDigitalChannel(unsigned int channelId) +{ + channel.SendActiveTypePacket(ClosedCaptionsActiveTypePacket::CEA::type_708, channelId); +} + +void CCDataController::ccSetAnalogChannel(unsigned int channelId) +{ + channel.SendActiveTypePacket(ClosedCaptionsActiveTypePacket::CEA::type_608, channelId); +} + +void CCDataController::ccGetAttributes(gsw_CcAttributes * attrib, gsw_CcType /*ccType*/) +{ + std::memcpy(attrib, ¤tAttributes, sizeof(gsw_CcAttributes)); +} + +namespace +{ + uint32_t getValue(const gsw_CcColor& attrib) + { + return static_cast(attrib.rgb); + } + uint32_t getValue(const gsw_CcOpacity& attrib) + { + return static_cast(attrib); + } + uint32_t getValue(const gsw_CcFontSize& attrib) + { + return static_cast(attrib); + } + uint32_t getValue(const gsw_CcFontStyle& attrib) + { + // values based on cea-708 + static const std::unordered_map valuesMap{ + {GSW_CC_FONT_STYLE_DEFAULT, 0}, + {GSW_CC_FONT_STYLE_MONOSPACED_SERIF, 1}, + {GSW_CC_FONT_STYLE_PROPORTIONAL_SERIF, 2}, + {GSW_CC_FONT_STYLE_MONOSPACED_SANSSERIF, 3}, + {GSW_CC_FONT_STYLE_PROPORTIONAL_SANSSERIF, 4}, + {GSW_CC_FONT_STYLE_CASUAL, 5}, + {GSW_CC_FONT_STYLE_CURSIVE, 6}, + {GSW_CC_FONT_STYLE_SMALL_CAPITALS, 7}, + }; + + const auto it = valuesMap.find(attrib); + if(it != valuesMap.end()) + return it->second; + else + { + MW_LOG_WARN("Cannot match %s to attribute value", attrib); + return 0; // value for default + } + + } + uint32_t getValue(const gsw_CcTextStyle& attrib) + { + return static_cast(attrib); + } + uint32_t getValue(const gsw_CcBorderType& attrib) + { + return static_cast(attrib); + } + uint32_t getValue(const gsw_CcEdgeType& attrib) + { + return static_cast(attrib); + } + uint32_t getValue(const gsw_CcType& type) + { + return static_cast(type); + } + + uint32_t getValue(gsw_CcAttributes * attrib, short type) + { + switch(type) + { + case GSW_CC_ATTRIB_BACKGROUND_COLOR: + return getValue(attrib->charBgColor); + case GSW_CC_ATTRIB_FONT_COLOR: + return getValue(attrib->charFgColor); + case GSW_CC_ATTRIB_WIN_COLOR: + return getValue(attrib->winColor); + case GSW_CC_ATTRIB_BACKGROUND_OPACITY: + return getValue(attrib->charBgOpacity); + case GSW_CC_ATTRIB_FONT_OPACITY: + return getValue(attrib->charFgOpacity); + case GSW_CC_ATTRIB_WIN_OPACITY: + return getValue(attrib->winOpacity); + case GSW_CC_ATTRIB_FONT_SIZE: + return getValue(attrib->fontSize); + case GSW_CC_ATTRIB_FONT_STYLE: + return getValue(attrib->fontStyle); + case GSW_CC_ATTRIB_FONT_ITALIC: + return getValue(attrib->fontItalic); + case GSW_CC_ATTRIB_FONT_UNDERLINE: + return getValue(attrib->fontUnderline); + case GSW_CC_ATTRIB_BORDER_TYPE: + return getValue(attrib->borderType); + case GSW_CC_ATTRIB_BORDER_COLOR: + return getValue(attrib->borderColor); + case GSW_CC_ATTRIB_EDGE_TYPE: + return getValue(attrib->edgeType); + case GSW_CC_ATTRIB_EDGE_COLOR: + return getValue(attrib->edgeColor); + default: + MW_LOG_WARN("wrong attribute type used 0x%x",type); + return -1; + + } + } + + void setValue(gsw_CcAttributes* currentAttributes, gsw_CcAttributes * attrib, short type) + { + switch(type) + { + case GSW_CC_ATTRIB_BACKGROUND_COLOR: + currentAttributes->charBgColor = attrib->charBgColor; + return; + case GSW_CC_ATTRIB_FONT_COLOR: + currentAttributes->charFgColor = attrib->charFgColor; + return; + case GSW_CC_ATTRIB_WIN_COLOR: + currentAttributes->winColor = attrib->winColor; + return; + case GSW_CC_ATTRIB_BACKGROUND_OPACITY: + currentAttributes->charBgOpacity = attrib->charBgOpacity; + return; + case GSW_CC_ATTRIB_FONT_OPACITY: + currentAttributes->charFgOpacity = attrib->charFgOpacity; + return; + case GSW_CC_ATTRIB_WIN_OPACITY: + currentAttributes->winOpacity = attrib->winOpacity; + return; + case GSW_CC_ATTRIB_FONT_SIZE: + currentAttributes->fontSize = attrib->fontSize; + return; + case GSW_CC_ATTRIB_FONT_STYLE: + std::memcpy(currentAttributes->fontStyle, attrib->fontStyle, GSW_CC_MAX_FONT_NAME_LENGTH); + // currentAttributes->fontStyle = attrib->fontStyle; + return; + case GSW_CC_ATTRIB_FONT_ITALIC: + currentAttributes->fontItalic = attrib->fontItalic; + return; + case GSW_CC_ATTRIB_FONT_UNDERLINE: + currentAttributes->fontUnderline = attrib->fontUnderline; + return; + case GSW_CC_ATTRIB_BORDER_TYPE: + currentAttributes->borderType = attrib->borderType; + return; + case GSW_CC_ATTRIB_BORDER_COLOR: + currentAttributes->borderColor = attrib->borderColor; + return; + case GSW_CC_ATTRIB_EDGE_TYPE: + currentAttributes->edgeType = attrib->edgeType; + return; + case GSW_CC_ATTRIB_EDGE_COLOR: + currentAttributes->edgeColor = attrib->edgeColor; + return; + default: + MW_LOG_WARN("%s: wrong attribute type used 0x%x",__func__, type); + return; + + } + } +} + +void CCDataController::sendCCSetAttribute(gsw_CcAttributes * attrib, short type, gsw_CcType ccType) +{ + using AttributesArray = std::array ; + + static constexpr AttributesArray masks = { + GSW_CC_ATTRIB_FONT_COLOR, + GSW_CC_ATTRIB_BACKGROUND_COLOR, + GSW_CC_ATTRIB_FONT_OPACITY, + GSW_CC_ATTRIB_BACKGROUND_OPACITY, + GSW_CC_ATTRIB_FONT_STYLE, + GSW_CC_ATTRIB_FONT_SIZE, + GSW_CC_ATTRIB_FONT_ITALIC, + GSW_CC_ATTRIB_FONT_UNDERLINE, + GSW_CC_ATTRIB_BORDER_TYPE, + GSW_CC_ATTRIB_BORDER_COLOR, + GSW_CC_ATTRIB_WIN_COLOR, + GSW_CC_ATTRIB_WIN_OPACITY, + GSW_CC_ATTRIB_EDGE_TYPE, + GSW_CC_ATTRIB_EDGE_COLOR, + }; + + AttributesArray attributes{}; + + for(int i = 0; i(context); + p->closedCaptionDecodeCb(decoderIndex, event); +} + +void closedCaptionDataCb (void *context, int decoderIndex, VL_CC_DATA_TYPE eType, unsigned char* ccData, + unsigned dataLength, int sequenceNumber, long long localPts) +{ + CCDataController* p = static_cast(context); + p->closedCaptionDataCb(decoderIndex, eType, ccData, dataLength, sequenceNumber, localPts); +} + + +} // subtecConnector diff --git a/middleware/closedcaptions/subtec/CCDataController.h b/middleware/closedcaptions/subtec/CCDataController.h new file mode 100644 index 000000000..178afdc27 --- /dev/null +++ b/middleware/closedcaptions/subtec/CCDataController.h @@ -0,0 +1,89 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file CCDataController.h + * + * @brief Impl of subtec communication layer + * + */ + +#ifndef __CC_DATA_CONTROLLER_H__ +#define __CC_DATA_CONTROLLER_H__ + +#include + +#include "ccDataReader.h" + +#include "SubtecConnector.h" + +/** + * @brief Print logs to console / log file + * @param[in] format - printf style string + * @return void + */ +extern void logprintf(int playerId,const char* levelstr,const char* file, int line,const char *format, ...); + +namespace subtecConnector +{ + +/** + * @brief Controller for CCdata + */ +class CCDataController +{ +public: + static CCDataController* Instance(); + + void closedCaptionDataCb (int decoderIndex, VL_CC_DATA_TYPE eType, unsigned char* ccData, + unsigned dataLength, int sequenceNumber, long long localPts); + + void closedCaptionDecodeCb(int decoderIndex, int event); + + void sendMute(); + void sendUnmute(); + + void sendPause(); + void sendResume(); + void sendResetChannelPacket(); + void sendCCSetAttribute(gsw_CcAttributes * attrib, short type, gsw_CcType ccType); + + void ccSetDigitalChannel(unsigned int channel); + void ccSetAnalogChannel(unsigned int channel); + + void ccGetAttributes(gsw_CcAttributes * attrib, gsw_CcType ccType); + +private: + CCDataController(); + CCDataController(const CCDataController&) = delete; + CCDataController(CCDataController&&) = delete; + ClosedCaptionsChannel channel; + + gsw_CcAttributes currentAttributes; +}; + +void closedCaptionDecodeCb(void *context, int decoderIndex, int event); + +void closedCaptionDataCb (void *context, int decoderIndex, VL_CC_DATA_TYPE eType, unsigned char* ccData, + unsigned dataLength, int sequenceNumber, long long localPts); + + +} // subtecConnector + +#endif //__CC_DATA_CONTROLLER_H__ diff --git a/middleware/closedcaptions/subtec/PlayerSubtecCCManager.cpp b/middleware/closedcaptions/subtec/PlayerSubtecCCManager.cpp new file mode 100644 index 000000000..3ff1517a8 --- /dev/null +++ b/middleware/closedcaptions/subtec/PlayerSubtecCCManager.cpp @@ -0,0 +1,202 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerSubtecCCManager.cpp + * + * @brief Impl of Subtec ClosedCaption integration layer + * + */ + +#include "PlayerSubtecCCManager.h" + +#include "PlayerLogManager.h" // Included for MW_LOG +//TODO: Fix cyclic dependency btw GlobalConfig and PlayerLogManager + +#include "SubtecConnector.h" + +/** + * @brief Impl specific initialization code called before each public interface call + */ +void PlayerSubtecCCManager::EnsureInitialized() +{ + EnsureHALInitialized(); + EnsureRendererCommsInitialized(); +} + +/** + * @brief Impl specific initialization code for HAL + */ +void PlayerSubtecCCManager::EnsureHALInitialized() +{ + if(not mHALInitialized || mHandleUpdated) + { + if(subtecConnector::initHal((void *)mCCHandle) == CC_VL_OS_API_RESULT_SUCCESS) + { + MW_LOG_WARN("PlayerSubtecCCManager::calling subtecConnector::initHal() - success"); + mHALInitialized = true; + mHandleUpdated = false; + } + else + { + MW_LOG_WARN("PlayerSubtecCCManager::calling subtecConnector::initHal() - failure"); + } + } +}; + +/** + * @brief Impl specific initialization code for Communication with rendered + */ +void PlayerSubtecCCManager::EnsureRendererCommsInitialized() +{ + if(not mRendererInitialized) + { + if(subtecConnector::initPacketSender() == CC_VL_OS_API_RESULT_SUCCESS) + { + MW_LOG_WARN("PlayerSubtecCCManager::calling subtecConnector::initPacketSender() - success"); + mRendererInitialized = true; + } + else + { + MW_LOG_WARN("PlayerSubtecCCManager::calling subtecConnector::initPacketSender() - failure"); + } + } +}; + +/** + * @brief stores Handle + */ +int PlayerSubtecCCManager::Initialize(void * handle) +{ + if (mCCHandle != handle) + mHandleUpdated = true; + mCCHandle = handle; + + return 0; +} + + +/** + * @brief Gets Handle or ID, Every client using subtec must call GetId in the beginning , save id, which is required for Release function. + */ +int PlayerSubtecCCManager::GetId() +{ + std::lock_guard lock(mIdLock); + mId++; + mIdSet.insert(mId); + return mId; +} + +/** + * @brief Release CC resources + */ +void PlayerSubtecCCManager::Release(int id) +{ + std::lock_guard lock(mIdLock); + if( mIdSet.erase(id) > 0 ) + { + int iSize = mIdSet.size(); + MW_LOG_WARN("PlayerSubtecCCManager::users:%d",iSize); + //No one using subtec, stop/close it. + if(0 == iSize) + { + subtecConnector::resetChannel(); + if(mHALInitialized) + { + subtecConnector::close(); + mHALInitialized = false; + if (mCCHandle != NULL) + { + mCCHandle = NULL; + } + } + mTrickplayStarted = false; + mParentalCtrlLocked = false; + } + } + else + { + MW_LOG_TRACE("PlayerSubtecCCManager::ID:%d not found returning",id); + } +} + +/** + * @brief To start CC rendering + */ +void PlayerSubtecCCManager::StartRendering() +{ + subtecConnector::ccMgrAPI::ccShow(); +} + +/** + * @brief To stop CC rendering + */ +void PlayerSubtecCCManager::StopRendering() +{ + subtecConnector::ccMgrAPI::ccHide(); +} + +/** + * @brief set digital channel with specified id + */ +int PlayerSubtecCCManager::SetDigitalChannel(unsigned int id) +{ + const auto ret = subtecConnector::ccMgrAPI::ccSetDigitalChannel(id); + EnsureRendererStateConsistency(); + return ret; +} + +/** + * @brief set analog channel with specified id + */ +int PlayerSubtecCCManager::SetAnalogChannel(unsigned int id) +{ + const auto ret = subtecConnector::ccMgrAPI::ccSetAnalogChannel(id); + EnsureRendererStateConsistency(); + return ret; +} + +/** + * @brief ensure mRendering is consistent with renderer state + */ +void PlayerSubtecCCManager::EnsureRendererStateConsistency() +{ + MW_LOG_WARN("PlayerSubtecCCManager::"); + if(mEnabled) + { + Start(); + } + else + { + Stop(); + } + SetStyle(mOptions); +} + +/** + * @brief Constructor + */ +PlayerSubtecCCManager::PlayerSubtecCCManager() +{ + // Some of the apps don’t call set track and as default CC is not set, CC doesn’t work. + // In this case app expect to render default cc as CC1. + // Hence Set default CC track to CC1 + MW_LOG_WARN("PlayerSubtecCCManager::PlayerSubtecCCManager setting default to cc1"); + SetTrack("CC1"); +} diff --git a/middleware/closedcaptions/subtec/PlayerSubtecCCManager.h b/middleware/closedcaptions/subtec/PlayerSubtecCCManager.h new file mode 100644 index 000000000..76d07ea5b --- /dev/null +++ b/middleware/closedcaptions/subtec/PlayerSubtecCCManager.h @@ -0,0 +1,144 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerSubtecCCManager.h + * + * @brief Integration layer of Subtec ClosedCaption in Middleware + * + */ + +#ifndef __PLAYER_SUBTEC_CC_MANAGER_H__ +#define __PLAYER_SUBTEC_CC_MANAGER_H__ + +#include "PlayerCCManager.h" + +#include +#include +#include +#include "SubtecConnector.h" + + +/** + * @class PlayerSubtecCCManager + * @brief Handling Subtec CC operation + */ + +class PlayerSubtecCCManager : public PlayerCCManagerBase +{ +public: + + /** + * @fn Release + * @param[in] id - returned from GetId function + */ + void Release(int iID) override; + + /** + * @fn GetId + * @return int - unique ID + */ + virtual int GetId(); + + /** + * @fn PlayerSubtecCCManager + */ + PlayerSubtecCCManager(); + + /** + * @brief Destructor + */ + ~PlayerSubtecCCManager() = default; + + PlayerSubtecCCManager(const PlayerSubtecCCManager&) = delete; + PlayerSubtecCCManager& operator=(const PlayerSubtecCCManager&) = delete; + +private: + /** + * @fn StartRendering + * + * @return void + */ + void StartRendering() override; + + /** + * @fn StopRendering + * + * @return void + */ + void StopRendering() override; + + /** + * @fn EnsureInitialized + * @return void + */ + void EnsureInitialized() override; + + /** + * @fn EnsureHALInitialized + * @return void + */ + void EnsureHALInitialized() override; + + /** + * @brief Impl specific initialization code called once in Init() function + * + * @return 0 - success, -1 - failure + */ + int Initialize(void *handle) override; + + /** + * @fn EnsureRendererCommsInitialized + * @return void + */ + void EnsureRendererCommsInitialized() override; + + /** + * @fn SetDigitalChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetDigitalChannel(unsigned int id) override; + /** + * @fn SetAnalogChannel + * + * @return CC_VL_OS_API_RESULT + */ + int SetAnalogChannel(unsigned int id) override; + + /** + * @fn EnsureRendererStateConsistency + * + * @return void + */ + void EnsureRendererStateConsistency(); + + void *mCCHandle{nullptr}; /**< Decoder handle for initializing CC resources */ + + +private: + bool mRendererInitialized{false}; + bool mHALInitialized{false}; + bool mHandleUpdated{false}; + std::mutex mIdLock{}; + int mId{0}; + std::set mIdSet{}; +}; + +#endif /* __PLAYER_SUBTEC_CC_MANAGER_H__ */ diff --git a/middleware/closedcaptions/subtec/SubtecConnector.cpp b/middleware/closedcaptions/subtec/SubtecConnector.cpp new file mode 100644 index 000000000..0cf4a4d9f --- /dev/null +++ b/middleware/closedcaptions/subtec/SubtecConnector.cpp @@ -0,0 +1,168 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file SubtecConnector.cpp + * + * @brief Impl of libsubtec_connector + * + */ + +#include "SubtecConnector.h" +#include "CCDataController.h" +#include "ccDataReader.h" + + +#include +#include +#include + +#include +#include + +#include "PlayerLogManager.h" + + +namespace subtecConnector +{ + mrcc_Error initHal(void * handle) + { + const auto registerResult = vlhal_cc_Register(0, CCDataController::Instance(), closedCaptionDataCb, closedCaptionDecodeCb); + MW_LOG_WARN("vlhal_cc_Register return value = %d", registerResult); + + if(registerResult != 0) + return CC_VL_OS_API_RESULT_FAILED; + + const auto startResult = media_closeCaptionStart(handle); + MW_LOG_WARN("media_closeCaptionStart return value = %d", startResult); + + if(startResult != 0) + return CC_VL_OS_API_RESULT_FAILED; + + return CC_VL_OS_API_RESULT_SUCCESS; + } + + mrcc_Error initPacketSender() + { + const auto packetSenderStartResult = ClosedCaptionsChannel::InitComms(); + MW_LOG_WARN("CCDataController::Instance()->InitComms() return value = %d", (int)packetSenderStartResult); + + if(!packetSenderStartResult) + return CC_VL_OS_API_RESULT_FAILED; + + return CC_VL_OS_API_RESULT_SUCCESS; + } + void resetChannel() + { + CCDataController::Instance()->sendResetChannelPacket(); + } + + void close() + { + media_closeCaptionStop(); + } + + +namespace ccMgrAPI +{ + + typedef int mrcc_Error; + + mrcc_Error ccShow(void) + { + CCDataController::Instance()->sendUnmute(); + return {}; + } + + mrcc_Error ccHide(void) + { + CCDataController::Instance()->sendMute(); + return {}; + } + + mrcc_Error ccSetAttributes(gsw_CcAttributes * attrib, short type, gsw_CcType ccType) + { + CCDataController::Instance()->sendCCSetAttribute(attrib, type, ccType); + return {}; + } + + mrcc_Error ccSetDigitalChannel(unsigned int channel) + { + CCDataController::Instance()->ccSetDigitalChannel(channel); + return {}; + } + + mrcc_Error ccSetAnalogChannel(unsigned int channel) + { + CCDataController::Instance()->ccSetAnalogChannel(channel); + return {}; + } + + mrcc_Error ccGetAttributes(gsw_CcAttributes * attrib, gsw_CcType ccType) + { + CCDataController::Instance()->ccGetAttributes(attrib, ccType); + return {}; + } + + mrcc_Error ccGetCapability(gsw_CcAttribType attribType, gsw_CcType ccType, void **values, unsigned int *size) + { + + if(!values || !size) + { + return CC_VL_OS_API_RESULT_FAILED; + } + + *size = 0; //default case + int i = 0; + + + switch(attribType) + { + case GSW_CC_ATTRIB_FONT_COLOR : + case GSW_CC_ATTRIB_BACKGROUND_COLOR: + case GSW_CC_ATTRIB_BORDER_COLOR: + case GSW_CC_ATTRIB_WIN_COLOR: + case GSW_CC_ATTRIB_EDGE_COLOR: + { + *size = sizeof(CCSupportedColors_strings)/sizeof(char *); + const char** pValuesNames = CCSupportedColors_strings; + + *size = sizeof(CCSupportedColors)/sizeof( unsigned long); + const unsigned long * pValues = CCSupportedColors; + // Copy our colors into the given capability array + + for(i = 0; i < *size; i++) + { + ((gsw_CcColor*)values[i])->rgb = pValues[i]; + strncpy(((gsw_CcColor*)values[i])->name, pValuesNames[i], + GSW_MAX_CC_COLOR_NAME_LENGTH); + } + break; + } + default: + MW_LOG_WARN("ccGetCapability invoked with not supported attribType = 0x%x", (int)attribType); + return CC_VL_OS_API_RESULT_FAILED; + } + + return CC_VL_OS_API_RESULT_SUCCESS; + } + + +} // ccMgrAPI +} // subtecConnector diff --git a/middleware/closedcaptions/subtec/SubtecConnector.h b/middleware/closedcaptions/subtec/SubtecConnector.h new file mode 100644 index 000000000..0ff57e2a0 --- /dev/null +++ b/middleware/closedcaptions/subtec/SubtecConnector.h @@ -0,0 +1,404 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file SubtecConnector.h + * + * @brief Interface header for libsubtec_connector + * + */ + +#ifndef __SUBTEC_CONNECTOR_H__ +#define __SUBTEC_CONNECTOR_H__ + + +/* Closed Captioning Color definition */ + +#define GSW_CC_EMBEDDED_COLOR (0xff000000) +#define GSW_CC_COLOR(r,g,b) ( (((r) & 0xFF) << 16) | (((g) & 0xFF) << 8) | ((b) & 0xFF) ) + +#define GSW_MAX_CC_COLOR_NAME_LENGTH 32 + +/** + * @struct gsw_CcColor + * @brief Structure to hold color information for CC + */ + +typedef struct gsw_CcColor { + int rgb; + char name[GSW_MAX_CC_COLOR_NAME_LENGTH]; +} gsw_CcColor; +// Maximum number of CC color capability values +#define GSW_CC_COLOR_MAX 32 + + +/** + * @enum gsw_CcType + * @brief Closed Captioning type + */ +typedef enum gsw_CcType { + GSW_CC_TYPE_ANALOG, + GSW_CC_TYPE_DIGITAL, + GSW_CC_TYPE_MAX +} gsw_CcType; + + +/** + * @enum gsw_CcOpacity + * @brief Closed Captioning Opacity + */ +typedef enum gsw_CcOpacity { + GSW_CC_OPACITY_EMBEDDED = -1, + GSW_CC_OPACITY_SOLID, + GSW_CC_OPACITY_FLASHING, + GSW_CC_OPACITY_TRANSLUCENT, + GSW_CC_OPACITY_TRANSPARENT, + GSW_CC_OPACITY_MAX +} gsw_CcOpacity; + +/** + * @enum gsw_CcFontSize + * @brief Closed caption Fontsize + */ +typedef enum gsw_CcFontSize { + GSW_CC_FONT_SIZE_EMBEDDED = -1, + GSW_CC_FONT_SIZE_SMALL, + GSW_CC_FONT_SIZE_STANDARD, + GSW_CC_FONT_SIZE_LARGE, + GSW_CC_FONT_SIZE_EXTRALARGE, + GSW_CC_FONT_SIZE_MAX +} gsw_CcFontSize; + + +static const char *CCSupportedColors_strings[] = { + "BLACK", /**< CCColor_BLACK, */ + "WHITE", /**< CCColor_WHITE, */ + "RED", /**< CCColor_RED, */ + "GREEN", /**< CCColor_GREEN, */ + "BLUE", /**< CCColor_BLUE, */ + "YELLOW", /**< CCColor_YELLOW, */ + "MAGENTA", /**< CCColor_MAGENTA, */ + "CYAN", /**< CCColor_CYAN, */ + "AUTO", /**< CCColor_AUTO, */ +}; + +static unsigned long CCSupportedColors[] = { + 0x00000000, /**< CCColor_BLACK, */ + 0x00ffffff, /**< CCColor_WHITE, */ + 0x00FF0000, /**< CCColor_RED, */ + 0x0000FF00, /**< CCColor_GREEN, */ + 0x000000FF, /**< CCColor_BLUE, */ + 0x00FFFF00, /**< CCColor_YELLOW, */ + 0x00FF00FF, /**< CCColor_MAGENTA, */ + 0x0000FFFF, /**< CCColor_CYAN, */ + 0xFF000000, /**< CCColor_AUTO, */ +}; + + +/* Closed Captioning Font Style */ +#define GSW_CC_MAX_FONT_NAME_LENGTH 128 +//#define GSW_CC_FONT_STYLE_EMBEDDED "EMBEDDED" +typedef char gsw_CcFontStyle[GSW_CC_MAX_FONT_NAME_LENGTH]; + +#define GSW_CC_FONT_STYLE_EMBEDDED "embedded" //"EMBEDDED" +#define GSW_CC_FONT_STYLE_DEFAULT "Default" //"DEFAULT" +#define GSW_CC_FONT_STYLE_MONOSPACED_SERIF "MonospacedSerif" //"MONOSPACED_SERIF" +#define GSW_CC_FONT_STYLE_PROPORTIONAL_SERIF "ProportionalSerif" //"PROPORTIONAL_SERIF" +#define GSW_CC_FONT_STYLE_MONOSPACED_SANSSERIF "MonospacedSansSerif" //"MONOSPACED_SANSSERIF" +#define GSW_CC_FONT_STYLE_PROPORTIONAL_SANSSERIF "ProportionalSansSerif" //"PROPORTIONAL_SANSSERIF" +#define GSW_CC_FONT_STYLE_CASUAL "Casual" //"CASUAL" +#define GSW_CC_FONT_STYLE_CURSIVE "Cursive" //"CURSIVE" +#define GSW_CC_FONT_STYLE_SMALL_CAPITALS "SmallCapital" //"SMALL_CAPITALS" + + +/** + * @enum gsw_CcTextStyle + * @brief Closed captioning text styles. + */ +typedef enum gsw_CcTextStyle { + GSW_CC_TEXT_STYLE_EMBEDDED_TEXT = -1, + GSW_CC_TEXT_STYLE_FALSE, + GSW_CC_TEXT_STYLE_TRUE, + GSW_CC_TEXT_STYLE_MAX +} gsw_CcTextStyle; + + +/** + * @enum gsw_CcBorderType + * @brief Window Border type + */ +typedef enum gsw_CcBorderType { + GSW_CC_BORDER_TYPE_EMBEDDED = -1, + GSW_CC_BORDER_TYPE_NONE, + GSW_CC_BORDER_TYPE_RAISED, + GSW_CC_BORDER_TYPE_DEPRESSED, + GSW_CC_BORDER_TYPE_UNIFORM, + GSW_CC_BORDER_TYPE_SHADOW_LEFT, + GSW_CC_BORDER_TYPE_SHADOW_RIGHT, + GSW_CC_BORDER_TYPE_MAX +} gsw_CcBorderType; + +/** + * @enum gsw_CcEdgeType + * @brief Font Edge type + */ +typedef enum gsw_CcEdgeType { + GSW_CC_EDGE_TYPE_EMBEDDED = -1, + GSW_CC_EDGE_TYPE_NONE, + GSW_CC_EDGE_TYPE_RAISED, + GSW_CC_EDGE_TYPE_DEPRESSED, + GSW_CC_EDGE_TYPE_UNIFORM, + GSW_CC_EDGE_TYPE_SHADOW_LEFT, + GSW_CC_EDGE_TYPE_SHADOW_RIGHT, + GSW_CC_EDGE_TYPE_MAX +} gsw_CcEdgeType; + +/** + * @enum gsw_CcAttributes + * @brief Closed Captioning Attributes + */ +typedef struct gsw_CcAttributes { + gsw_CcColor charBgColor; /**< character background color */ + gsw_CcColor charFgColor; /**< character foreground color */ + gsw_CcColor winColor; /**< window color */ + gsw_CcOpacity charBgOpacity; /**< background opacity */ + gsw_CcOpacity charFgOpacity; /**< foreground opacity */ + gsw_CcOpacity winOpacity; /**< window opacity */ + gsw_CcFontSize fontSize; /**< font size */ + gsw_CcFontStyle fontStyle; /**< font style */ + gsw_CcTextStyle fontItalic; /**< italicized font */ + gsw_CcTextStyle fontUnderline; /**< underlined font */ + gsw_CcBorderType borderType; /**< window border type */ + gsw_CcColor borderColor; /**< window border color */ + gsw_CcEdgeType edgeType; /**< font edge type */ + gsw_CcColor edgeColor; /**< font edge color */ + +} gsw_CcAttributes; + +/** + * @enum gsw_CcAttribType + * @brief type of attributes + */ +typedef enum gsw_CcAttribType { + GSW_CC_ATTRIB_FONT_COLOR = 0x0001, + GSW_CC_ATTRIB_BACKGROUND_COLOR = 0x0002, + GSW_CC_ATTRIB_FONT_OPACITY = 0x0004, + GSW_CC_ATTRIB_BACKGROUND_OPACITY = 0x0008, + GSW_CC_ATTRIB_FONT_STYLE = 0x0010, + GSW_CC_ATTRIB_FONT_SIZE = 0x0020, + GSW_CC_ATTRIB_FONT_ITALIC = 0x0040, + GSW_CC_ATTRIB_FONT_UNDERLINE = 0x0080, + GSW_CC_ATTRIB_BORDER_TYPE = 0x0100, + GSW_CC_ATTRIB_BORDER_COLOR = 0x0200, + GSW_CC_ATTRIB_WIN_COLOR = 0x0400, + GSW_CC_ATTRIB_WIN_OPACITY = 0x0800, + GSW_CC_ATTRIB_EDGE_TYPE = 0x1000, + GSW_CC_ATTRIB_EDGE_COLOR = 0x2000, + GSW_CC_ATTRIB_MAX +} gsw_CcAttribType; + +/** + * @enum _CC_VL_OS_API_RESULT + * @brief CC API result + */ +typedef enum _CC_VL_OS_API_RESULT { + CC_VL_OS_API_RESULT_SUCCESS = 0x0, + CC_VL_OS_API_RESULT_FAILED = 0x1000000, + CC_VL_OS_API_RESULT_CHECK_ERRNO = 0x1000001, + CC_VL_OS_API_RESULT_UNSPECIFIED_ERROR = 0x1000002, + CC_VL_OS_API_RESULT_ACCESS_DENIED = 0x1000003, + CC_VL_OS_API_RESULT_NOT_IMPLEMENTED = 0x1000004, + CC_VL_OS_API_RESULT_NOT_EXISTING = 0x1000005, + CC_VL_OS_API_RESULT_NULL_PARAM = 0x1000006, + CC_VL_OS_API_RESULT_INVALID_PARAM = 0x1000007, + CC_VL_OS_API_RESULT_OUT_OF_RANGE = 0x1000008, + CC_VL_OS_API_RESULT_OPEN_FAILED = 0x1000009, + CC_VL_OS_API_RESULT_READ_FAILED = 0x1000010, + CC_VL_OS_API_RESULT_WRITE_FAILED = 0x1000011, + CC_VL_OS_API_RESULT_MALLOC_FAILED = 0x1000012, + CC_VL_OS_API_RESULT_TIMEOUT = 0x1000013, + CC_VL_OS_API_RESULT_INFINITE_LOOP = 0x1000014, + CC_VL_OS_API_RESULT_BUFFER_OVERFLOW = 0x1000015, + +} CC_VL_OS_API_RESULT; + +/** + * @enum CCAnalogChannel_t + * @brief Defines closed captioning analog channels. Each of these channels defines + * a different service. For example, CC1 is the "Primary Synchronous + * Caption Service", and CC2 is the "Secondary Synchronous Caption Service". + */ + +typedef enum { + + CCChannel_INCLUSIVE_MINIMUM = 1000, /**< Not set */ + + CCChannel_CC1, /**< CC1 */ + + CCChannel_CC2, /**< CC2 */ + + CCChannel_CC3, /**< CC3 */ + + CCChannel_CC4, /**< CC4 */ + + CCChannel_TEXT1, /**< Text 1 */ + + CCChannel_TEXT2, /**< Text 2 */ + + CCChannel_TEXT3, /**< Text 3 */ + + CCChannel_TEXT4, /**< Text 4 */ + + CCChannel_NONE, /**< Used to Set Digital channel to none. (Used to Disable Analog CC ) */ + + CCChannel_XDS, /**< XDS */ + + CCChannel_EXCLUSIVE_MAXIMUM /**< Exclusive bounds check */ +} CCAnalogChannel_t; + +typedef enum gsw_CcAnalogServices { + GSW_CC_ANALOG_SERVICE_NONE = 0, + GSW_CC_ANALOG_SERVICE_CC1 = 1000, /**< Primary Caption service*/ + GSW_CC_ANALOG_SERVICE_CC2 = 1001, /**< Secondary Caption service */ + GSW_CC_ANALOG_SERVICE_CC3 = 1002, /**< Caption 3 */ + GSW_CC_ANALOG_SERVICE_CC4 = 1003, /**< Caption 4 */ + GSW_CC_ANALOG_SERVICE_T1 = 1004, /**< Text 1 */ + GSW_CC_ANALOG_SERVICE_T2 = 1005, /**< Text 2 */ + GSW_CC_ANALOG_SERVICE_T3 = 1006, /**< Text 3 */ + GSW_CC_ANALOG_SERVICE_T4 = 1007 /**< Text 4 */ +} gsw_CcAnalogServices; + +/** + * @enum CCDigitalChannel_t + * @brief Defines closed captioning digital channels. Each of these channels defines + * a different service. + */ +typedef enum { + CCDigitalChannel_INCLUSIVE_MINIMUM = 0, /* Not set */ + CCDigitalChannel_DS1, /**< DS1 */ + CCDigitalChannel_DS2, /**< DS2 */ + CCDigitalChannel_DS3, /**< DS3 */ + CCDigitalChannel_DS4, /**< DS4 */ + CCDigitalChannel_DS5, /**< DS5 */ + CCDigitalChannel_DS6, /**< DS6 */ + CCDigitalChannel_DS7, /**< DS7 */ + CCDigitalChannel_DS8, /**< DS8 */ + CCDigitalChannel_DS9, /**< DS9 */ + CCDigitalChannel_DS10, /**< DS10 */ + CCDigitalChannel_DS11, /**< DS11 */ + CCDigitalChannel_DS12, /**< DS12 */ + CCDigitalChannel_DS13, /**< DS13 */ + CCDigitalChannel_DS14, /**< DS14 */ + CCDigitalChannel_DS15, /**< DS15 */ + CCDigitalChannel_DS16, /**< DS16 */ + CCDigitalChannel_DS17, /**< DS17 */ + CCDigitalChannel_DS18, /**< DS18 */ + CCDigitalChannel_DS19, /**< DS19 */ + CCDigitalChannel_DS20, /**< DS20 */ + CCDigitalChannel_DS21, /**< DS21 */ + CCDigitalChannel_DS22, /**< DS22 */ + CCDigitalChannel_DS23, /**< DS23 */ + CCDigitalChannel_DS24, /**< DS24 */ + CCDigitalChannel_DS25, /**< DS25 */ + CCDigitalChannel_DS26, /**< DS26 */ + CCDigitalChannel_DS27, /**< DS27 */ + CCDigitalChannel_DS28, /**< DS28 */ + CCDigitalChannel_DS29, /**< DS29 */ + CCDigitalChannel_DS30, /**< DS30 */ + CCDigitalChannel_DS31, /**< DS31 */ + CCDigitalChannel_DS32, /**< DS32 */ + CCDigitalChannel_DS33, /**< DS33 */ + CCDigitalChannel_DS34, /**< DS34 */ + CCDigitalChannel_DS35, /**< DS35 */ + CCDigitalChannel_DS36, /**< DS36 */ + CCDigitalChannel_DS37, /**< DS37 */ + CCDigitalChannel_DS38, /**< DS38 */ + CCDigitalChannel_DS39, /**< DS39 */ + CCDigitalChannel_DS40, /**< DS40 */ + CCDigitalChannel_DS41, /**< DS41 */ + CCDigitalChannel_DS42, /**< DS42 */ + CCDigitalChannel_DS43, /**< DS43 */ + CCDigitalChannel_DS44, /**< DS44 */ + CCDigitalChannel_DS45, /**< DS45 */ + CCDigitalChannel_DS46, /**< DS46 */ + CCDigitalChannel_DS47, /**< DS47 */ + CCDigitalChannel_DS48, /**< DS48 */ + CCDigitalChannel_DS49, /**< DS49 */ + CCDigitalChannel_DS50, /**< DS50 */ + CCDigitalChannel_DS51, /**< DS51 */ + CCDigitalChannel_DS52, /**< DS52 */ + CCDigitalChannel_DS53, /**< DS53 */ + CCDigitalChannel_DS54, /**< DS54 */ + CCDigitalChannel_DS55, /**< DS55 */ + CCDigitalChannel_DS56, /**< DS56 */ + CCDigitalChannel_DS57, /**< DS57 */ + CCDigitalChannel_DS58, /**< DS58 */ + CCDigitalChannel_DS59, /**< DS59 */ + CCDigitalChannel_DS60, /**< DS60 */ + CCDigitalChannel_DS61, /**< DS61 */ + CCDigitalChannel_DS62, /**< DS62 */ + CCDigitalChannel_DS63, /**< DS63 */ + CCDigitalChannel_NONE, /**< Used to Set Digital channel to none. (Used to Disable Digital CC.) */ + CCDigitalChannel_EXCLUSIVE_MAXIMUM /**< Exclusive bounds check */ +} CCDigitalChannel_t; + +/** + * @enum CCStatus_t + * @brief Closed Caption Status values as referred to by Applications + */ +typedef enum { + CCStatus_OFF = 0, /**< Closed Caption Disabled */ + + CCStatus_ON, /**< Closed Caption Enabled */ + + CCStatus_ON_MUTE, /**< Closed Caption ON MUTE */ + + CCStatus_EXCLUSIVE_MAXIMUM +} CCStatus_t; + +typedef int mrcc_Error; +namespace subtecConnector +{ + void resetChannel(); + void close(); + + mrcc_Error initHal(void *handle); + mrcc_Error initPacketSender(); + +namespace ccMgrAPI +{ + mrcc_Error ccShow(void); + mrcc_Error ccHide(void); + + mrcc_Error ccSetDigitalChannel(unsigned int channel); + + mrcc_Error ccSetAnalogChannel(unsigned int channel); + + mrcc_Error ccSetAttributes(gsw_CcAttributes * attrib, short type, gsw_CcType ccType); + mrcc_Error ccGetAttributes(gsw_CcAttributes * attrib, gsw_CcType ccType); + + mrcc_Error ccGetCapability(gsw_CcAttribType attribType, gsw_CcType ccType, void **value, unsigned int *size); + + // mrcc_Error ccSetCCState(CCStatus_t ccStatus, int /*not used*/); + // mrcc_Error ccGetCCState(CCStatus_t * pCcStatus); + +} // ccMgrAPI +} // subtecConnector + +#endif //__SUBTEC_CONNECTOR_H__ diff --git a/middleware/drm/ClearKeyDrmSession.cpp b/middleware/drm/ClearKeyDrmSession.cpp new file mode 100644 index 000000000..173c84d98 --- /dev/null +++ b/middleware/drm/ClearKeyDrmSession.cpp @@ -0,0 +1,632 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file ClearKeyDrmSession.cpp +* @brief Source file for ClearKey DRM Session. +*/ + +#include "ClearKeyDrmSession.h" +#include "PlayerUtils.h" +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" + +#include +#include +#include + +#define AES_CTR_KID_LEN 16 +#define AES_CTR_IV_LEN 16 +#define AES_CTR_KEY_LEN 16 + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OPEN_SSL_CONTEXT mOpensslCtx +#else +#define OPEN_SSL_CONTEXT &mOpensslCtx +#endif + + +/** + * @brief ClearKeySession Constructor + */ +ClearKeySession::ClearKeySession() : + DrmSession(CLEAR_KEY_SYSTEM_STRING), + m_sessionID(), + m_eKeyState(KEY_INIT), + decryptMutex(), + m_keyId(NULL), + mOpensslCtx(), + m_keyStr(NULL), + m_keyLen(0), + m_keyIdLen(0) +{ + initDRMSession(); +} + + +/** + * @brief Initialize CK DRM session, Initializes EVP context. + */ +void ClearKeySession::initDRMSession() +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + OPEN_SSL_CONTEXT = EVP_CIPHER_CTX_new(); +#else + EVP_CIPHER_CTX_init(OPEN_SSL_CONTEXT); +#endif + MW_LOG_ERR("ClearKeySession: enter "); +} + +/** + * @brief SetKid for this session. + */ +void ClearKeySession::setKeyId(const char* keyId, int32_t keyIDLen) +{ + if (m_keyId != NULL) + { + free(m_keyId); + } + m_keyId = (unsigned char*) malloc(sizeof(unsigned char) * keyIDLen); + memcpy(m_keyId, keyId, keyIDLen); + m_keyIdLen = keyIDLen; +} + +/** + * @brief Extract the keyId from PSSH data. + * Different procedures are used for PlayReady and WideVine. + * + * @param[in] psshData - Pointer to PSSH data. + * @param[in] dataLength - Length of PSSH data. + * @param[out] len - Gets updated with length of keyId. + * @return Pointer to extracted keyId. + * @note Memory for keyId is dynamically allocated, deallocation + * should be handled at the caller side. + */ +static unsigned char * extractKeyIdFromPssh(const char* psshData, int dataLength, int *len) +{ + unsigned char* key_id = NULL; + /* + 00 00 00 34 70 73 73 68 01 00 00 00 10 77 ef ec + c0 b2 4d 02 ac e3 3c 1e 52 e2 fb 4b 00 00 00 01 + + fe ed f0 0d ee de ad be ef f0 ba ad f0 0d d0 0d + 00 00 00 00 + */ + uint32_t header = 32; //CID:10819 : key_id_count variable removed since its declared but not used + //uint8_t key_id_count = (uint8_t)psshData[header]; // unused + key_id = (unsigned char*)malloc(16 + 1); + memset(key_id, 0, 16 + 1); + strncpy(reinterpret_cast(key_id), psshData + header, 16); + *len = (int)16; + MW_LOG_INFO("ck keyid: %s keyIdlen: %d", key_id, 16); + if (PlayerLogManager::isLogLevelAllowed(mLOGLEVEL_TRACE)) + { + DumpBinaryBlob(key_id, 16); + } + + return key_id; +} + +/** + * @brief Create drm session with given init data + * state will be KEY_INIT on success KEY_ERROR if failed + */ +void ClearKeySession::generateDRMSession(const uint8_t *f_pbInitData, + uint32_t f_cbInitData, std::string &customData) +{ + unsigned char *keyId = NULL; + if(f_pbInitData != NULL && f_cbInitData > 0) + { + int keyIDLen = 0; + keyId = extractKeyIdFromPssh(reinterpret_cast(f_pbInitData),f_cbInitData, &keyIDLen); + if (keyId == NULL) + { + MW_LOG_ERR("ClearKeySession: ERROR: Key Id not found in initdata"); + m_eKeyState = KEY_ERROR; + } + else + { + if(keyIDLen != AES_CTR_KID_LEN) + { + MW_LOG_ERR("ClearKeySession: ERROR: Invalid keyID length %d", keyIDLen); + m_eKeyState = KEY_ERROR; + } + else + { + setKeyId(reinterpret_cast(keyId), keyIDLen); + MW_LOG_ERR("ClearKeySession: Session generated for clearkey"); + } + free(keyId); + } + } + else + { + MW_LOG_ERR("Invalid init data"); + m_eKeyState = KEY_ERROR; + } + return; +} + + +/** + * @brief ClearKeySession Destructor + */ +ClearKeySession::~ClearKeySession() +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if( OPEN_SSL_CONTEXT ) + { + EVP_CIPHER_CTX_free(OPEN_SSL_CONTEXT); + OPEN_SSL_CONTEXT = NULL; + } +#else + EVP_CIPHER_CTX_cleanup(OPEN_SSL_CONTEXT); +#endif + if(m_keyId != NULL) + { + free(m_keyId); + } + if(m_keyStr != NULL) + { + free(m_keyStr); + } + + m_eKeyState = KEY_CLOSED; +} + + +/** + * @brief Generate key request from DRM session + * Caller function should free the returned memory. + */ +DrmData * ClearKeySession::generateKeyRequest(string& destinationURL, uint32_t timeout) +{ + DrmData *licenseChallenge = NULL; + if(NULL == m_keyId || m_keyIdLen <= 0) + { + MW_LOG_ERR("ClearKeySession: Error generating license request "); + } + else + { + char* urlEncodedkeyId = base64_URL_Encode(m_keyId, m_keyIdLen); + if(urlEncodedkeyId) + { + cJSON *licenseRequest = cJSON_CreateObject(); + if(licenseRequest) + { + cJSON *keyIds = cJSON_CreateArray(); + if(keyIds) + { + cJSON_AddItemToArray(keyIds, cJSON_CreateString(urlEncodedkeyId)); + cJSON_AddItemToObject(licenseRequest, "kids", keyIds); + cJSON_AddItemToObject(licenseRequest, "type",cJSON_CreateString("temporary")); + char* requestBody = cJSON_PrintUnformatted(licenseRequest); + if(requestBody) + { + MW_LOG_INFO("Generated license request : %s", requestBody); + licenseChallenge = new DrmData( requestBody, strlen(requestBody)); //CID:154682 - overrun + m_eKeyState = KEY_PENDING; + cJSON_free(requestBody); + } + } + cJSON_Delete(licenseRequest); + } + free(urlEncodedkeyId); + } + } + return licenseChallenge; +} + + +/** + * @brief Updates the received key to DRM session + */ +int ClearKeySession::processDRMKey(DrmData* key, uint32_t timeout) +{ + int ret = 0; + if (!key) + { + MW_LOG_ERR("ClearKeySession: Cannot Process Null Key "); + return ret; + } + + std::string keyDataStr = key->getData(); + MW_LOG_INFO("ClearKeySession: Processing license response %s", keyDataStr.c_str()); + if (m_eKeyState == KEY_PENDING) + { + cJSON *licenseResponse = cJSON_Parse(keyDataStr.c_str()); + if (licenseResponse) + { + if (cJSON_HasObjectItem(licenseResponse, "keys")) + { + cJSON * keyEntry = NULL; + cJSON *keyJsonObj = NULL; + cJSON *keyIdJsonObj = NULL; + + cJSON *keysArray = cJSON_GetObjectItem(licenseResponse, "keys"); + if(keysArray) + { + keyEntry = keysArray->child; + if(keyEntry) + { + keyJsonObj = cJSON_GetObjectItem(keyEntry, "k"); + keyIdJsonObj = cJSON_GetObjectItem(keyEntry, "kid"); + } + } + + if (keyJsonObj && keyIdJsonObj) + { + char *keyJsonStr = cJSON_GetStringValue(keyJsonObj); + char *keyIdStr = cJSON_GetStringValue(keyIdJsonObj); + size_t resKeyIdLen = 0; + size_t resKeyLen = 0; + unsigned char * resKeyId = base64_URL_Decode(keyIdStr, &resKeyIdLen, strlen(keyIdStr)); + if (resKeyIdLen == m_keyIdLen && 0 == memcmp(m_keyId, resKeyId, m_keyIdLen)) + { + if (m_keyStr != NULL) + { + free (m_keyStr); + } + m_keyStr = base64_URL_Decode(keyJsonStr, &resKeyLen, strlen(keyJsonStr)); + if (resKeyLen == AES_CTR_KEY_LEN) + { + m_keyLen = resKeyLen; + m_eKeyState = KEY_READY; + MW_LOG_INFO("ClearKeySession: Got key from license response keyLength %zu", m_keyLen); + ret = 1; + } + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed parse Key from response"); + if (m_keyStr) + { + free (m_keyStr); + m_keyStr = NULL; + } + m_eKeyState = KEY_ERROR; + } + } + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed parse KeyID/invalid keyID, from response"); + m_eKeyState = KEY_ERROR; + } + if (resKeyId) + { + free(resKeyId); + } + } + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed parse Key info, from response"); + m_eKeyState = KEY_ERROR; + } + } + cJSON_Delete(licenseResponse); + } + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed parse response JSON"); + m_eKeyState = KEY_ERROR; + } + } + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Invalid DRM state : DRMState %d", m_eKeyState); + } + return ret; +} + +/** + * @brief Function to decrypt stream. + */ +int ClearKeySession::decrypt(GstBuffer* keyIDBuffer, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, + GstBuffer* subSamplesBuffer, GstCaps* caps) +{ +#if 0 + GstBuffer* KeyIdBuffer = (GstBuffer*)keyIDBufferIn; + GstBuffer* ivBuffer = (GstBuffer*)ivBufferIn; + GstBuffer* buffer = (GstBuffer*)bufferIn; + GstCaps* caps = (GstCaps*)capsIn; +#endif + int retVal = 1; + uint8_t *pbData = NULL; + uint32_t cbData = 0; + uint16_t nBytesClear = 0; + uint32_t nBytesEncrypted = 0; + + GstMapInfo ivMap; + GstMapInfo subsampleMap = GST_MAP_INFO_INIT; + GstMapInfo bufferMap; + GstByteReader* reader = NULL; + + bool ivMapped = false; + bool subSampleMapped = false; + bool bufferMapped = false; + + if(!(ivBuffer && buffer && (subSampleCount == 0 || subSamplesBuffer))) + { + MW_LOG_ERR( + "ClearKeySession: ERROR : Ivalid buffer ivBuffer(%p), buffer(%p), subSamplesBuffer(%p) ", + ivBuffer, buffer, subSamplesBuffer); + } + else + { + bufferMapped = gst_buffer_map(buffer, &bufferMap,static_cast(GST_MAP_READWRITE)); + if (!bufferMapped) + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to map buffer"); + } + + ivMapped = gst_buffer_map(ivBuffer, &ivMap, GST_MAP_READ); + if (!ivMapped) + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to map ivBuffer"); + } + + if(subSampleCount > 0) + { + subSampleMapped = gst_buffer_map(subSamplesBuffer, &subsampleMap, GST_MAP_READ); + if (!subSampleMapped) + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to map subSamplesBuffer"); + } + } + } + + if(bufferMapped && ivMapped && (subSampleCount ==0 || subSampleMapped)) + { + reader = gst_byte_reader_new(subsampleMap.data, (guint)subsampleMap.size); + if(reader) + { + // collect all the encrypted bytes into one contiguous buffer + // we need to call decrypt once for all encrypted bytes. + if (subSampleCount > 0) + { + pbData = (uint8_t *) malloc(sizeof(uint8_t) * bufferMap.size); + uint8_t *pbCurrTarget = (uint8_t *) pbData; + uint32_t iCurrSource = 0; + + for (int i = 0; i < subSampleCount; i++) + { + if (!gst_byte_reader_get_uint16_be(reader, &nBytesClear) + || !gst_byte_reader_get_uint32_be(reader, &nBytesEncrypted)) + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to read from subsamples reader"); + cbData = 0; + break; + } + // Skip the clear byte range from source buffer. + iCurrSource += nBytesClear; + + // Copy cipher bytes from f_pbData to target buffer. + memcpy(pbCurrTarget, (uint8_t*) bufferMap.data + iCurrSource, nBytesEncrypted); + + // Adjust current pointer of target buffer. + pbCurrTarget += nBytesEncrypted; + + // Adjust current offset of source buffer. + iCurrSource += nBytesEncrypted; + cbData += nBytesEncrypted; + } + } + else + { + pbData = bufferMap.data; + cbData = (uint32_t)bufferMap.size; + } + + if(cbData != 0) + { + retVal = decrypt(static_cast(ivMap.data), static_cast(ivMap.size), + pbData, cbData, NULL); + } + + if(retVal == 0) + { + if(subSampleCount > 0) + { + // If subsample mapping is used, copy decrypted bytes back + // to the original buffer. + gst_byte_reader_set_pos(reader, 0); + uint8_t *pbCurrTarget = bufferMap.data; + uint32_t iCurrSource = 0; + + for (int i = 0; i < subSampleCount; i++) + { + if (!gst_byte_reader_get_uint16_be(reader, &nBytesClear) + || !gst_byte_reader_get_uint32_be(reader, &nBytesEncrypted)) + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to read from subsamples reader"); + retVal = 1; + break; + } + // Skip the clear byte range from target buffer. + pbCurrTarget += nBytesClear; + memcpy(pbCurrTarget, pbData + iCurrSource, nBytesEncrypted); + + // Adjust current pointer of target buffer. + pbCurrTarget += nBytesEncrypted; + + // Adjust current offset of source buffer. + iCurrSource += nBytesEncrypted; + } + }//SubSample end if + } //retVal end if + + gst_byte_reader_free(reader); + }//reader end if + else + { + MW_LOG_ERR("ClearKeySession: ERROR : Failed to allocate subsample reader"); + } + } + + if(subSampleCount > 0 && pbData) + { + free(pbData); + } + if(bufferMapped) + { + gst_buffer_unmap(buffer, &bufferMap); + } + if(ivMapped) + { + gst_buffer_unmap(ivBuffer, &ivMap); + } + if(subSampleMapped) + { + gst_buffer_unmap(subSamplesBuffer, &subsampleMap); + } + + return retVal; +} + +/** + * @brief Function to decrypt stream buffer. + */ +int ClearKeySession::decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, + const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData=NULL) +{ + int status = 1; + std::lock_guard guard(decryptMutex); + if (m_eKeyState == KEY_READY) + { + uint8_t *decryptedDataBuf = (uint8_t *)malloc(payloadDataSize); + uint32_t decryptedDataLen = 0; + uint8_t *ivBuff = NULL; + if(decryptedDataBuf) + { + memset(decryptedDataBuf, 0, payloadDataSize); + if(f_cbIV == 8)//8 byte IV need to pad with 0 before decrypt + { + ivBuff = (uint8_t *)malloc(sizeof(uint8_t) * AES_CTR_IV_LEN); + if(ivBuff != NULL) + { + memset(ivBuff + 8, 0, 8); + memcpy(ivBuff, f_pbIV, 8); + } + } + else if(f_cbIV == AES_CTR_IV_LEN) + { + ivBuff = (uint8_t *)malloc(sizeof(uint8_t) * AES_CTR_IV_LEN); + memcpy(ivBuff, f_pbIV, AES_CTR_IV_LEN); + } + else + { + MW_LOG_TRACE("ClearKeySession: invalid IV size %u", f_cbIV); + } + } + + if (decryptedDataBuf && ivBuff) + { + uint32_t decLen = payloadDataSize; + + if(!EVP_DecryptInit_ex(OPEN_SSL_CONTEXT, EVP_aes_128_ctr(), NULL, m_keyStr, ivBuff)) + { + MW_LOG_TRACE( "ClearKeySession: EVP_DecryptInit_ex failed"); + } + else + { + if (!EVP_DecryptUpdate(OPEN_SSL_CONTEXT,(unsigned char*) decryptedDataBuf, (int*) &decLen, (const unsigned char*) payloadData, + payloadDataSize)) + { + MW_LOG_TRACE("ClearKeySession: EVP_DecryptUpdate failed"); + } + else + { + decryptedDataLen = decLen; + decLen = 0; + MW_LOG_TRACE("ClearKeySession: EVP_DecryptUpdate success decryptedDataLen = %d payload Data length = %d", (int) decryptedDataLen, (int)payloadDataSize); + if (!EVP_DecryptFinal_ex(OPEN_SSL_CONTEXT, (unsigned char*) (decryptedDataBuf + decryptedDataLen), (int*) &decLen)) + { + MW_LOG_TRACE("ClearKeySession: EVP_DecryptFinal_ex failed mDrmState = %d", (int) m_eKeyState); + } + else + { + decryptedDataLen += decLen; + MW_LOG_TRACE("ClearKeySession: decrypt success"); + status = 0; + } + } + } + + memcpy((void *)payloadData, decryptedDataBuf, payloadDataSize); + } + if(ivBuff) + { + free(ivBuff); //CID:108053 - Resource leak + ivBuff = NULL; + } + if(decryptedDataBuf) + { + free(decryptedDataBuf); + decryptedDataBuf = NULL; + } + } + else + { + MW_LOG_ERR( "ClearKeySession: key not ready! mDrmState = %d", m_eKeyState); + } + return status; +} + +/** + * @brief Get the current state of DRM Session. + */ +KeyState ClearKeySession::getState() +{ + return m_eKeyState; +} + +/** + * @brief Clear the current session context + * So that new init data can be bound. + */ +void ClearKeySession:: clearDecryptContext() +{ + if(m_keyId != NULL) + { + free(m_keyId); + m_keyId = NULL; + m_keyIdLen = 0; + } + if(m_keyStr != NULL) + { + free(m_keyStr); + m_keyStr = NULL; + m_keyLen = 0; + } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if( OPEN_SSL_CONTEXT ) + { + EVP_CIPHER_CTX_free(OPEN_SSL_CONTEXT); + OPEN_SSL_CONTEXT = NULL; + } +#else + EVP_CIPHER_CTX_cleanup(OPEN_SSL_CONTEXT); +#endif + m_eKeyState = KEY_INIT; +} + + diff --git a/middleware/drm/ClearKeyDrmSession.h b/middleware/drm/ClearKeyDrmSession.h new file mode 100644 index 000000000..bc6e0f45b --- /dev/null +++ b/middleware/drm/ClearKeyDrmSession.h @@ -0,0 +1,154 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file ClearKeyDrmSession.h + * @brief Header file for ClearKeySession + */ + +#ifndef ClearKeySession_h +#define ClearKeySession_h + +#include "DrmSession.h" +#include "openssl/evp.h" + +#include +#include +#include + +using namespace std; + +/** + * @class ClearKeySession + * @brief Open CDM DRM session + */ +class ClearKeySession : public DrmSession +{ + +private: + std::mutex decryptMutex; + + KeyState m_eKeyState; + string m_sessionID; + unsigned char* m_keyStr; + size_t m_keyLen; + unsigned char* m_keyId; + size_t m_keyIdLen; + /** + * @fn initDRMSession + */ + void initDRMSession(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_CIPHER_CTX *mOpensslCtx; +#else + EVP_CIPHER_CTX mOpensslCtx; +#endif +public: + + /** + * @fn ClearKeySession + */ + ClearKeySession(); + + /** + * @fn ~ClearKeySession + */ + ~ClearKeySession(); + /** + * @brief Copy constructor disabled + * + */ + ClearKeySession(const ClearKeySession&) = delete; + /** + * @brief assignment operator disabled + * + */ + ClearKeySession& operator=(const ClearKeySession&) = delete; + + /** + * @fn generateDRMSession + * @param f_pbInitData pointer to initdata + * @param f_cbInitData init data size + */ + void generateDRMSession(const uint8_t *f_pbInitData, + uint32_t f_cbInitData, std::string &customData); + + /** + * @fn GenerateKeyRequest + * @param destinationURL : gets updated with license server url + * @param timeout: max timeout untill which to wait for cdm key generation. + * @retval Pointer to DrmData containing license request, NULL if failure. + */ + DrmData * generateKeyRequest(string& destinationURL, uint32_t timeout); + + /** + * @fn DRMProcessKey + * @param key : License key from license server. + * @param timeout: max timeout untill which to wait for cdm processing. + * @retval DRM_SUCCESS(1) if no errors encountered + */ + int processDRMKey(DrmData* key, uint32_t timeout); + + /** + * @fn setKeyId + * @param keyId Clear key ID + * @param keyIDLen key length + */ + void setKeyId(const char* keyId, int32_t keyIDLen); + + /** + * @fn decrypt + * @param f_pbIV : Initialization vector. + * @param f_cbIV : Initialization vector length. + * @param payloadData : Data to decrypt. + * @param payloadDataSize : Size of data. + * @param ppOpaqueData : pointer to opaque buffer in case of SVP. + * @retval Returns 0 on success. + */ + int decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, + const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData); + + //If OCDM_ADAPTOR is in use below decrypt function will be invoked from plugin + /** + * @fn decrypt + * @param keyIDBuffer : keyID Buffer. + * @param ivBuffer : Initialization vector buffer. + * @param buffer : Data to decrypt. + * @param subSampleCount : subSampleCount in buffer + * @param subSamplesBuffer : sub Samples Buffer. + * @retval Returns 0 on success. + */ + + int decrypt(GstBuffer* keyIDBuffer, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, + GstBuffer* subSamplesBuffer, GstCaps* caps); + + /** + * @fn getState + * @retval KeyState + */ + KeyState getState(); + + /** + * @fn clearDecryptContext + */ + void clearDecryptContext(); +}; + +#endif + diff --git a/middleware/drm/DrmCallbacks.h b/middleware/drm/DrmCallbacks.h new file mode 100644 index 000000000..79bf5fa0d --- /dev/null +++ b/middleware/drm/DrmCallbacks.h @@ -0,0 +1,45 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef DRMCALLBACKS_H +#define DRMCALLBACKS_H + +/** + * @file DrmCallbacks.h + * @brief Call back handler for Player + */ + +#include + +/** + * @class DrmCallbacks + * @brief DRM callback interface + */ +class DrmCallbacks +{ +public: + virtual void Individualization(const std::string& payload) = 0; + virtual void LicenseRenewal(DrmHelperPtr drmHelper, void* userData) = 0; + virtual ~DrmCallbacks() {}; +}; + + + +#endif /* DRMCALLBACKS_H */ + diff --git a/middleware/drm/DrmConstants.h b/middleware/drm/DrmConstants.h new file mode 100755 index 000000000..704f70c02 --- /dev/null +++ b/middleware/drm/DrmConstants.h @@ -0,0 +1,38 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmConstants.h + * @brief Constants in DRM + */ + +#ifndef __DRM_CONSTANTS_H__ +#define __DRM_CONSTANTS_H__ + +//DRM UUIDs +#define PLAYREADY_UUID "9a04f079-9840-4286-ab92-e65be0885f95" +#define WIDEVINE_UUID "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" +#define CLEARKEY_UUID "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" +#define CONSEC_AGNOSTIC_UUID "afbcb50e-bf74-3d13-be8f-13930c783962" +#define VERIMATRIX_UUID "9a27dd82-fde2-4725-8cbc-4234aa06ec09" + +#define DRM_MAX_PIPE_DATA_SIZE 1024 /**< Max size of data send across pipe */ + +#endif /* __DRM_CONSTANTS_H__ */ + diff --git a/middleware/drm/DrmData.h b/middleware/drm/DrmData.h new file mode 100644 index 000000000..976060219 --- /dev/null +++ b/middleware/drm/DrmData.h @@ -0,0 +1,98 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmData.h + * @brief File holds DRM License data + */ + +#ifndef DRMDATA_H +#define DRMDATA_H + +/** + * @class DrmData + * @brief To hold DRM key, license request etc. + */ +class DrmData{ + +private: + std::string data; /**< License Data */ +public: + /** + * @fn DrmData + */ + DrmData(); + /** + * @fn DrmData + * + * @param[in] data - pointer to data to be copied. + * @param[in] dataLength - length of data + */ + DrmData(const char *dataPtr, size_t dataLength); + + /** + * @brief Copy constructor disabled + * + */ + DrmData(const DrmData&) = delete; + /** + * @brief assignment operator disabled + * + */ + DrmData& operator=(const DrmData&) = delete; + /** + * @fn ~DrmData + */ + ~DrmData(); + + /** + * @fn getData + * + * @return Returns pointer to data. + */ + const std::string &getData(); + + /** + * @fn getDataLength + * + * @return Returns dataLength. + */ + size_t getDataLength(); + + /** + * @fn setData + * + * @param[in] data - Pointer to data to be set. + * @param[in] dataLength - length of data. + * @return void. + */ + void setData(const char *dataPtr, size_t dataLength); + /** + * @fn addData + * + * @param[in] data - Pointer to data to be appended. + * @param[in] dataLength - length of data. + * @return void. + */ + void addData(const char *dataPtr, size_t dataLength); +}; + + +#endif /* DRMDATA_H */ + diff --git a/middleware/drm/DrmInfo.h b/middleware/drm/DrmInfo.h new file mode 100755 index 000000000..5c36f6795 --- /dev/null +++ b/middleware/drm/DrmInfo.h @@ -0,0 +1,95 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmInfo.h + * @brief DRM license information for Player + */ + +#ifndef DRMINFO_H +#define DRMINFO_H + +#include +#include +#include "DrmMediaFormat.h" + +#define DRM_IV_LEN 16 + +/** + * @enum DrmMethod + * @brief DRM method + */ +typedef enum +{ + eMETHOD_NONE, + eMETHOD_AES_128, /**< encrypted using Advanced Encryption Standard 128-bit key and PKCS7 padding */ +} DrmMethod; + +/** + * @struct DrmInfo + * @brief DRM information required to decrypt + */ +struct DrmInfo +{ + DrmInfo() : method(eMETHOD_NONE), mediaFormat(eMEDIAFORMAT_HLS), useFirst16BytesAsIV(false), iv(), + masterManifestURL(), manifestURL(), keyURI(), keyFormat(), systemUUID(), initData(), bPropagateUriParams(true), + bUseMediaSequenceIV(true), bDecryptClearSamplesRequired(true) + {}; + ~DrmInfo() {}; + // copy constructor + DrmInfo(const DrmInfo& other) : method(other.method), mediaFormat(other.mediaFormat), + useFirst16BytesAsIV(other.useFirst16BytesAsIV), masterManifestURL(other.masterManifestURL), + manifestURL(other.manifestURL), keyURI(other.keyURI), keyFormat(other.keyFormat), + systemUUID(other.systemUUID), initData(other.initData), bPropagateUriParams(other.bPropagateUriParams), + bUseMediaSequenceIV(other.bUseMediaSequenceIV), bDecryptClearSamplesRequired(other.bDecryptClearSamplesRequired) + { + memcpy( iv, other.iv, DRM_IV_LEN ); + } + DrmInfo& operator=(const DrmInfo& other) + { + method = other.method; + mediaFormat = other.mediaFormat; + useFirst16BytesAsIV = other.useFirst16BytesAsIV; + bDecryptClearSamplesRequired = other.bDecryptClearSamplesRequired; + masterManifestURL = other.masterManifestURL; + manifestURL = other.manifestURL; + keyURI = other.keyURI; + keyFormat = other.keyFormat; + systemUUID = other.systemUUID; + initData = other.initData; + // copying same iv, releases memory allocated after deleting any of these objects. + memcpy( iv, other.iv, DRM_IV_LEN ); + return *this; + } + DrmMethod method; /**< Encryption method */ + MediaFormat mediaFormat; /**< Format of the media being played e.g. DASH, HLS*/ + bool useFirst16BytesAsIV; + bool bPropagateUriParams; /**< Propagate Manifest uri params in DRM */ + bool bUseMediaSequenceIV; /**< To create IV using media sequence number */ + bool bDecryptClearSamplesRequired; /**< Process call to decrypt clear samples */ + unsigned char iv[DRM_IV_LEN]; /**< [16] Initialisation vector */ + std::string masterManifestURL; /**< URL of the master manifest */ + std::string manifestURL; /**< URL of playlist the DRM info was taken from. May be the same as the masterManifestURL */ + std::string keyURI; /**< URI to fetch key. May be relative to the manifest URL */ + std::string keyFormat; /**< Format of key */ + std::string systemUUID; /**< UUID of the DRM */ + std::string initData; /**< Base64 init data string from the main manifest URI */ +}; + +#endif /* DRMINFO_H */ diff --git a/middleware/drm/DrmJsonObject.cpp b/middleware/drm/DrmJsonObject.cpp new file mode 100755 index 000000000..b7ce13012 --- /dev/null +++ b/middleware/drm/DrmJsonObject.cpp @@ -0,0 +1,660 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmJsonObject.cpp + * @brief File to handle Json format object + */ + +#include + +#include "DrmJsonObject.h" +#include "PlayerUtils.h" +#include "_base64.h" + + + +DrmJsonObject::DrmJsonObject() : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_CreateObject(); +} + +DrmJsonObject::DrmJsonObject(const std::string& jsonStr) : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_Parse(jsonStr.c_str()); + + if (!mJsonObj) + { + throw DrmJsonParseException(); + } +} + +DrmJsonObject::DrmJsonObject(const char* jsonStr) : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_Parse(jsonStr); + + if (!mJsonObj) + { + throw DrmJsonParseException(); + } +} + +DrmJsonObject::~DrmJsonObject() +{ + if (!mParent) + { + cJSON_Delete(mJsonObj); + } +} + +/** + * @brief Add a string value + */ +bool DrmJsonObject::add(const std::string& name, const std::string& value, const ENCODING encoding) +{ + bool res = false; + + if (encoding == ENCODING_STRING) + { + res = add(name, cJSON_CreateString(value.c_str())); + } + else + { + res = add(name, std::vector(value.begin(), value.end()), encoding); + } + + return res; +} + +/** + * @brief Add a string value + */ +bool DrmJsonObject::add(const std::string& name, const char *value, const ENCODING encoding) +{ + return add(name, std::string(value), encoding); +} + +/** + * @brief Add a vector of string values as a JSON array + */ +bool DrmJsonObject::add(const std::string& name, const std::vector& values) +{ + bool res = false; + cJSON* arr = cJSON_CreateArray(); + + if (!arr) + { + // Failed to create array + } + else + { + res = true; + for (auto& value : values) + { + cJSON* string_item = cJSON_CreateString(value.c_str()); + + if (!string_item) + { + // Create string failed + res = false; + } + else if (!cJSON_AddItemToArray(arr, string_item)) + { + cJSON_Delete(string_item); + res = false; + } + else + { + // String added successfully + } + + if (!res) + { + // Adding an item failed + break; + } + } + + if (!res) + { + // Something failed + } + else if (!add(name, arr)) + { + // Adding the array to the json object failed + res = false; + } + else + { + // Array added successfully + } + + if (!res) + { + // Something failed, so delete whole array + cJSON_Delete(arr); + } + } + + return res; +} + +/** + * @brief Add the provided bytes after encoding in the specified encoding + */ +bool DrmJsonObject::add(const std::string& name, const std::vector& values, const ENCODING encoding) +{ + bool res = false; + + switch (encoding) + { + case ENCODING_STRING: + { + std::string strValue(values.begin(), values.end()); + res = add(name, cJSON_CreateString(strValue.c_str())); + } + break; + + case ENCODING_BASE64: + { + const char *encodedResponse = base64_Encode( reinterpret_cast(values.data()), values.size()); + if (encodedResponse != NULL) + { + res = add(name, cJSON_CreateString(encodedResponse)); + free((void*)encodedResponse); + } + } + break; + + case ENCODING_BASE64_URL: + { + const char *encodedResponse = base64_URL_Encode( reinterpret_cast(values.data()), values.size()); + if (encodedResponse != NULL) + { + res = add(name, cJSON_CreateString(encodedResponse)); + free((void*)encodedResponse); + } + } + break; + + default: + /* Unsupported encoding format */ + break; + } + + return res; +} + +/** + * @brief Add a #DrmJsonObject value + */ +bool DrmJsonObject::add(const std::string& name, DrmJsonObject& value) +{ + bool res = false; + + if (cJSON_AddItemToObject(mJsonObj, name.c_str(), value.mJsonObj)) + { + value.mParent = this; + res = true; + } + + return res; +} + +/** + * @brief Add a vector of #DrmJsonObject values as a JSON array + */ +bool DrmJsonObject::add(const std::string& name, std::vector& values) +{ + bool res = false; + cJSON *arr = cJSON_CreateArray(); + + if (!arr) + { + // Failed to create array + } + else + { + res = true; + for (auto& obj : values) + { + if (!cJSON_AddItemToArray(arr, obj->mJsonObj)) + { + // Failed to add item to array + res = false; + break; + } + else + { + obj->mParent = this; + } + } + + if (!res) + { + // Something failed + } + else if (!add(name, arr)) + { + // Adding the array to the json object failed + res = false; + } + else + { + // Array added successfully + } + + if (!res) + { + // Something failed, so delete whole array + cJSON_Delete(arr); + } + } + + return res; +} + +/** + * @brief Add a cJSON value + */ +bool DrmJsonObject::add(const std::string& name, cJSON *value) +{ + bool res = false; + + if (NULL == value) + { + // NULL parameter passed + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), value)) + { + // cJSON_AddItemToObject failed + } + else + { + res = true; + } + + return res; +} + + +/** + * @brief Add a bool value + */ +bool DrmJsonObject::add(const std::string& name, bool value) +{ + bool res = false; + cJSON *bool_item = cJSON_CreateBool(value); + + if (!bool_item) + { + // Failed to create a bool + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), bool_item)) + { + cJSON_Delete(bool_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add an int value + */ +bool DrmJsonObject::add(const std::string& name, int value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add a double value + */ +bool DrmJsonObject::add(const std::string& name, double value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add a long value + */ +bool DrmJsonObject::add(const std::string& name, long value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Set cJSON value + */ +bool DrmJsonObject::set(DrmJsonObject *parent, cJSON *object) +{ + this->mParent = parent; + this->mJsonObj = object; + /**< return true always to match the template */ + return true; +} + +/** + * @brief Get the DrmJson object from json data within the Json data + */ +bool DrmJsonObject::get(const std::string& name, DrmJsonObject &value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj) + { + retValue = value.set(this, strObj); + } + return retValue; +} + +/** + * @brief Get an array of objects from JSON data + * @param name name for the property + * @param[out] value JSON object array + * @return true if successfully retrieved, false otherwise + */ +bool DrmJsonObject::get(const std::string& name, std::vector &values) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + cJSON *object = NULL; + bool retVal = true; + int idx = 0; + + values.clear(); + cJSON_ArrayForEach(object, strObj) + { + values.emplace_back(); + values[idx++].set(this, object); + } + return retVal; +} + +/** + * @brief Get a string value + */ +bool DrmJsonObject::get(const std::string& name, std::string& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + + if (strObj) + { + char *strValue = cJSON_GetStringValue(strObj); + if (strValue) + { + value = strValue; + return true; + } + } + return false; +} + +/** + * @brief Get a int value from a JSON data + */ +bool DrmJsonObject::get(const std::string& name, int& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj) + { + // TODO: replace with cJSON_GetNumberValue(strObj); + value = (int)strObj->valuedouble; + retValue = true; + } + return retValue; +} + +/** + * @brief Get a double value from JSON data + * @param name name for the property + * @param[out] value double value + * @return true if successfully retrieved, false otherwise + */ +bool DrmJsonObject::get(const std::string& name, double& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj && cJSON_IsNumber(strObj)) + { + value = strObj->valuedouble; + retValue = true; + } + return retValue; +} + +/** + * @brief Get a string value + */ +bool DrmJsonObject::get(const std::string& name, std::vector& values) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + cJSON *object = NULL; + bool retVal = false; + cJSON_ArrayForEach(object, strObj) + { + char *strValue = cJSON_GetStringValue(object); + if (strValue) + { + values.push_back(std::string(strValue)); + retVal = true; + } + } + return retVal; +} + +/** + * @brief Get a string value as a vector of bytes + */ +bool DrmJsonObject::get(const std::string& name, std::vector& values, const ENCODING encoding) +{ + bool res = false; + std::string strValue; + + if (get(name, strValue)) + { + values.clear(); + + switch (encoding) + { + case ENCODING_STRING: + { + values.insert(values.begin(), strValue.begin(), strValue.end()); + } + break; + + case ENCODING_BASE64: + { + size_t decodedSize = 0; + const unsigned char *decodedResponse = base64_Decode(strValue.c_str(), &decodedSize, strValue.length()); + if (decodedResponse != NULL) + { + values.insert(values.begin(), decodedResponse, decodedResponse + decodedSize); + res = true; + free((void *)decodedResponse); + } + } + break; + + case ENCODING_BASE64_URL: + { + size_t decodedSize = 0; + const unsigned char *decodedResponse = base64_URL_Decode(strValue.c_str(), &decodedSize, strValue.length()); + if (decodedResponse != NULL) + { + values.insert(values.begin(), decodedResponse, decodedResponse + decodedSize); + res = true; + free((void *)decodedResponse); + } + } + break; + + default: + /* Unsupported encoding format */ + break; + } + } + return res; +} + +/** + * @brief Print the constructed JSON to a string + */ +std::string DrmJsonObject::print() +{ + char *jsonString = cJSON_Print(mJsonObj); + if (NULL != jsonString) + { + std::string retStr(jsonString); + cJSON_free(jsonString); + return retStr; + } + return ""; +} + +/** + * @brief Print the constructed JSON to a string + */ +std::string DrmJsonObject::print_UnFormatted() +{ + char *jsonString = cJSON_PrintUnformatted(mJsonObj); + if (NULL != jsonString) + { + std::string retStr(jsonString); + cJSON_free(jsonString); + return retStr; + } + return ""; +} + +/** + * @brief Print the constructed JSON into the provided vector + */ +void DrmJsonObject::print(std::vector& data) +{ + std::string jsonOutputStr = print(); + (void)data.insert(data.begin(), jsonOutputStr.begin(), jsonOutputStr.end()); +} + +/** + * @brief Check whether the value is Array or not + */ +bool DrmJsonObject::isArray(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsArray(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is String or not + */ +bool DrmJsonObject::isString(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsString(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is Number or not + */ +bool DrmJsonObject::isNumber(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsNumber(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is Object or not + */ +bool DrmJsonObject::isObject(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsObject(strObj); + } + return retVal; +} diff --git a/middleware/drm/DrmJsonObject.h b/middleware/drm/DrmJsonObject.h new file mode 100755 index 000000000..9894d71d4 --- /dev/null +++ b/middleware/drm/DrmJsonObject.h @@ -0,0 +1,279 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +#ifndef _DRM_JSON_OBJECT_H +#define _DRM_JSON_OBJECT_H + +/** + * @file DrmJsonObject.h + * @brief File to handle Json format + */ + + +#include +#include +#include // for uint8_t + +#include + +/** + * @class DrmJsonObject + * @brief Utility class to construct a JSON string + */ +class DrmJsonObject { +public: + DrmJsonObject(); + DrmJsonObject(const std::string& jsonStr); + DrmJsonObject(const char* jsonStr); + ~DrmJsonObject(); + DrmJsonObject(const DrmJsonObject&) = delete; + DrmJsonObject& operator=(const DrmJsonObject&) = delete; + DrmJsonObject(DrmJsonObject &&) = default; + + enum ENCODING + { + ENCODING_STRING, /**< Bytes encoded as a string as-is */ + ENCODING_BASE64, /**< Bytes base64 encoded to a string */ + ENCODING_BASE64_URL /**< Bytes base64url encoded to a string */ + }; + + /** + * @fn add + * + * @param name name for the value + * @param value string value to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::string& value, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the value + * @param value string value to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const char *value, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the array + * @param values vector of strings to add as an array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::vector& values); + + /** + * @fn add + * + * @param name name for the value + * @param values vector of bytes to add in the specified encoding + * @param encoding how to encode the byte array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::vector& values, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the array + * @param values vector of #DrmJsonObject to add as an array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, std::vector& values); + + /** + * @fn get + * + * @param name name for the array + * @param[out] values String Array + * @return true if successfully added, false otherwise + */ + bool get(const std::string& name, std::vector& values); + + /** + * @fn get + * + * @param name name for the property + * @param[out] values int value + * @return true if successfully added, false otherwise + */ + bool get(const std::string& name, int& value); + + /** + * @brief Get a double value from JSON data + * @param name name for the property + * @param[out] value double value + * @return true if successfully retrieved, false otherwise + */ + bool get(const std::string& name, double& value); + + /** + * @fn get + * + * @param name name of the property to retrieve + * @param value string to populate with the retrieved value + * @return true if successfully retrieved value, false otherwise + */ + bool get(const std::string& name, std::string& value); + + /** + * @fn fn + * + * @param name name of the property to retrieve + * @param values vector to populate with the retrieved value + * @param encoding the encoding of the string, used to determine how to decode the content into the vector + * @return true if successfully retrieved and decoded value, false otherwise + */ + bool get(const std::string& name, std::vector& values, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn get + * + * @param name Name of the internal json data + * @param value[out] reference Object which return as json object inside json data. + * @return true if successfully retrieved and decoded value, false otherwise + */ + bool get(const std::string& name, DrmJsonObject &value); + + /** + * @brief Get an array of objects from JSON data + * @param name name for the property + * @param[out] value JSON object array + * @return true if successfully retrieved, false otherwise + */ + bool get(const std::string& name, std::vector &values); + + /** + * @fn print + * + * @return JSON string + */ + std::string print(); + + + /** + * @fn print_UnFormatted + * + * @return JSON string + */ + std::string print_UnFormatted(); + + /** + * @fn print + * + * @param[out] data - vector to output printed JSON to + * @return void. + */ + void print(std::vector& data); + /** + * @fn add + * + * @param name name for the value + * @param value bool to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, bool value); + /** + * @fn add + * + * @param name name for the value + * @param value int to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, int value); + + /** + * @fn add + * + * @param name name for the value + * @param value double to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, double value); + + /** + * @fn add + * + * @param name name for the value + * @param value long to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, long value); + + bool add(const std::string& name, DrmJsonObject& value); + + /** + * @fn add + * + * @param name name for the value + * @param value long to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, cJSON *value); + + /** + * @fn isArray + * + * @return true if it is Array or false + */ + bool isArray(const std::string& name); + + /** + * @fn isString + * + * @return true if it is String or false + */ + bool isString(const std::string& name); + + /** + * @fn isNumber + * + * @return true if it is Number or false + */ + bool isNumber(const std::string& name); + + /** + * @fn isObject + * + * @return true if it is Object or false + */ + bool isObject(const std::string& name); + +private: + bool set(DrmJsonObject *parent, cJSON *object); + bool add(const std::string& name); + cJSON *mJsonObj; + DrmJsonObject *mParent; +}; + +/** + * @class DrmJsonParseException + * @brief Handles the exception for JSON parser + */ +class DrmJsonParseException : public std::exception +{ +public: + virtual const char* what() const throw() override + { + return "Failed to parse JSON string"; + } +}; + +#endif //_DRM_JSON_OBJECT_H diff --git a/middleware/drm/DrmMediaFormat.h b/middleware/drm/DrmMediaFormat.h new file mode 100644 index 000000000..e9a41595c --- /dev/null +++ b/middleware/drm/DrmMediaFormat.h @@ -0,0 +1,47 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef DRMMEDIAFORMAT_H +#define DRMMEDIAFORMAT_H + +/** + * @file DrmMediaFormat.h + * @brief Types of Media + */ + +/** + * @enum MediaFormat + * @brief Media format types + */ +typedef enum +{ + eMEDIAFORMAT_HLS, /**< HLS Media */ + eMEDIAFORMAT_DASH, /**< Dash Media */ + eMEDIAFORMAT_PROGRESSIVE, /**< Progressive Media */ + eMEDIAFORMAT_HLS_MP4, /**< HLS mp4 Format */ + eMEDIAFORMAT_OTA, /**< OTA Media */ + eMEDIAFORMAT_HDMI, /**< HDMI Format */ + eMEDIAFORMAT_COMPOSITE, /**< Composite Media */ + eMEDIAFORMAT_SMOOTHSTREAMINGMEDIA, /**< Smooth streaming Media */ + eMEDIAFORMAT_RMF, /**< RMF media */ + eMEDIAFORMAT_UNKNOWN /**< Unknown media format */ +} MediaFormat; + +#endif /* DRMMEDIAFORMAT_H */ + diff --git a/middleware/drm/DrmMemorySystem.h b/middleware/drm/DrmMemorySystem.h new file mode 100644 index 000000000..90309f1bc --- /dev/null +++ b/middleware/drm/DrmMemorySystem.h @@ -0,0 +1,80 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef DRMMEMORYSYSTEM_H +#define DRMMEMORYSYSTEM_H + +/** + * @file DrmMemorySystem.h + * @brief Memory handler for Player DRM process + */ + +#include +#include +#include + +/** + * @class DRMMemorySystem + * @brief Handles the operations for DRM memory management + */ +class DRMMemorySystem { +public: + /** + * @brief Encode a block of data to send over the divide + * @param dataIn pointer to the data to encode + * @param dataInSz the size to encode + * @param out dataOut the data to send + * @return true if data is encoded + */ + virtual bool encode(const uint8_t *dataIn, uint32_t dataInSz, std::vector& dataOut) = 0; + /** + * @brief Decode from getting back + * @param dataIn pointer to the data to decode + * @param size the size to decode + * @param out dataOut the data to recover + * @param int dataOutSz the size of the space for data to recover + */ + virtual bool decode(const uint8_t* dataIn, uint32_t dataInSz, uint8_t *dataOut, uint32_t dataOutSz) = 0; + + /** + * @brief Call this if there's an failure external to the MS and it needs to tidy up unexpectedly + */ + virtual void terminateEarly() {} + + DRMMemorySystem(){} + DRMMemorySystem(const DRMMemorySystem&) = delete; + DRMMemorySystem& operator=(const DRMMemorySystem&) = delete; + virtual ~DRMMemorySystem() {} +}; + +/** + * @class DrmMemoryHandleCloser + * @brief This just closes a file on descope + */ +class DrmMemoryHandleCloser { +public: + DrmMemoryHandleCloser(int handle) : handle_(handle) {}; + ~DrmMemoryHandleCloser() { if (handle_ > 0) { close(handle_); } } +private: + int handle_; +}; + + +#endif /* DRMMEMORYSYSTEM_H */ + diff --git a/middleware/drm/DrmSession.cpp b/middleware/drm/DrmSession.cpp new file mode 100644 index 000000000..34087df3f --- /dev/null +++ b/middleware/drm/DrmSession.cpp @@ -0,0 +1,67 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file DrmSession.cpp +* @brief Source file for DrmSession. +*/ + +#include "DrmSession.h" +#include "PlayerLogManager.h" + +/** + * @brief Constructor for DrmSession. + */ +DrmSession::DrmSession(const string &keySystem) : m_keySystem(keySystem),m_OutputProtectionEnabled(false) + , mContentSecurityManagerSession() +{ +} + +/** + * @brief Destructor for DrmSession.. + */ +DrmSession::~DrmSession() +{ +} + +/** + * @brief Get the DRM System, ie, UUID for PlayReady WideVine etc.. + */ +string DrmSession::getKeySystem() +{ + return m_keySystem; +} + +/** + * @brief Function to decrypt GStreamer stream buffer. + */ +int DrmSession::decrypt(GstBuffer* keyIDBuffer, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer, GstCaps* caps) +{ + MW_LOG_ERR("GST decrypt method not implemented"); + return -1; +} + +/** + * @brief Function to decrypt stream buffer. + */ +int DrmSession::decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData) +{ + MW_LOG_ERR("Standard decrypt method not implemented"); + return -1; +} diff --git a/middleware/drm/DrmSession.h b/middleware/drm/DrmSession.h new file mode 100755 index 000000000..4ff9e7849 --- /dev/null +++ b/middleware/drm/DrmSession.h @@ -0,0 +1,176 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmSession.h + * @brief Header file for DrmSession + */ + + +#ifndef DrmSession_h +#define DrmSession_h +#include +#include +#include +#include +#include "DrmUtils.h" +#include "ContentSecurityManagerSession.h" + +using namespace std; + +#define PLAYREADY_KEY_SYSTEM_STRING "com.microsoft.playready" +#define WIDEVINE_KEY_SYSTEM_STRING "com.widevine.alpha" +#define CLEAR_KEY_SYSTEM_STRING "org.w3.clearkey" +#define VERIMATRIX_KEY_SYSTEM_STRING "com.verimatrix.ott" + +#define HDCP_COMPLIANCE_CHECK_FAILURE 4327 +#define HDCP_OUTPUT_PROTECTION_FAILURE 4427 +/** + * @enum KeyState + * @brief DRM session states + */ +typedef enum +{ + KEY_INIT = 0, /**< Has been initialized */ + KEY_PENDING = 1, /**< Has a key message pending to be processed */ + KEY_READY = 2, /**< Has a usable key */ + KEY_ERROR = 3, /**< Has an error */ + KEY_CLOSED = 4, /**< Has been closed */ + KEY_ERROR_EMPTY_SESSION_ID = 5 /**< Has Empty DRM session id */ + +} KeyState; + +/** + * @class DrmSession + * @brief Base class for DRM sessions + */ +class DrmSession +{ +protected: + std::string m_keySystem; + bool m_OutputProtectionEnabled; + ContentSecurityManagerSession mContentSecurityManagerSession; +public: + /** + * @brief Create drm session with given init data + * @param f_pbInitData : pointer to initdata + * @param f_cbInitData : init data size + */ + virtual void generateDRMSession(const uint8_t *f_pbInitData,uint32_t f_cbInitData, std::string &customData ) = 0; + + /** + * @brief Generate key request from DRM session + * Caller function should free the returned memory. + * @param destinationURL : gets updated with license server url + * @param timeout: max timeout untill which to wait for cdm key generation. + * @retval Pointer to DrmData containing license request. + */ + virtual DrmData* generateKeyRequest(string& destinationURL, uint32_t timeout) = 0; + + /** + * @brief Updates the received key to DRM session + * @param key : License key from license server. + * @param timeout: max timeout untill which to wait for cdm key processing. + * @retval returns status of update request + */ + virtual int processDRMKey(DrmData* key, uint32_t timeout) = 0; + + /** + * @fn decrypt + * @param keyIDBuffer : Key ID. + * @param ivBuffer : Initialization vector. + * @param buffer : Data to decrypt. + * @param subSampleCount : Number of subsamples. + * @param subSamplesBuffer : Subsamples buffer. + * @param caps : Caps of the media that is currently being decrypted + * @retval Returns status of decrypt request. + */ + virtual int decrypt(GstBuffer* keyIDBuffer, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer, GstCaps* caps = NULL); + + /** + * @fn decrypt + * @param f_pbIV : Initialization vector. + * @param f_cbIV : Initialization vector length. + * @param payloadData : Data to decrypt. + * @param payloadDataSize : Size of data. + * @param ppOpaqueData : pointer to opaque buffer in case of SVP. + * @retval Returns status of decrypt request. + */ + virtual int decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData); + + /** + * @brief Get the current state of DRM Session. + * @retval KeyState + */ + virtual KeyState getState() = 0; + + /** + * @brief Waits for the current state of DRM Session to match required.. Timeout is that from the helper. + * Only used by OCDM Adapter for now + * @param state the KeyState to achieve + * @param timeout how long to wait in mSecs + * @return true if obtained, false otherwise + */ + virtual bool waitForState(KeyState state, const uint32_t timeout) { return true; } + + /** + * @brief Clear the current session context + * So that new init data can be bound. + */ + virtual void clearDecryptContext() = 0; + + /** + * @fn DrmSession + * @param keySystem : DRM key system uuid + */ + DrmSession(const string &keySystem); + /** + * @brief Copy constructor disabled + * + */ + DrmSession(const DrmSession&) = delete; + /** + * @brief assignment operator disabled + * + */ + DrmSession& operator=(const DrmSession&) = delete; + /** + * @fn ~DrmSession + */ + virtual ~DrmSession(); + + /** + * @fn getKeySystem + * @retval DRM system uuid + */ + string getKeySystem(); + + /** + * @brief Set the OutputProtection for DRM Session + * @param bValue : Enable/Disable flag + * @retval void + */ + void setOutputProtection(bool bValue) { m_OutputProtectionEnabled = bValue;} +#if defined(USE_OPENCDM_ADAPTER) + virtual void setKeyId(const std::vector& keyId) {}; +#endif + void setSecManagerSession(ContentSecurityManagerSession session){mContentSecurityManagerSession=session;} + ContentSecurityManagerSession getSecManagerSession() const { return mContentSecurityManagerSession;} +}; +#endif diff --git a/middleware/drm/DrmSessionFactory.cpp b/middleware/drm/DrmSessionFactory.cpp new file mode 100644 index 000000000..b5bf0179c --- /dev/null +++ b/middleware/drm/DrmSessionFactory.cpp @@ -0,0 +1,66 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmSessionFactory.cpp + * @brief Source file for DrmSessionFactory + */ + +#include "DrmSessionFactory.h" +#if defined(USE_OPENCDM_ADAPTER) +#include "OcdmBasicSessionAdapter.h" +#include "OcdmGstSessionAdapter.h" +#endif +#include "ClearKeyDrmSession.h" + +/** + * @brief Creates an appropriate DRM session based on the given DrmHelper + */ +DrmSession* DrmSessionFactory::GetDrmSession(DrmHelperPtr drmHelper, DrmCallbacks *drmCallbacks) +{ + const std::string systemId = drmHelper->ocdmSystemId(); + +#if defined (USE_OPENCDM_ADAPTER) + if (drmHelper->isClearDecrypt()) + { +#if defined(USE_CLEARKEY) + if (systemId == CLEAR_KEY_SYSTEM_STRING) + { + return new ClearKeySession(); + } + else +#endif + { + return new OCDMBasicSessionAdapter(drmHelper, drmCallbacks); + } + } + else + { + return new OCDMGSTSessionAdapter(drmHelper, drmCallbacks); + } +#else // No form of OCDM support. Attempt to fallback to hardcoded session classes + if (systemId == CLEAR_KEY_SYSTEM_STRING) + { +#if defined(USE_CLEARKEY) + return new ClearKeySession(); +#endif // USE_CLEARKEY + } +#endif // Not USE_OPENCDM_ADAPTER + return NULL; +} diff --git a/middleware/drm/DrmSessionFactory.h b/middleware/drm/DrmSessionFactory.h new file mode 100644 index 000000000..483c90001 --- /dev/null +++ b/middleware/drm/DrmSessionFactory.h @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file DrmSessionFactory.h +* @brief Header file for DrmSessionFactory +*/ + +#ifndef DrmSessionFactory_h +#define DrmSessionFactory_h + +#include "DrmSession.h" +#include "DrmHelper.h" +#include "DrmCallbacks.h" +/** + * @class DrmSessionFactory + * @brief Factory class to create DRM sessions based on + * requested system ID + */ +class DrmSessionFactory +{ +public: + + /** + * @fn GetDrmSession + * + * @param[in] drmHelper - DrmHelper instance + * @return Pointer to DrmSession. + */ + static DrmSession* GetDrmSession(DrmHelperPtr drmHelper, DrmCallbacks *drmCallbacks); +}; +#endif diff --git a/middleware/drm/DrmSessionManager.cpp b/middleware/drm/DrmSessionManager.cpp new file mode 100755 index 000000000..61c8f404b --- /dev/null +++ b/middleware/drm/DrmSessionManager.cpp @@ -0,0 +1,851 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file DrmSessionManager.cpp + * @brief Source file for DrmSessionManager. + */ + +#include "DrmSessionManager.h" +#include "_base64.h" +#include +#include "DrmHelper.h" +#include +#include "PlayerUtils.h" +#include "ContentSecurityManager.h" +#define DRM_METADATA_TAG_START "" +#define DRM_METADATA_TAG_END "" +#define SESSION_TOKEN_URL "http://localhost:50050/authService/getSessionToken" + +#define INVALID_SESSION_SLOT -1 +#define DEFAULT_CDM_WAIT_TIMEOUT_MS 2000 + +KeyID::KeyID() : creationTime(0), isFailedKeyId(false), isPrimaryKeyId(false), data() +{ +} + + +/** + * @brief DrmSessionManager constructor. + */ +DrmSessionManager::DrmSessionManager(int maxDrmSessions, void *player, std::function watermarkSessionUpdateCallback) : drmSessionContexts(NULL), + cachedKeyIDs(NULL), accessToken(NULL), + accessTokenLen(0), sessionMgrState(SessionMgrState::eSESSIONMGR_ACTIVE), accessTokenMutex(), + cachedKeyMutex() + ,mEnableAccessAttributes(true) + ,mDrmSessionLock() + ,mMaxDRMSessions(maxDrmSessions) + ,playerSecInstance(nullptr) + ,mContentSecurityManagerSession() + ,mIsVideoOnMute(false) + ,mCurrentSpeed(0) + ,mFirstFrameSeen(false) + ,mPlayerSendWatermarkSessionUpdateEventCB(watermarkSessionUpdateCallback) +{ + drmSessionContexts = new DrmSessionContext[mMaxDRMSessions]; + cachedKeyIDs = new KeyID[mMaxDRMSessions]; + m_drmConfigParam = new configs(); + playerSecInstance = new PlayerSecInterface(); + + registerCallback(); + + MW_LOG_INFO("DrmSessionManager MaxSession:%d",mMaxDRMSessions); +} + +/** + * @brief DrmSessionManager Destructor. + */ +DrmSessionManager::~DrmSessionManager() +{ + clearAccessToken(); + clearSessionData(); + MW_SAFE_DELETE_ARRAY(drmSessionContexts); + MW_SAFE_DELETE_ARRAY(cachedKeyIDs); + MW_SAFE_DELETE(playerSecInstance); + ContentSecurityManager::setWatermarkSessionEvent_CB(nullptr); +} +void DrmSessionManager::UpdateDRMConfig( + bool useSecManager, + bool enablePROutputProtection, + bool propagateURIParam, + bool isFakeTune, + bool wideVineKIDWorkaround) +{ + m_drmConfigParam->mUseSecManager = useSecManager; + m_drmConfigParam->mEnablePROutputProtection = enablePROutputProtection; + m_drmConfigParam->mPropagateURIParam = propagateURIParam; + m_drmConfigParam->mIsFakeTune = isFakeTune; + m_drmConfigParam->mIsWVKIDWorkaround = wideVineKIDWorkaround; + +} + +/** + * @brief Clean up the memory used by session variables. + */ +void DrmSessionManager::clearSessionData() +{ + MW_LOG_WARN(" DrmSessionManager:: Clearing session data"); + for(int i = 0 ; i < mMaxDRMSessions; i++) + { + if (drmSessionContexts != NULL && drmSessionContexts[i].drmSession != NULL) + { + MW_SAFE_DELETE(drmSessionContexts[i].drmSession); + drmSessionContexts[i] = DrmSessionContext(); + } + + { + std::lock_guard guard(cachedKeyMutex); + if (cachedKeyIDs != NULL) + { + cachedKeyIDs[i] = KeyID(); + } + } + } +} + +/** + * @brief Set Session manager state + */ +void DrmSessionManager::setSessionMgrState(SessionMgrState state) +{ + sessionMgrState = state; +} + +/** + * @brief Get Session manager state + */ +SessionMgrState DrmSessionManager::getSessionMgrState() +{ + return sessionMgrState; +} + +/** + * @brief Clean up the failed keyIds. + */ +void DrmSessionManager::clearFailedKeyIds() +{ + std::lock_guard guard(cachedKeyMutex); + for(int i = 0 ; i < mMaxDRMSessions; i++) + { + if(cachedKeyIDs[i].isFailedKeyId) + { + if(!cachedKeyIDs[i].data.empty()) + { + cachedKeyIDs[i].data.clear(); + } + cachedKeyIDs[i].isFailedKeyId = false; + cachedKeyIDs[i].creationTime = 0; + } + cachedKeyIDs[i].isPrimaryKeyId = false; + } +} + +/** + * @brief Clean up the memory for accessToken. + */ +void DrmSessionManager::clearAccessToken() +{ + if(accessToken) + { + free(accessToken); + accessToken = NULL; + accessTokenLen = 0; + } +} + +/** + * @brief Get the failed key ID status for a specific session + */ +bool DrmSessionManager::getFailedKeyIdStatus(int sessionIndex) +{ + bool rc = false; + std::lock_guard guard(cachedKeyMutex); + if (sessionIndex >= 0 && sessionIndex < mMaxDRMSessions && cachedKeyIDs) + { + rc = cachedKeyIDs[sessionIndex].isFailedKeyId; + } + return rc; +} + +/** + * @brief Clean up the Session Data if license key acquisition failed or if LicenseCaching is false. + */ +void DrmSessionManager::clearDrmSession(bool forceClearSession) +{ + for(int i = 0 ; i < mMaxDRMSessions; i++) + { + // Clear the session data if license key acquisition failed or if forceClearSession is true in the case of LicenseCaching is false. + if((cachedKeyIDs[i].isFailedKeyId || forceClearSession) && drmSessionContexts != NULL) + { + std::lock_guard guard(drmSessionContexts[i].sessionMutex); + if (drmSessionContexts[i].drmSession != NULL) + { + MW_LOG_WARN("DrmSessionManager:: Clearing failed Session Data Slot : %d", i); + MW_SAFE_DELETE(drmSessionContexts[i].drmSession); + } + } + } +} + + +void DrmSessionManager::setVideoWindowSize(int width, int height) +{ + auto localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + MW_LOG_WARN("In DrmSessionManager:: setting video window size w:%d x h:%d mMaxDRMSessions=%d sessionID=[%" PRId64 "]",width,height,mMaxDRMSessions,localSession.getSessionID()); + if(localSession.isSessionValid()) + { + MW_LOG_WARN("In DrmSessionManager:: valid session ID. Calling setVideoWindowSize()."); + ContentSecurityManager::GetInstance()->setVideoWindowSize(localSession.getSessionID(), width, height); + } +} +/** + * @brief Deactivate the session while video on mute and then activate it and update the speed once video is unmuted + */ +void DrmSessionManager::setVideoMute(bool live, double currentLatency, bool livepoint , double liveOffsetMs,bool isVideoOnMute, double positionMs) +{ + MW_LOG_WARN("Video mute status (new): %d, state changed: %.1s, pos: %f", isVideoOnMute, (isVideoOnMute == mIsVideoOnMute) ? "N":"Y", positionMs); + + mIsVideoOnMute.store(isVideoOnMute); + auto localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + if(localSession.isSessionValid()) + { + ContentSecurityManager::GetInstance()->UpdateSessionState(localSession.getSessionID(), !mIsVideoOnMute.load()); + if(!mIsVideoOnMute.load()) + { + //this is required as secmanager waits for speed update to show wm once session is active + int speed=mCurrentSpeed.load(); + MW_LOG_INFO("Setting speed after video unmute %d ", speed); + setPlaybackSpeedState(live, currentLatency, livepoint, liveOffsetMs, speed, positionMs); + } + } +} + +/** + * @brief De-activate watermark and prevent it from being re-enabled until we get a new first video frame at normal play speed + */ +void DrmSessionManager::hideWatermarkOnDetach(void) +{ + MW_LOG_WARN("Clearing first frame flag and de-activating watermark."); + auto localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + if(localSession.isSessionValid()) + { + ContentSecurityManager::GetInstance()->UpdateSessionState(localSession.getSessionID(), false); + } + mFirstFrameSeen.store(false); +} + + +void DrmSessionManager::setPlaybackSpeedState(bool live, double currentLatency, bool livepoint , double liveOffsetMs, int speed, double positionMs, bool firstFrameSeen) +{ + bool isVideoOnMute=mIsVideoOnMute.load(); + auto localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + MW_LOG_WARN("In DrmSessionManager::after calling setPlaybackSpeedState speed=%d position=%f sessionID=[%" PRId64 "], mute: %d",speed, positionMs, localSession.getSessionID(), isVideoOnMute); + mCurrentSpeed.store(speed); + if(firstFrameSeen) + { + MW_LOG_INFO("First frame seen - latched"); + mFirstFrameSeen.store(true); + } + else if (mFirstFrameSeen.load()) + { + MW_LOG_INFO("First frame has previously been seen, we will send speed updates"); + } + + if(localSession.isSessionValid() && !mIsVideoOnMute.load() && mFirstFrameSeen.load()) + { + MW_LOG_INFO("calling ContentSecurityManager::setPlaybackSpeedState()"); + + double adjustedPos; + if(live) + { + // Live (not VOD) playback: SecManager expects zero for live, negative position if playhead in past + // This is relative to the broadcast live so we can just return the latency here + adjustedPos = -currentLatency; + MW_LOG_INFO("setPlaybackSpeedState for live playback: position=%fms (at live %d, live offset %fms))", + adjustedPos, livepoint,liveOffsetMs); + } + else + { + // VOD - report position relative to start of VOD asset + adjustedPos = positionMs; + } + + MW_LOG_INFO("setPlaybackSpeedState pos=%fs speed=%d", adjustedPos/1000, speed ); + ContentSecurityManager::GetInstance()->setPlaybackSpeedState(localSession.getSessionID(), speed, adjustedPos); + } + else + { + bool firstFrameSeenCopy=mFirstFrameSeen.load(); + isVideoOnMute=mIsVideoOnMute.load(); + MW_LOG_INFO("Not calling ContentSecurityManager::setPlaybackSpeedState(), sessionID=[%" PRId64 "], mIsVideoOnMute=%d, firstFrameSeen=%d", localSession.getSessionID(), isVideoOnMute, firstFrameSeenCopy); + } +} + + +/** + * @brief Extract substring between (excluding) two string delimiters. + * + * @param[in] parentStr - Parent string from which substring is extracted. + * @param[in] startStr, endStr - String delimiters. + * @return Returns the extracted substring; Empty string if delimiters not found. + */ +string _extractSubstring(string parentStr, string startStr, string endStr) +{ + string ret = ""; + auto startPos = parentStr.find(startStr); + if(string::npos != startPos) + { + auto offset = strlen(startStr.c_str()); + auto endPos = parentStr.find(endStr, startPos + offset + 1); + if(string::npos != endPos) + { + ret = parentStr.substr(startPos + offset, endPos - (startPos + offset)); + } + } + return ret; +} + + +/** + * @fn IsKeyIdProcessed + * @param[in] keyIdArray - key Id extracted from pssh data + * @param[out] status - processed status of the key id success/fail + * @return bool - true if keyId is already marked as failed or cached, + * false if key is not cached + */ +bool DrmSessionManager::IsKeyIdProcessed(std::vector keyIdArray, bool &status) +{ + bool ret = false; + std::lock_guard guard(cachedKeyMutex); + for (int sessionSlot = 0; sessionSlot < mMaxDRMSessions; sessionSlot++) + { + auto keyIDSlot = cachedKeyIDs[sessionSlot].data; + if (!keyIDSlot.empty() && keyIDSlot.end() != std::find(keyIDSlot.begin(), keyIDSlot.end(), keyIdArray)) + { + std::string debugStr = PlayerLogManager::getHexDebugStr(keyIdArray); + MW_LOG_INFO("Session created/in progress with same keyID %s at slot %d", debugStr.c_str(), sessionSlot); + status = !cachedKeyIDs[sessionSlot].isFailedKeyId; + ret = true; + break; + } + } + return ret; +} + + +int DrmSessionManager::getSlotIdForSession(DrmSession* session) +{ + int slot = -1; + std::lock_guard guard(mDrmSessionLock); + + if (drmSessionContexts != NULL) + { + for (int i = 0; i < mMaxDRMSessions; i++) + { + if (drmSessionContexts[i].drmSession == session) + { + MW_LOG_INFO("DRM Session found at slot:%d", i); + slot = i; + break; + } + } + } + + if (slot == -1) + { + MW_LOG_WARN("DRM Session not found"); + } + + return slot; +} + +/** + * @brief Creates and/or returns the DRM session corresponding to keyId (Present in initDataPtr) + * DRMSession manager has two static DrmSession objects. + * This method will return the existing DRM session pointer if any one of these static + * DRM session objects are created against requested keyId. Binds the oldest DRM Session + * with new keyId if no matching keyId is found in existing sessions. + * @return Pointer to DrmSession for the given PSSH data; NULL if session creation/mapping fails. + */ +DrmSession * DrmSessionManager::createDrmSession( int& responseCode, + int &err, const char* systemId, MediaFormat mediaFormat, const unsigned char * initDataPtr, + uint16_t initDataLen, int streamType, + DrmCallbacks* player, void *metaDataPtr, const unsigned char* contentMetadataPtr, + bool isPrimarySession) +{ + DrmInfo drmInfo; + std::shared_ptr drmHelper; + DrmSession *drmSession = NULL; + + drmInfo.method = eMETHOD_AES_128; + drmInfo.mediaFormat = mediaFormat; + drmInfo.systemUUID = systemId; + drmInfo.bPropagateUriParams = m_drmConfigParam->mPropagateURIParam; + + if (!DrmHelperEngine::getInstance().hasDRM(drmInfo)) + { + MW_LOG_ERR(" Failed to locate DRM helper"); + } + else + { + drmHelper = DrmHelperEngine::getInstance().createHelper(drmInfo); + + if(contentMetadataPtr) + { + std::string contentMetadataPtrString = reinterpret_cast(contentMetadataPtr); + drmHelper->setDrmMetaData(contentMetadataPtrString); + } + + if (!drmHelper->parsePssh(initDataPtr, initDataLen)) + { + MW_LOG_ERR(" Failed to Parse PSSH from the DRM InitData"); + err = MW_CORRUPT_DRM_METADATA; + } + else + { + drmSession = DrmSessionManager::createDrmSession(responseCode, err,std::move(drmHelper), player, streamType, metaDataPtr); + } + } + + return drmSession; +} +/** + * @brief Create DrmSession by using the DrmHelper object + */ +DrmSession* DrmSessionManager::createDrmSession(int &responseCode, int &err, std::shared_ptr drmHelper, DrmCallbacks* Instance, int streamType,void* metaDataPtr) +{ + if (!drmHelper || !Instance) + { + /* This should never happen, since the caller should have already + ensure the provided DRMInfo is supported using hasDRM */ + MW_LOG_ERR(" Failed to create DRM Session invalid parameters "); + return nullptr; + } + + // protect createDrmSession multi-thread calls; found during PR 4.0 DRM testing + std::lock_guard guard(mDrmSessionLock); + + int cdmError = -1; + KeyState code = KEY_ERROR; + + if (SessionMgrState::eSESSIONMGR_INACTIVE == sessionMgrState) + { + MW_LOG_ERR(" SessionManager state inactive, aborting request"); + return nullptr; + } + + int selectedSlot = INVALID_SESSION_SLOT; + + MW_LOG_INFO("StreamType :%d keySystem is %s",streamType, drmHelper->ocdmSystemId().c_str()); + + /** + * Create drm session without primaryKeyId markup OR retrieve old DRM session. + */ + code = getDrmSession(err, drmHelper, selectedSlot, Instance); + /** + * KEY_READY code indicates that a previously created session is being reused. + */ + int isContentProcess = -1; + if((code == KEY_READY) || ((code != KEY_INIT) || (selectedSlot == INVALID_SESSION_SLOT))) + { + isContentProcess =0; + } + std::vector keyId; + drmHelper->getKey(keyId); + /* callback to initiate content protection data update */ + mCustomData = ContentUpdateCb(drmHelper, streamType, std::move(keyId), isContentProcess); + if (code == KEY_READY) + { + return drmSessionContexts[selectedSlot].drmSession; + } + + if ((code != KEY_INIT) || (selectedSlot == INVALID_SESSION_SLOT)) + { + MW_LOG_WARN(" Unable to get DrmSession : Key State %d ", code); + return nullptr; + } + code = initializeDrmSession(drmHelper, selectedSlot, err); + if (code != KEY_INIT) + { + MW_LOG_WARN(" Unable to initialize DrmSession : Key State %d ", code); + std::lock_guard guard(cachedKeyMutex); + if (cachedKeyIDs) + { + cachedKeyIDs[selectedSlot].isFailedKeyId = true; + } + return nullptr; + } + + if(m_drmConfigParam->mIsFakeTune) + { + MW_LOG_MIL( "Exiting fake tune after DRM initialization."); + std::lock_guard guard(cachedKeyMutex); + if (cachedKeyIDs) + { + cachedKeyIDs[selectedSlot].isFailedKeyId = true; + } + return nullptr; + } + code =this->AcquireLicenseCb(responseCode, std::move(drmHelper), selectedSlot, cdmError, (GstMediaType)streamType, metaDataPtr, false); + if (code != KEY_READY) + { + MW_LOG_WARN(" Unable to get Ready Status DrmSession : Key State %d ", code); + std::lock_guard guard(cachedKeyMutex); + if (cachedKeyIDs) + { + cachedKeyIDs[selectedSlot].isFailedKeyId = true; + } + return nullptr; + } + + // License acquisition was done, so mContentSecurityManagerSession will be populated now + const auto &localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + if (localSession.isSessionValid()) + { + MW_LOG_WARN(" Setting sessionId[%" PRId64 "] to current drmSession", localSession.getSessionID()); + drmSessionContexts[selectedSlot].drmSession->setSecManagerSession(localSession); + } + + return drmSessionContexts[selectedSlot].drmSession; +} + +/** + * @brief Create a DRM Session using the Drm Helper + * Determine a slot in the drmSession Contexts which can be used + */ +KeyState DrmSessionManager::getDrmSession(int &err, std::shared_ptr drmHelper, int &selectedSlot, DrmCallbacks* Instance, bool isPrimarySession) +{ + KeyState code = KEY_ERROR; + bool keySlotFound = false; + bool isCachedKeyId = false; + + std::vector keyIdArray; + std::map> keyIdArrays; + drmHelper->getKeys(keyIdArrays); + + drmHelper->getKey(keyIdArray); + + //Need to Check , Are all Drm Schemes/Helpers capable of providing a non zero keyId? + if (keyIdArray.empty()) + { + err = MW_FAILED_TO_GET_KEYID; + return code; + } + + if (keyIdArrays.empty()) + { + // Insert keyId into map + keyIdArrays[0] = keyIdArray; + } + + std::string keyIdDebugStr = PlayerLogManager::getHexDebugStr(keyIdArray); + + /* Slot Selection + * Find drmSession slot by going through cached keyIds + * Check if requested keyId is already cached + */ + int sessionSlot = 0; + + { + std::lock_guard guard(cachedKeyMutex); + + for (; sessionSlot < mMaxDRMSessions; sessionSlot++) + { + auto keyIDSlot = cachedKeyIDs[sessionSlot].data; + if (!keyIDSlot.empty() && keyIDSlot.end() != std::find(keyIDSlot.begin(), keyIDSlot.end(), keyIdArray)) + { + MW_LOG_INFO("Session created/in progress with same keyID %s at slot %d", keyIdDebugStr.c_str(), sessionSlot); + keySlotFound = true; + isCachedKeyId = true; + break; + } + } + + if (!keySlotFound) + { + /* Key Id not in cached list so we need to find out oldest slot to use; + * Oldest slot may be used by current playback which is marked primary + * Avoid selecting that slot + * */ + /*select the first slot that is not primary*/ + for (int index = 0; index < mMaxDRMSessions; index++) + { + if (!cachedKeyIDs[index].isPrimaryKeyId) + { + keySlotFound = true; + sessionSlot = index; + break; + } + } + + if (!keySlotFound) + { + MW_LOG_WARN(" Unable to find keySlot for keyId %s ", keyIdDebugStr.c_str()); + return KEY_ERROR; + } + + /*Check if there's an older slot */ + for (int index= sessionSlot + 1; index< mMaxDRMSessions; index++) + { + if (cachedKeyIDs[index].creationTime < cachedKeyIDs[sessionSlot].creationTime) + { + sessionSlot = index; + } + } + MW_LOG_WARN(" Selected slot %d for keyId %s", sessionSlot, keyIdDebugStr.c_str()); + } + else + { + // Already same session Slot is marked failed , not to proceed again . + if(cachedKeyIDs[sessionSlot].isFailedKeyId) + { + MW_LOG_WARN(" Found FailedKeyId at sesssionSlot :%d, return key error",sessionSlot); + return KEY_ERROR; + } + } + + + if (!isCachedKeyId) + { + if(cachedKeyIDs[sessionSlot].data.size() != 0) + { + cachedKeyIDs[sessionSlot].data.clear(); + } + + cachedKeyIDs[sessionSlot].isFailedKeyId = false; + + std::vector> data; + for(auto& keyId : keyIdArrays) + { + std::string debugStr = PlayerLogManager::getHexDebugStr(keyId.second); + MW_LOG_INFO("Insert[%d] - slot:%d keyID %s", keyId.first, sessionSlot, debugStr.c_str()); + data.push_back(keyId.second); + } + + cachedKeyIDs[sessionSlot].data = data; + } + cachedKeyIDs[sessionSlot].creationTime = GetCurrentTimeMS(); + cachedKeyIDs[sessionSlot].isPrimaryKeyId = isPrimarySession; + } + + selectedSlot = sessionSlot; + const std::string systemId = drmHelper->ocdmSystemId(); + std::lock_guard guard(drmSessionContexts[sessionSlot].sessionMutex); + if (drmSessionContexts[sessionSlot].drmSession != NULL) + { + if (drmHelper->ocdmSystemId() != drmSessionContexts[sessionSlot].drmSession->getKeySystem()) + { + MW_LOG_WARN("changing DRM session for %s to %s", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), drmHelper->ocdmSystemId().c_str()); + } + else if (cachedKeyIDs[sessionSlot].data.end() != std::find(cachedKeyIDs[sessionSlot].data.begin(), cachedKeyIDs[sessionSlot].data.end() ,drmSessionContexts[sessionSlot].data)) + { + KeyState existingState = drmSessionContexts[sessionSlot].drmSession->getState(); + if (existingState == KEY_READY) + { + MW_LOG_INFO("Found drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + auto slotSession = drmSessionContexts[sessionSlot].drmSession->getSecManagerSession(); + if (slotSession.isSessionValid() && (!mContentSecurityManagerSession.isSessionValid()) ) + { + // Set the drmSession's ID as mContentSecurityManagerSession so that this code will not be repeated for multiple calls for createDrmSession + mContentSecurityManagerSession = slotSession; + bool videoMuteState = mIsVideoOnMute.load(); + MW_LOG_WARN("Activating re-used DRM, sessionId[%" PRId64 "], with video mute = %d", slotSession.getSessionID(), videoMuteState); + ContentSecurityManager::GetInstance()->UpdateSessionState(slotSession.getSessionID(), true); + } + return KEY_READY; + } + if (existingState == KEY_INIT) + { + MW_LOG_WARN("Found drm session in INIT state with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + return KEY_INIT; + } + else if (existingState <= KEY_READY) + { + if (drmSessionContexts[sessionSlot].drmSession->waitForState(KEY_READY, drmHelper->keyProcessTimeout())) + { + MW_LOG_WARN("Waited for drm session READY with same keyID %s - Reusing drm session", keyIdDebugStr.c_str()); + return KEY_READY; + } + MW_LOG_WARN("key was never ready for %s ", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str()); + //CID-164094 : Added the mutex lock due to overriding the isFailedKeyId variable + std::lock_guard guard(cachedKeyMutex); + cachedKeyIDs[selectedSlot].isFailedKeyId = true; + return KEY_ERROR; + } + else + { + MW_LOG_WARN("existing DRM session for %s has error state %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), existingState); + //CID-164094 : Added the mutex lock due to overriding the isFailedKeyId variable + std::lock_guard guard(cachedKeyMutex); + cachedKeyIDs[selectedSlot].isFailedKeyId = true; + return KEY_ERROR; + } + } + else + { + MW_LOG_WARN("existing DRM session for %s has different key in slot %d", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str(), sessionSlot); + } + MW_LOG_WARN("deleting existing DRM session for %s ", drmSessionContexts[sessionSlot].drmSession->getKeySystem().c_str()); + MW_SAFE_DELETE(drmSessionContexts[sessionSlot].drmSession); + } + this->ProfileUpdateCb(); + + drmSessionContexts[sessionSlot].drmSession = DrmSessionFactory::GetDrmSession(drmHelper, Instance); + if (drmSessionContexts[sessionSlot].drmSession != NULL) + { + MW_LOG_INFO("Created new DrmSession for DrmSystemId %s", systemId.c_str()); + drmSessionContexts[sessionSlot].data = keyIdArray; + code = drmSessionContexts[sessionSlot].drmSession->getState(); + // exception : by default for all types of drm , outputprotection is not handled in player + // for playready , its configured within player + if (systemId == PLAYREADY_KEY_SYSTEM_STRING && m_drmConfigParam->mEnablePROutputProtection) + { + drmSessionContexts[sessionSlot].drmSession->setOutputProtection(true); + drmHelper->setOutputProtectionFlag(true); + } + } + else + { + MW_LOG_WARN("Unable to Get DrmSession for DrmSystemId %s", systemId.c_str()); + err = MW_DRM_INIT_FAILED ; + } + +#if defined(USE_OPENCDM_ADAPTER) + drmSessionContexts[sessionSlot].drmSession->setKeyId(keyIdArray); +#endif + + return code; +} + +/** + * @brief Initialize the Drm System with InitData(PSSH) + */ +KeyState DrmSessionManager::initializeDrmSession(std::shared_ptr drmHelper, int sessionSlot, int &err ) +{ + KeyState code = KEY_ERROR; + + std::vector drmInitData; + drmHelper->createInitData(drmInitData); + + std::lock_guard guard(drmSessionContexts[sessionSlot].sessionMutex); + MW_LOG_INFO("DRM session Custom Data - %s ", mCustomData.empty()?"NULL":mCustomData.c_str()); + drmSessionContexts[sessionSlot].drmSession->generateDRMSession(drmInitData.data(), (uint32_t)drmInitData.size(), mCustomData); + + code = drmSessionContexts[sessionSlot].drmSession->getState(); + if (code != KEY_INIT) + { + MW_LOG_ERR("DRM session was not initialized : Key State %d ", code); + if (code == KEY_ERROR_EMPTY_SESSION_ID) + { + MW_LOG_ERR("DRM session ID is empty: Key State %d ", code); + err = MW_DRM_SESSIONID_EMPTY; + } + else + { + err= MW_DRM_DATA_BIND_FAILED; + } + } + + return code; +} +/** + * @brief Re-use the current seesion ID, watermarking variables and de-activate watermarking session status + */ +void DrmSessionManager::notifyCleanup() +{ + auto localSession = mContentSecurityManagerSession; //Remove potential isSessionValid(), getSessionID() race by using a local copy + if(localSession.isSessionValid()) + { + // Set current session to inactive + MW_LOG_WARN("De-activate DRM session [%" PRId64 "] and watermark", localSession.getSessionID() ); + ContentSecurityManager::GetInstance()->UpdateSessionState(localSession.getSessionID(), false); + // Reset the session ID, the session ID is preserved within DrmSession instances + mContentSecurityManagerSession.setSessionInvalid(); //note this doesn't necessarily close the session as the session ID is also saved in the slot + mCurrentSpeed.store(0); + mFirstFrameSeen.store(false); + } +} + +/** + * @brief To update the max DRM sessions supported + * + * @param[in] maxSessions max DRM Sessions + */ +void DrmSessionManager::UpdateMaxDRMSessions(int maxSessions) +{ + if (mMaxDRMSessions != maxSessions) + { + // Clean up the current sessions + clearSessionData(); + MW_SAFE_DELETE_ARRAY(drmSessionContexts); + MW_SAFE_DELETE_ARRAY(cachedKeyIDs); + + //Update to new session count + mMaxDRMSessions = maxSessions; + drmSessionContexts = new DrmSessionContext[mMaxDRMSessions]; + cachedKeyIDs = new KeyID[mMaxDRMSessions]; + MW_LOG_INFO("Updated DrmSessionManager MaxSession to:%d", mMaxDRMSessions); + } +} + +/** + * @brief To register the callback for watermark session update + */ +void DrmSessionManager::registerCallback() +{ + auto instance = this; + + std::function watermarkCallBack = + [instance](uint32_t sessionHandle, uint32_t status, const std::string& system) + { + MW_LOG_INFO("[DrmSessionManager] Received WM callback: handle=%u, status=%u, system=%s", + sessionHandle, status, system.c_str()); + + if (instance && instance->mPlayerSendWatermarkSessionUpdateEventCB) + { + MW_LOG_INFO("[DrmSessionManager] Forwarding WM callback -> aampInstance callback (%p)", + (void*)&(instance->mPlayerSendWatermarkSessionUpdateEventCB)); + + // call the actual AAMP callback + instance->mPlayerSendWatermarkSessionUpdateEventCB(sessionHandle, status, system); + } + else + { + MW_LOG_ERR("[DrmSessionManager] ERROR: mPlayerSendWatermarkSessionUpdateEventCB not set!"); + } + }; + ContentSecurityManager::setWatermarkSessionEvent_CB(watermarkCallBack); + MW_LOG_INFO("WatermarkSessionEvent Callback registered"); +} + +/** + * @brief To wrap the callback for watermark session update + * @param[in] sessionHandle session handle + * @param[in] status status of the session + * @param[in] systemData system data + * @retval void + */ +void DrmSessionManager::watermarkSessionHandlerWrapper(uint32_t sessionHandle, uint32_t status, const std::string &systemData) +{ + if(NULL != mPlayerSendWatermarkSessionUpdateEventCB) + { + mPlayerSendWatermarkSessionUpdateEventCB( sessionHandle, status, systemData); + } +} diff --git a/middleware/drm/DrmSessionManager.h b/middleware/drm/DrmSessionManager.h new file mode 100644 index 000000000..0bd8a7171 --- /dev/null +++ b/middleware/drm/DrmSessionManager.h @@ -0,0 +1,497 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** +* @file DrmSessionManager.h +* @brief Header file for DRM session manager +*/ +#ifndef DrmSessionManager_h +#define DrmSessionManager_h + +#include "DrmSessionFactory.h" +#include "DrmSession.h" +#include "DrmUtils.h" +#include "GstUtils.h" +#include +#include +#include "DrmHelper.h" + +#include "PlayerSecInterface.h" +#include "ContentSecurityManagerSession.h" + +#include + + +#define VIDEO_SESSION 0 +#define AUDIO_SESSION 1 + +/** + * @struct DrmSessionContext + * @brief To store drmSession and keyId data. + */ +struct DrmSessionContext +{ + std::vector data; + std::mutex sessionMutex; + DrmSession * drmSession; + + DrmSessionContext() : sessionMutex(), drmSession(NULL),data() + { + } + DrmSessionContext(const DrmSessionContext& other) : data(other.data), drmSession(other.drmSession) + { + // Releases memory allocated after destructing any of these objects + } + DrmSessionContext& operator=(const DrmSessionContext& other) + { + data = other.data; + drmSession = other.drmSession; + return *this; + } + ~DrmSessionContext() + { + } +}; + +/** + * @struct KeyID + * @brief Structure to hold, keyId and session creation time for + * keyId + */ +struct KeyID +{ + std::vector> data; + long long creationTime; + bool isFailedKeyId; + bool isPrimaryKeyId; + + KeyID(); +}; + +/** + * @brief Enum to represent session manager state. + * Session manager would abort any createDrmSession + * request if in eSESSIONMGR_INACTIVE state. + */ +typedef enum{ + eSESSIONMGR_INACTIVE, /**< DRM Session mgr is inactive */ + eSESSIONMGR_ACTIVE /**< DRM session mgr is active */ +}SessionMgrState; + +/** + * @brief Enum to represent DRM request type. + */ +typedef enum{ + DRM_GET_LICENSE, /**< DRM get license request */ + DRM_GET_LICENSE_SEC /**< DRM get license SEC request */ +}DrmRequestType; + +struct configs{ + bool mUseSecManager; + int mLicenseRetryWaitTime; + int mDrmNetworkTimeout; + int mDrmStallTimeout; + int mCurlConnectTimeout; + bool mCurlLicenseLogging; + bool mRuntimeDRMConfig; + int mContentProtectionDataUpdateTimeout; + bool mEnablePROutputProtection; + bool mPropagateURIParam; + bool mIsFakeTune; + bool mIsWVKIDWorkaround; +}; +/** + * @class DrmSessionManager + * @brief Controller for managing DRM sessions. + */ +class DrmSessionManager +{ +public: + + DrmSessionContext *drmSessionContexts; + configs *m_drmConfigParam; + PlayerSecInterface *playerSecInstance;/** PlayerSecInterface instance **/ + ContentSecurityManagerSession mContentSecurityManagerSession; + std::atomic mFirstFrameSeen; + std::atomic mIsVideoOnMute; + std::atomic mCurrentSpeed; +private: + KeyID *cachedKeyIDs; + char* accessToken; + int accessTokenLen; + SessionMgrState sessionMgrState; + std::mutex accessTokenMutex; + std::mutex cachedKeyMutex; + std::mutex mDrmSessionLock; + bool mEnableAccessAttributes; + int mMaxDRMSessions; + std::function mPlayerSendWatermarkSessionUpdateEventCB; + /** + * @brief Copy constructor disabled + * + */ + DrmSessionManager(const DrmSessionManager &) = delete; + /** + * @brief assignment operator disabled + * + */ + DrmSessionManager& operator=(const DrmSessionManager &) = delete; + /** + * @fn write_callback + * @param[in] ptr - Pointer to received data. + * @param[in] size, nmemb - Size of received data (size * nmemb). + * @param[out] userdata - Pointer to buffer where the received data is copied. + * @return returns the number of bytes processed. + */ + static size_t write_callback(char *ptr, size_t size, size_t nmemb, + void *userdata); + /** + * @brief + * @param clientp app-specific as optionally set with CURLOPT_PROGRESSDATA + * @param dltotal total bytes expected to download + * @param dlnow downloaded bytes so far + * @param ultotal total bytes expected to upload + * @param ulnow uploaded bytes so far + * @retval Return non-zero for CURLE_ABORTED_BY_CALLBACK + */ + static int progress_callback(void *clientp, double dltotal, + double dlnow, double ultotal, double ulnow ); + + /** + * @brief callback invoked on http header by curl + * @param ptr pointer to buffer containing the data + * @param size size of the buffer + * @param nmemb number of bytes + * @param user_data CurlCallbackContext pointer + * @retval + */ + static size_t header_callback(const char *ptr, size_t size, size_t nmemb, void *user_data); +public: + + /** + * @fn DrmSessionManager + */ + DrmSessionManager(int maxDrmSessions, void *player, std::function watermarkSessionUpdateCallback); + + void initializeDrmSessions(); + + /** + * @fn watermarkSessionHandlerWrapper + * @brief Wrapper function to handle session watermark. + * @param[in] sessionHandle - Session handle. + * @param[in] status - Status of the session. + * @param[in] systemData - System data. + */ + void watermarkSessionHandlerWrapper(uint32_t sessionHandle, uint32_t status, const std::string &systemData); + + /** + * @fn registerCallback + */ + void registerCallback( ); + + /** + * @brief Set the Common Key Duration object + * + * @param keyDuration key duration + */ + void SetCommonKeyDuration(int keyDuration); + + /** + * @brief Set to true if error event to be sent to application if any license request fails + * Otherwise, error event will be sent if a track doesn't have a successful or pending license request + * + * @param sendErrorOnFailure key duration + */ + void SetSendErrorOnFailure(bool sendErrorOnFailure); + + /** + * @brief Queue a content protection info to be processed later + * + * @param drmHelper DrmHelper shared_ptr + * @param periodId ID of the period to which CP belongs to + * @param adapId Index of the adaptation to which CP belongs to + * @param type media type + * @param isVssPeriod flag denotes if this is for a VSS period + * @return true if successfully queued + * @return false if error occurred + */ + bool QueueContentProtection(DrmHelperPtr drmHelper, std::string periodId, uint32_t adapIdx, GstMediaType type, bool isVssPeriod = false); + + /** + * @brief Queue a content protection event to the pipeline + * + * @param drmHelper DrmHelper shared_ptr + * @param periodId ID of the period to which CP belongs to + * @param adapId Index of the adaptation to which CP belongs to + * @param type media type + * @return none + */ + void QueueProtectionEvent(DrmHelperPtr drmHelper, std::string periodId, uint32_t adapIdx, GstMediaType type); + + + /** + * @fn ~DrmSessionManager + */ + ~DrmSessionManager(); + /** + * @fn createDrmSession + * @param[in] err - To retrieve the error case and to report to application + * @param[in] systemId - UUID of the DRM system. + * @param[in] initDataPtr - Pointer to PSSH data. + * @param[in] dataLength - Length of PSSH data. + * @param[in] streamType - Whether audio or video. + * @param[in] contentMetadata - Pointer to content meta data, when content meta data + * is already extracted during manifest parsing. Used when content meta data + * is available as part of another PSSH header, like DRM Agnostic PSSH + * header. + * @param[in] Player - Pointer to player, for DRM related profiling. + * @retval error_code - Gets updated with proper error code, if session creation fails. + * No NULL checks are done for error_code, caller should pass a valid pointer. + */ + DrmSession * createDrmSession(int &responseCode, int &err, const char* systemId, MediaFormat mediaFormat, + const unsigned char * initDataPtr, uint16_t dataLength, int streamType, + DrmCallbacks* player, void *ptr, const unsigned char *contentMetadata = nullptr, + bool isPrimarySession = false ); + /** + * @fn createDrmSession + * @return drmSession + */ + DrmSession* createDrmSession(int& responseCode, int &err, DrmHelperPtr drmHelper, DrmCallbacks* Instance, int streamType, void *metaDataPtr); + + /** + * @fn IsKeyIdProcessed + * @param[in] keyIdArray - key Id extracted from pssh data + * @param[out] status - processed status of the key id success/fail + * @return bool - true if keyId is already marked as failed or processed, + * false if key is not cached + */ + bool IsKeyIdProcessed(std::vector keyIdArray, bool &status); + /** + * @fn clearSessionData + * + * @return void. + */ + void clearSessionData(); + /** + * @fn clearAccessToken + * + * @return void. + */ + void clearAccessToken(); + /** + * @fn clearFailedKeyIds + * + * @return void. + */ + void clearFailedKeyIds(); + + + /** + * @fn getFailedKeyIdStatus + * + * @param sessionIndex - curl session index to check + * @return bool - true if the key ID is marked as failed, false otherwise + */ + bool getFailedKeyIdStatus(int sessionIndex); + + /** + * @fn clearDrmSession + * + * @param forceClearSession clear the drm session irrespective of failed keys if LicenseCaching is false. + * @return void. + */ + void clearDrmSession(bool forceClearSession = false); + + void setVideoWindowSize(int width, int height); + + /** + * @fn De-activate watermark and prevent it from being re-enabled until we get a new first video frame at normal play speed + * @return void. + */ + void hideWatermarkOnDetach(); + + /** + * @fn Update tracking of speed status and send watermarking requests as required. This is based on video presence, video mute state, and speed + * @param speed playback speed + * @param position indicates the playback position at which most recent playback activity began + * @param firstFrameSeen set to true the first time we see a video frame after tune + * @return void. + */ + void setPlaybackSpeedState(bool live, double currentLatency, bool livepoint , double liveOffsetMs,int speed, double positionMs, bool firstFrameSeen = false); + + /** + * @fn Update tracking of video mute status and update watermarking requests as required, based on video presence, video mute state, and speed + * @param videoMuteStatus video mute state to be set + * @param seek_pos_seconds indicates the playback position at which most recent playback activity began + * @return void. + */ + void setVideoMute(bool live, double currentLatency, bool livepoint , double liveOffsetMs,bool videoMuteStatus, double positionMs); + /** + * @fn setSessionMgrState + * @param state session manager sate to be set + * @return void. + */ + void setSessionMgrState(SessionMgrState state); + + /** + * @fn getSessionMgrState + * @return session manager state. + */ + SessionMgrState getSessionMgrState(); + /** + * @fn getAccessToken + * + * @param[out] tokenLength - Gets updated with accessToken length. + * @return Pointer to accessToken. + * @note AccessToken memory is dynamically allocated, deallocation + * should be handled at the caller side. + */ + const char* getAccessToken(int &tokenLength, int &error_code ,bool bSslPeerVerify); + /** + * @fn getDrmSession + * @return index to the selected drmSessionContext which has been selected + */ + KeyState getDrmSession(int &err, DrmHelperPtr drmHelper, int &selectedSlot, DrmCallbacks* Instance, bool isPrimarySession = false ); + /** + * @fn getSlotIdForSession + * @return index to the session slot for selected drmSessionContext + */ + int getSlotIdForSession(DrmSession* session); + /** + * @fn releaseLicenseRenewalThreads + */ + void releaseLicenseRenewalThreads(); + /** + * @fn initializeDrmSession + */ + KeyState initializeDrmSession(DrmHelperPtr drmHelper, int sessionSlot, int &err ); + /** + * @fn notifyCleanup + */ + void notifyCleanup(); + + + /** + * @brief To update the max DRM sessions supported + * + * @param[in] maxSessions max DRM Sessions + */ + void UpdateMaxDRMSessions(int maxSessions); + + /* + *@brief Type definition for acquireLicense callback from application + */ + using LicenseCallback = std::function; + LicenseCallback AcquireLicenseCb; + /* + *@brief Registers acquireLicense callback from application + */ + void RegisterLicenseDataCb(const LicenseCallback Callback) { + AcquireLicenseCb = Callback; + }; + /* + * @brief Register Profile update callback to application + */ + using ProfileUpdateCallback = std::function; + ProfileUpdateCallback ProfileUpdateCb; + + void RegisterProfilingUpdateCb(const ProfileUpdateCallback callback) + { + ProfileUpdateCb = callback; + }; + using ProfileDecryptProfileCallback = std::function; + ProfileDecryptProfileCallback profileDecryptProfileCb; + void RegisterDecryptProfile(const ProfileDecryptProfileCallback callback) + { + profileDecryptProfileCb = callback; + }; + using LAProfileBeginCallback = std::function; + LAProfileBeginCallback laprofileBeginCb; + void RegisterLAProfBegin(const LAProfileBeginCallback callback) + { + laprofileBeginCb = callback; + }; + + using LAProfileEndCallback = std::function; + LAProfileEndCallback laprofileEndCb; + void RegisterLAProfEnd(const LAProfileEndCallback callback) + { + laprofileEndCb = callback; + }; + + using LAProfileErrorCallback = std::function; + LAProfileErrorCallback laprofileErrorCb; + void RegisterLAProfError(const LAProfileErrorCallback callback) + { + laprofileErrorCb = callback; + }; + + using SetFailureCallback = std::function; + SetFailureCallback setfailureCb; + void RegisterSetFailure(const SetFailureCallback callback) + { + setfailureCb = callback; + }; + + using DrmMetaDataCallback = std::function()>; + DrmMetaDataCallback DrmMetaDataCb; + void RegisterMetaDataCb(const DrmMetaDataCallback callback) + { + DrmMetaDataCb = callback; + } + /* + * @brief Register Content Protection Update callback to application + */ + using ContentUpdateCallback = std::function keyId, int contentProtectionUpd)>; + ContentUpdateCallback ContentUpdateCb; + void RegisterHandleContentProtectionCb(const ContentUpdateCallback callback) + { + ContentUpdateCb = callback; + }; + + /* + * @brief Custom data stores + */ + std::string mCustomData; + /** + * @brief Configuration parameters needed from Player + */ + void UpdateDRMConfig( + bool useSecManager, + bool enablePROutputProtection, + bool propagateURIParam, + bool isFakeTune, + bool wideVineKIDWorkaround); + + +}; + +/** + * @struct writeCallbackData + * @brief structure to hold DRM data to write + */ + +typedef struct writeCallbackData{ + DrmData *data ; + DrmSessionManager* mDrmSessionManager; +}writeCallbackData; + +#endif diff --git a/middleware/drm/DrmSystems.h b/middleware/drm/DrmSystems.h new file mode 100644 index 000000000..cfbf601ae --- /dev/null +++ b/middleware/drm/DrmSystems.h @@ -0,0 +1,47 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmSystems.h + * @brief Define DRM types + */ + +#ifndef DRMSYSTEMS_H +#define DRMSYSTEMS_H + +/** + * @enum DRMSystems + * @brief DRM system types + * @note these are now deprecated in favor of DrmHelpers, don't expand this + */ +enum DRMSystems +{ + eDRM_NONE, /**< No DRM */ + eDRM_WideVine, /**< Widevine, used to set legacy API */ + eDRM_PlayReady, /**< Playready, used to set legacy API */ + eDRM_CONSEC_agnostic, /**< CONSEC Agnostic DRM, deprecated */ + eDRM_Adobe_Access, /**< Adobe Access, fully deprecated */ + eDRM_Vanilla_AES, /**< Vanilla AES, fully deprecated */ + eDRM_ClearKey, /**< Clear key, used to set legacy API */ + eDRM_MAX_DRMSystems /**< Drm system count */ +}; + + +#endif /* DRMSYSTEMS_H */ + diff --git a/middleware/drm/DrmUtils.cpp b/middleware/drm/DrmUtils.cpp new file mode 100644 index 000000000..2bc12aeb8 --- /dev/null +++ b/middleware/drm/DrmUtils.cpp @@ -0,0 +1,170 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file DrmUtils.cpp + * @brief DataStructures and methods for DRM license acquisition + */ + +#include "DrmUtils.h" +#include "PlayerLogManager.h" + +#include +#include + +#define KEYID_TAG_START "" +#define KEYID_TAG_END "" + +/* Regex to detect if a string starts with a protocol definition e.g. http:// */ +static const std::string PROTOCOL_REGEX = "^[a-zA-Z0-9\\+\\.-]+://"; +//#define KEY_ID_SIZE_INDICATOR 0x12 + +/** + * @brief Default constructor for DrmData. + * NULL initialize data and dataLength. + */ +DrmData::DrmData() : data("") +{ +} + +/** + * @brief Constructor for DrmData + * allocate memory and initialize data and + * dataLength with given params. + * + */ +DrmData::DrmData(const char *dataPtr, size_t dataLength) : data("") +{ + data.assign(dataPtr,dataLength); +} + +/** + * @brief Distructor for DrmData. + * Free memory (if any) allocated for data. + */ +DrmData::~DrmData() +{ + if(!data.empty()) + { + data.clear(); + } +} + +/** + * @brief Getter method for data. + * + */ +const std::string &DrmData::getData() +{ + return data; +} + +/** + * @brief Getter method for dataLength. + */ +size_t DrmData::getDataLength() +{ + return data.length(); +} + +/** + * @brief Updates DrmData with given data. + */ +void DrmData::setData(const char *dataPtr, size_t dataLength) +{ + if(!data.empty()) + { + data.clear(); + } + data.assign(dataPtr,dataLength); +} + +/** + * @brief Appends DrmData with given data. + */ +void DrmData::addData(const char *dataPtr, size_t dataLength) +{ + if(data.empty()) + { + setData(dataPtr,dataLength); + } + else + { + std::string key; + key.assign(dataPtr,dataLength); + data += key; + } +} + +/** + * @brief Swap the bytes at given positions. + * + * @param[out] bytes - Pointer to byte block where swapping is done. + * @param[in] pos1, pos2 - Swap positions. + * @return void. + */ +static void swapBytes(unsigned char *bytes, int pos1, int pos2) +{ + unsigned char temp = bytes[pos1]; + bytes[pos1] = bytes[pos2]; + bytes[pos2] = temp; +} + +/** + * @brief Convert endianness of 16 byte block. + */ +void DrmUtils::convertEndianness(unsigned char *original, unsigned char *guidBytes) +{ + memcpy(guidBytes, original, 16); + swapBytes(guidBytes, 0, 3); + swapBytes(guidBytes, 1, 2); + swapBytes(guidBytes, 4, 5); + swapBytes(guidBytes, 6, 7); +} + +/** + * @brief Extract WideVine content meta data from DRM + * Agnostic PSSH header. Might not work with WideVine PSSH header + * + */ +std::string DrmUtils::extractWVContentMetadataFromPssh(const char* psshData, int dataLength) +{ + //WV PSSH format 4+4+4+16(system id)+4(data size) + uint32_t header = 28; + std::string metadata; + uint32_t content_id_size = + (uint32_t)((psshData[header] & 0x000000FFu) << 24 | + (psshData[header+1] & 0x000000FFu) << 16 | + (psshData[header+2] & 0x000000FFu) << 8 | + (psshData[header+3] & 0x000000FFu)); + + MW_LOG_INFO("content meta data length : %d",content_id_size); + if ((header + 4 + content_id_size) <= dataLength) + { + metadata = std::string(psshData + header + 4, content_id_size); + } + else + { + MW_LOG_WARN("psshData : %d bytes in length, metadata would read past end of buffer", dataLength); + } + + return metadata; +} +//End of special for Widevine diff --git a/middleware/drm/DrmUtils.h b/middleware/drm/DrmUtils.h new file mode 100644 index 000000000..b2f87fecc --- /dev/null +++ b/middleware/drm/DrmUtils.h @@ -0,0 +1,101 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** +* @file DrmUtils.h +* @brief Data structures to help with DRM sessions. +*/ + +#ifndef DrmUtils_h +#define DrmUtils_h + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DrmMediaFormat.h" +#include "DrmData.h" +#include "DrmInfo.h" +#include "DrmSystems.h" + +/** + * @brief Macros to track the value of API success or failure + */ +#define DRM_API_SUCCESS (0) +#define DRM_API_FAILED (-1) + +/** + * @brief start and end tags for DRM policy + */ +#define DRM_METADATA_TAG_START "" +#define DRM_METADATA_TAG_END "" + +typedef enum +{ + MW_DRM_INIT_FAILED, /**< DRM initialization failure */ + MW_DRM_DATA_BIND_FAILED, /**< InitData binding with DRM failed */ + MW_DRM_SESSIONID_EMPTY, /**< DRM session ID empty */ + MW_DRM_CHALLENGE_FAILED, /**< DRM key request challenge generation failed */ + MW_INVALID_DRM_KEY, /**< DRM reporting invalid license key */ + MW_CORRUPT_DRM_DATA, /**< DRM failure due to corrupt drm data, self heal might clear further errors*/ + MW_CORRUPT_DRM_METADATA, /**< DRM failure due to corrupt drm metadata in the stream*/ + MW_DRM_DECRYPT_FAILED, /**< DRM Decryption Failed for Fragments */ + MW_DRM_UNSUPPORTED, /**< DRM Format Unsupported */ + MW_DRM_SELF_ABORT, /**< Download activity is aborted by player */ + MW_DRM_KEY_UPDATE_FAILED, /**< Failed to process DRM key, see the error code returned from Update() for more info */ + MW_UNTRACKED_DRM_ERROR, + MW_FAILED_TO_GET_KEYID /**< Failed to parse key id from init data*/ +} DrmTuneFailure; +namespace DrmUtils +{ + /** + * @brief Convert endianness of 16 byte block. + * + * @param[in] original - Pointer to source byte block. + * @param[out] guidBytes - Pointer to destination byte block. + * @return void. + */ + void convertEndianness(unsigned char *original, unsigned char *guidBytes); + /** + * @fn extractDataFromPssh + * @param[in] psshData - Pointer to PSSH data. + * @param[in] dataLength - Length of PSSH data. + * @param[in] startStr, endStr - Pointer to delimiter strings. + * @param[in] verStr - Pointer to version delimiter string. + * @param[out] len - Gets updated with length of content meta data. + * @return Extracted data between delimiters; NULL if not found. + */ + unsigned char *extractDataFromPssh(const char* psshData, int dataLength, const char* startStr, const char* endStr, int *len, const char* verStr); + /** + * @fn extractWVContentMetadataFromPssh + * @param[in] psshData - Pointer to PSSH data. + * @param[in] dataLength - pssh data length + * @return Extracted ContentMetaData. + */ + std::string extractWVContentMetadataFromPssh(const char* psshData, int dataLength); + + unsigned char * extractKeyIdFromPssh(const char* psshData, int dataLength, int *len, DRMSystems drmSystem); +} +#endif diff --git a/middleware/drm/HlsDrmBase.h b/middleware/drm/HlsDrmBase.h new file mode 100644 index 000000000..87158b875 --- /dev/null +++ b/middleware/drm/HlsDrmBase.h @@ -0,0 +1,140 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file HlsDrmBase.h + * @brief Declaration common to various HLS DRM implementations + */ + + +#ifndef _DRM_HLSDRMBASE_H_ +#define _DRM_HLSDRMBASE_H_ +#include "PlayerUtils.h" +#include "DrmSession.h" + + +#define DECRYPT_WAIT_TIME_MS 3000 + +/** + * @enum ProfilerBucketType + * @brief Bucket types of profiler + */ +typedef enum +{ + DRM_PROFILE_BUCKET_DECRYPT_VIDEO, /**< Video decryption bucket*/ + DRM_PROFILE_BUCKET_DECRYPT_AUDIO, /**< Audio decryption bucket*/ + DRM_PROFILE_BUCKET_DECRYPT_SUBTITLE, /**< Subtitle decryption bucket*/ + DRM_PROFILE_BUCKET_DECRYPT_AUXILIARY, /**< Auxiliary decryption bucket*/ + + DRM_PROFILE_BUCKET_LA_TOTAL, /**< License acquisition total bucket*/ + DRM_PROFILE_BUCKET_LA_PREPROC, /**< License acquisition pre-processing bucket*/ + +} DrmProfilerBucketType; + +/** + * @enum DrmReturn + * @brief Return values of various functions + */ +enum DrmReturn +{ + eDRM_SUCCESS, /**< DRM is success */ + eDRM_ERROR, /**< DRM Failed */ + eDRM_KEY_ACQUISITION_TIMEOUT /**< DRM key acquisition timed out */ +}; + +/** + * @enum DRMState + * @brief States of DRM object + */ +enum DRMState +{ + eDRM_INITIALIZED, /**< DRM is initialized */ + eDRM_ACQUIRING_KEY, /**< DRM Acquiring key in progress */ + eDRM_KEY_ACQUIRED, /**< DRM key is Acquired */ + eDRM_KEY_FAILED, /**< DRM Acquiring key is failed */ + eDRM_KEY_FLUSH /**< DRM key is flushed */ +}; + +/** + * @class HlsDrmBase + * @brief Base class of HLS DRM implementations + */ +class HlsDrmBase +{ +public: + + /** + * @brief Set DRM specific meta-data + * + * @param metadata DRM specific metadata + * @retval 0 on success + */ + virtual DrmReturn SetMetaData(void* metadata,int trackType) = 0; + + /** + * @brief Set information required for decryption + * + * @param drmInfo Drm information + * @retval eDRM_SUCCESS on success + */ + virtual DrmReturn SetDecryptInfo(const struct DrmInfo *drmInfo, int acquireKeyWaitTime) = 0; + + + /** + * @brief Decrypts an encrypted buffer + * @param bucketType Type of bucket for profiling + * @param encryptedDataPtr pointer to encrypted payload + * @param encryptedDataLen length in bytes of data pointed to by encryptedDataPtr + * @param timeInMs wait time + * @retval eDRM_SUCCESS on success + */ + virtual DrmReturn Decrypt(int bucketType, void *encryptedDataPtr, size_t encryptedDataLen, int timeInMs = 3000) = 0; + + /** + * @brief Release drm session + */ + virtual void Release() = 0; + + /** + * @brief Cancel timed_wait operation drm_Decrypt + */ + virtual void CancelKeyWait() = 0; + + /** + * @brief Restore key state post cleanup of + * audio/video TrackState in case DRM data is persisted + */ + virtual void RestoreKeyState() = 0; + /** + * @brief AcquireKey Function to get DRM Key + * + */ + virtual void AcquireKey(void *metadata,int trackType) = 0; + /** + * @brief GetState Function to get current DRM state + * + */ + virtual DRMState GetState() = 0; + /** + * @brief HlsDrmBase Destructor + */ + virtual ~HlsDrmBase(){}; + +}; +#endif /* _DRM_HLSDRMBASE_H_ */ diff --git a/middleware/drm/HlsDrmSessionManager.cpp b/middleware/drm/HlsDrmSessionManager.cpp new file mode 100644 index 000000000..6a14a9e96 --- /dev/null +++ b/middleware/drm/HlsDrmSessionManager.cpp @@ -0,0 +1,62 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file HlsDrmSessionManager.cpp + * @brief Operations for HLS DRM + */ + +#include "HlsDrmSessionManager.h" +#include "DrmSessionManager.h" +#include "DrmHelper.h" +#include "HlsOcdmBridge.h" +using namespace std; + +/** + * @brief getInstance Get DRM instance + * Get an instance of the Hls DRM Session Manager + */ +HlsDrmSessionManager& HlsDrmSessionManager::getInstance() +{ + static HlsDrmSessionManager instance; + return instance; +} + +/** + * @brief Check stream is DRM supported + */ +bool HlsDrmSessionManager::isDrmSupported(const struct DrmInfo& drmInfo) const +{ + return DrmHelperEngine::getInstance().hasDRM(drmInfo); +} + +/** + * @brief createSession create session for DRM + */ +std::shared_ptr HlsDrmSessionManager::createSession(const struct DrmInfo& drmInfo, int streamTypeIn) +{ + DrmMediaType streamType = (DrmMediaType)streamTypeIn; + std::shared_ptr bridge = nullptr; + DrmHelperPtr drmHelper = DrmHelperEngine::getInstance().createHelper(drmInfo); + + this->GetHlsDrmSessionCb(bridge, drmHelper ,mDrmSession, streamType); + + return bridge; +} diff --git a/middleware/drm/HlsDrmSessionManager.h b/middleware/drm/HlsDrmSessionManager.h new file mode 100644 index 000000000..edeab0805 --- /dev/null +++ b/middleware/drm/HlsDrmSessionManager.h @@ -0,0 +1,62 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _HLS_DRM_SESSION_MGR_H +#define _HLS_DRM_SESSION_MGR_H + +/** + * @file HlsDrmSessionManager.h + * @brief Operations for HLS DRM + */ + + +#include "PlayerHlsDrmSessionInterfaceBase.h" + +/** + * @class HlsDrmSessionManager + * @brief DRM Session manager for HLS stream operations + */ + +class HlsDrmSessionManager : public PlayerHlsDrmSessionInterfaceBase +{ + DrmSession* mDrmSession; +public: + /** + * @fn getInstance + * @return Hls Drm Session Manager instance + */ + static HlsDrmSessionManager& getInstance(); + + /** + * @fn isDrmSupported + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM support available, false otherwise + */ + bool isDrmSupported(const struct DrmInfo& drmInfo) const override; + + /** + * @fn createSession + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM support available, false otherwise + */ + std::shared_ptr createSession( const struct DrmInfo& drmInfo, int streamType) override; + +}; + +#endif //_HLS_DRM_SESSION_MGR_H diff --git a/middleware/drm/HlsOcdmBridge.cpp b/middleware/drm/HlsOcdmBridge.cpp new file mode 100644 index 000000000..07dbecc60 --- /dev/null +++ b/middleware/drm/HlsOcdmBridge.cpp @@ -0,0 +1,102 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file HlsOcdmBridge.cpp + * @brief Handles OCDM bridge to validate DRM License + */ + +#include "HlsOcdmBridge.h" + +#include "PlayerLogManager.h" +#define DRM_IV_LEN 16 + + +HlsOcdmBridge::HlsOcdmBridge(DrmSession * drmSession) : + m_drmInfo(nullptr), + m_drmSession(drmSession), + m_drmState(eDRM_INITIALIZED), + m_Mutex() +{ +} + + +HlsOcdmBridge::~HlsOcdmBridge() +{ +} + + +DrmReturn HlsOcdmBridge::SetDecryptInfo( const struct DrmInfo *drmInfo, int acquireKeyWaitTime) +{ + DrmReturn result = eDRM_ERROR; + + std::lock_guard guard(m_Mutex); + m_drmInfo = drmInfo; + KeyState eKeyState = m_drmSession->getState(); + if (eKeyState == KEY_READY) + { + m_drmState = eDRM_KEY_ACQUIRED; + result = eDRM_SUCCESS; //frag_collector ignores the return + } + MW_LOG_TRACE("DecryptInfo Set"); + + return result; +} + + +DrmReturn HlsOcdmBridge::Decrypt( int bucketTypeIn, void *encryptedDataPtr, size_t encryptedDataLen,int timeInMs) +{ + + DrmReturn result = eDRM_ERROR; + + std::lock_guard guard(m_Mutex); + if (m_drmState == eDRM_KEY_ACQUIRED) + { + MW_LOG_TRACE("Starting decrypt"); + int retVal = m_drmSession->decrypt(m_drmInfo->iv, DRM_IV_LEN, (const uint8_t *)encryptedDataPtr , (uint32_t)encryptedDataLen, NULL); + if (retVal) + { + MW_LOG_WARN("Decrypt failed err = %d", retVal); + } + else + { + MW_LOG_TRACE("Decrypt success"); + result = eDRM_SUCCESS; + } + } + else + { + MW_LOG_WARN("Decrypt Called in Incorrect State! DrmState = %d", (int)m_drmState); + } + return result; +} + + +void HlsOcdmBridge::Release(void) +{ + MW_LOG_WARN("Releasing the Opencdm Session"); + m_drmSession->clearDecryptContext(); +} + + +void HlsOcdmBridge::CancelKeyWait(void) +{ + //TBD:Unimplemented +} + diff --git a/middleware/drm/HlsOcdmBridge.h b/middleware/drm/HlsOcdmBridge.h new file mode 100644 index 000000000..3792a5777 --- /dev/null +++ b/middleware/drm/HlsOcdmBridge.h @@ -0,0 +1,71 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _HLS_OCDM_BRIDGE_H_ +#define _HLS_OCDM_BRIDGE_H_ + +/** + * @file HlsOcdmBridge.h + * @brief Handles OCDM bridge to validate DRM License + */ + +#include "HlsDrmBase.h" + +/** + * @class HlsOcdmBridge + * @brief OCDM bridge to handle DRM key + */ + +class HlsOcdmBridge : public HlsDrmBase +{ + DRMState m_drmState; + + const DrmInfo* m_drmInfo; + DrmSession* m_drmSession; + std::mutex m_Mutex; +public: + HlsOcdmBridge(DrmSession * DrmSession); + + ~HlsOcdmBridge(); + + HlsOcdmBridge(const HlsOcdmBridge&) = delete; + + HlsOcdmBridge& operator=(const HlsOcdmBridge&) = delete; + + /*HlsDrmBase Methods*/ + + virtual DrmReturn SetMetaData(void* metadata,int trackType) override {return eDRM_SUCCESS;}; + + virtual DrmReturn SetDecryptInfo(const struct DrmInfo *drmInfo, int acquireKeyWaitTime) override; + + virtual DrmReturn Decrypt(int bucketType, void *encryptedDataPtr, size_t encryptedDataLen, int timeInMs = DECRYPT_WAIT_TIME_MS) override; + + virtual void Release() override; + + virtual void CancelKeyWait() override; + + virtual void RestoreKeyState() override {}; + + virtual void AcquireKey(void *metadata,int trackType) override {}; + + virtual DRMState GetState() override {return m_drmState;} + +}; + +#endif // _HLS_OCDM_BRIDGE_H_ diff --git a/middleware/drm/HlsOcdmBridgeInterface.cpp b/middleware/drm/HlsOcdmBridgeInterface.cpp new file mode 100644 index 000000000..9b8e86988 --- /dev/null +++ b/middleware/drm/HlsOcdmBridgeInterface.cpp @@ -0,0 +1,41 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file HlsOcdmBridgeInterface.cpp + * @brief Handles OCDM bridge interface to validate DRM License + */ + +#include "HlsOcdmBridgeInterface.h" + +#ifdef USE_OPENCDM_ADAPTER +#include "HlsOcdmBridge.h" +#endif + + +HlsDrmBase* HlsOcdmBridgeInterface::GetBridge(DrmSession * playerDrmSession) +{ + +#ifdef USE_OPENCDM_ADAPTER + return new HlsOcdmBridge(playerDrmSession); +#else + return new FakeHlsOcdmBridge(playerDrmSession); +#endif + +} diff --git a/middleware/drm/HlsOcdmBridgeInterface.h b/middleware/drm/HlsOcdmBridgeInterface.h new file mode 100644 index 000000000..3f6c34a43 --- /dev/null +++ b/middleware/drm/HlsOcdmBridgeInterface.h @@ -0,0 +1,77 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _HLS_OCDM_BRIDGE_INTERFACE_H_ +#define _HLS_OCDM_BRIDGE_INTERFACE_H_ + +/** + * @file HlsOcdmBridgeInterface.h + * @brief Handles OCDM bridge interface to validate DRM License + */ + +#include "HlsDrmBase.h" + +/** + * @class FakeHlsOcdmBridge + * @brief Fake OCDM bridge to handle DRM key + */ + +class FakeHlsOcdmBridge : public HlsDrmBase +{ +public: + FakeHlsOcdmBridge(DrmSession * DrmSession){} + + virtual ~FakeHlsOcdmBridge(){} + + FakeHlsOcdmBridge(const FakeHlsOcdmBridge&) = delete; + + FakeHlsOcdmBridge& operator=(const FakeHlsOcdmBridge&) = delete; + + /*HlsDrmBase Methods*/ + + virtual DrmReturn SetMetaData(void* metadata,int trackType) override {return DrmReturn::eDRM_ERROR;} + + virtual DrmReturn SetDecryptInfo(const struct DrmInfo *drmInfo, int acquireKeyWaitTime) override {return DrmReturn::eDRM_ERROR;} + + virtual DrmReturn Decrypt(int bucketType, void *encryptedDataPtr, size_t encryptedDataLen, int timeInMs = DECRYPT_WAIT_TIME_MS) override {return DrmReturn::eDRM_ERROR;} + + virtual void Release() override {} + + virtual void CancelKeyWait() override {} + + virtual void RestoreKeyState() override {} + + virtual void AcquireKey(void *metadata,int trackType) override {} + + virtual DRMState GetState() override {return DRMState::eDRM_KEY_FAILED;} + +}; + +class HlsOcdmBridgeInterface +{ + +public: + + static HlsDrmBase* GetBridge(DrmSession * drmSession); + +}; + + + +#endif // _HLS_OCDM_BRIDGE_INTERFACE_H_ diff --git a/middleware/drm/PlayerHlsDrmSessionInterface.cpp b/middleware/drm/PlayerHlsDrmSessionInterface.cpp new file mode 100644 index 000000000..4d42c0b24 --- /dev/null +++ b/middleware/drm/PlayerHlsDrmSessionInterface.cpp @@ -0,0 +1,80 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file PlayerHlsDrmSessionInterface.cpp + * @brief Interface for HLS DRM + */ + +#include "PlayerHlsDrmSessionInterface.h" +#ifdef USE_OPENCDM_ADAPTER +#include "HlsDrmSessionManager.h" +#endif + +PlayerHlsDrmSessionInterface* pInstance = nullptr; + +/** + * @brief Constructor + */ +PlayerHlsDrmSessionInterface::PlayerHlsDrmSessionInterface() +{ +#ifdef USE_OPENCDM_ADAPTER + m_pHlsDrmSessionManager = &HlsDrmSessionManager::getInstance(); +#else + m_pHlsDrmSessionManager = new FakeHlsDrmSessionManager(); +#endif +} + +/** + * @brief getInstance Get DRM instance + * Get an instance of the Hls DRM Session Manager + */ +PlayerHlsDrmSessionInterface* PlayerHlsDrmSessionInterface::getInstance() +{ + if(pInstance == nullptr) + { + pInstance = new PlayerHlsDrmSessionInterface(); + } + return pInstance; +} + +/** + * @brief Check stream is DRM supported + */ +bool PlayerHlsDrmSessionInterface::isDrmSupported(const struct DrmInfo& drmInfo) const +{ + return m_pHlsDrmSessionManager->isDrmSupported(drmInfo); +} + +/** + * @brief createSession create session for DRM + */ +std::shared_ptr PlayerHlsDrmSessionInterface::createSession(const struct DrmInfo& drmInfo, int streamTypeIn) +{ + return m_pHlsDrmSessionManager->createSession(drmInfo, streamTypeIn); +} + +/** + * @brief Registers GetAccessKey callback from application + */ +void PlayerHlsDrmSessionInterface::RegisterGetHlsDrmSessionCb(const GetHlsDrmSessionCallback Callback) +{ + return m_pHlsDrmSessionManager->RegisterGetHlsDrmSessionCb(Callback); +} diff --git a/middleware/drm/PlayerHlsDrmSessionInterface.h b/middleware/drm/PlayerHlsDrmSessionInterface.h new file mode 100644 index 000000000..1156bf819 --- /dev/null +++ b/middleware/drm/PlayerHlsDrmSessionInterface.h @@ -0,0 +1,96 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _PLAYER_HLS_DRM_SESSION_INTERFACE_H +#define _PLAYER_HLS_DRM_SESSION_INTERFACE_H + +/** + * @file PlayerHlsDrmSessionInterface.h + * @brief Interface for HLS DRM + */ + + +#include "PlayerHlsDrmSessionInterfaceBase.h" + + +/** + * @class FakeHlsDrmSessionManager + * @brief DRM Session manager for HLS stream operations + */ + +class FakeHlsDrmSessionManager : public PlayerHlsDrmSessionInterfaceBase +{ + +public: + /** + * @fn isDrmSupported + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM support available, false otherwise + */ + bool isDrmSupported(const struct DrmInfo& drmInfo) const override {return false;} + + /** + * @fn createSession + * @param drmInfo DrmInfo built by the HLS manifest parser + * @param streamType streamType + * @return true if a DRM support available, false otherwise + */ + std::shared_ptr createSession( const struct DrmInfo& drmInfo, int streamType) override {return nullptr;} + +}; + +class PlayerHlsDrmSessionInterface +{ + +private: + + PlayerHlsDrmSessionInterfaceBase* m_pHlsDrmSessionManager; + + PlayerHlsDrmSessionInterface(); + +public: + + /** + * @fn getInstance + * @return Player Hls Drm Session Interface instance + */ + static PlayerHlsDrmSessionInterface* getInstance(); + /** + * @fn isDrmSupported + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM support available, false otherwise + */ + bool isDrmSupported(const struct DrmInfo& drmInfo) const; + + /** + * @fn createSession + * @param drmInfo DrmInfo built by the HLS manifest parser + * @param streamType streamType + * @return true if a DRM support available, false otherwise + */ + std::shared_ptr createSession( const struct DrmInfo& drmInfo, int streamType); + /** ProfileUpdate callback for initiating the curl init from application */ + + /** + *@brief Registers GetAccessKey callback from application + */ + void RegisterGetHlsDrmSessionCb(const GetHlsDrmSessionCallback Callback); +}; + +#endif //_PLAYER_HLS_DRM_SESSION_INTERFACE_H diff --git a/middleware/drm/PlayerHlsDrmSessionInterfaceBase.h b/middleware/drm/PlayerHlsDrmSessionInterfaceBase.h new file mode 100644 index 000000000..24d7fe830 --- /dev/null +++ b/middleware/drm/PlayerHlsDrmSessionInterfaceBase.h @@ -0,0 +1,81 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _PLAYER_HLS_DRM_SESSION_INTERFACE_BASE_H +#define _PLAYER_HLS_DRM_SESSION_INTERFACE_BASE_H + +/** + * @file PlayerHlsDrmSessionManagerBase.h + * @brief Operations for HLS DRM, base class + */ + + +#include "HlsDrmBase.h" +#include "DrmSession.h" +#include +#include "DrmHelper.h" + +enum DrmMediaType +{ // renamed from "MediaType" to avoid namespace collision with OpenCDM definition + eDRM_MEDIATYPE_VIDEO, /**< Type video */ + eDRM_MEDIATYPE_AUDIO, /**< Type audio */ + eDRM_MEDIATYPE_SUBTITLE, /**< Type subtitle */ + eDRM_MEDIATYPE_AUX_AUDIO, /**< Type auxiliary audio */ + eDRM_MEDIATYPE_DEFAULT /**< Type unknown */ +}; + +using GetHlsDrmSessionCallback = std::function&bridge, std::shared_ptr &drmHelper , DrmSession* &session , int streamType)>; + +/** + * @class PlayerHlsDrmSessionInterfaceBase + * @brief DRM Session Interface Base class for HLS stream operations + */ + +class PlayerHlsDrmSessionInterfaceBase +{ +protected: + DrmSession* mDrmSession; +public: + /** + * @fn isDrmSupported + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM support available, false otherwise + */ + virtual bool isDrmSupported(const struct DrmInfo& drmInfo) const {return false;} + + /** + * @fn createSession + * @param drmInfo DrmInfo built by the HLS manifest parser + * @param streamType streamType + * @return true if a DRM support available, false otherwise + */ + virtual std::shared_ptr createSession( const struct DrmInfo& drmInfo, int streamType){return nullptr;} + + /** ProfileUpdate callback for initiating the curl init from application */ + GetHlsDrmSessionCallback GetHlsDrmSessionCb; + + /** + *@brief Registers GetAccessKey callback from application + */ + void RegisterGetHlsDrmSessionCb(const GetHlsDrmSessionCallback Callback){ + GetHlsDrmSessionCb = Callback; + }; +}; + +#endif //_PLAYER_HLS_DRM_SESSION_INTERFACE_BASE_H diff --git a/middleware/drm/aes/Aes.cpp b/middleware/drm/aes/Aes.cpp new file mode 100755 index 000000000..ef3517ee8 --- /dev/null +++ b/middleware/drm/aes/Aes.cpp @@ -0,0 +1,387 @@ +/* + * + * + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file Aes.cpp + * @brief HLS AES drm decryptor + */ + + +#include "Aes.h" +#include "cstring" +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#define OPEN_SSL_CONTEXT mOpensslCtx +#else +#define OPEN_SSL_CONTEXT &mOpensslCtx +#endif +#define AES_128_KEY_LEN_BYTES 16 + +static std::mutex instanceLock; + +/** + * @brief key acquisition thread + * @retval NULL + */ +void AesDec::acquire_key() +{ + AcquireKey(); + return; +} + +/** + * @brief Notify drm error + */ +void AesDec::NotifyDRMError(DrmTuneFailure drmFailure) +{ + this->NotifyDrmErrorCb((int)drmFailure); + SignalDrmError(); + MW_LOG_ERR("AesDec::NotifyDRMError: drmState:%d", mDrmState ); +} + + +/** + * @brief Signal drm error + */ +void AesDec::SignalDrmError() +{ + std::unique_lock lock(mMutex); + mDrmState = eDRM_KEY_FAILED; + mCond.notify_all(); +} + +/** + * @brief Signal key acquired event + */ +void AesDec::SignalKeyAcquired() +{ + MW_LOG_WARN("AesDRMListener drmState:%d moving to KeyAcquired", mDrmState); + { + std::unique_lock lock(mMutex); + mDrmState = eDRM_KEY_ACQUIRED; + mCond.notify_all(); + } + this->ProfileUpdateDrmDecrypt(1, DRM_PROFILE_BUCKET_LA_TOTAL); +} + +/** + * @brief Acquire drm key from URI + */ +void AesDec::AcquireKey() +{ + std::string tempEffectiveUrl; + std::string keyURI; + int http_error = 0; //CID:88814 - Initialization + double downloadTime = 0.0; + bool keyAcquisitionStatus = false; + DrmTuneFailure failureReasonIn = MW_UNTRACKED_DRM_ERROR; + + if (drm_pthread_setname(pthread_self(), "aesDRM")) + { + MW_LOG_ERR("pthread_setname_np failed"); + } + + ResolveURL(keyURI, mDrmInfo.manifestURL, mDrmInfo.keyURI.c_str(), mDrmInfo.bPropagateUriParams); + MW_LOG_WARN("Key acquisition start uri = %s", keyURI.c_str()); + int failureReason = (int)failureReasonIn; + + char* ptr = NULL; + this->GetAccessKeyCb(keyURI, tempEffectiveUrl, http_error, downloadTime, mCurlInstance, keyAcquisitionStatus, failureReason, &ptr); + m_ptr = ( char*)ptr; + if(keyAcquisitionStatus) + { + SignalKeyAcquired(); + } + else + { + NotifyDRMError((DrmTuneFailure)failureReason); + } +} + +/** + * @brief Set DRM meta-data. Stub implementation + * + */ +DrmReturn AesDec::SetMetaData(void* metadata,int trackType) +{ + return eDRM_SUCCESS; +} + +/** + * @brief AcquireKey Function to acquire key . Stub implementation + */ +void AesDec::AcquireKey(void *metadata,int trackType) +{ + +} + +/** + * @brief GetState Function to get current DRM State + * + */ +DRMState AesDec::GetState() +{ + return mDrmState; +} + +/** + * @brief Set information required for decryption + * + */ +DrmReturn AesDec::SetDecryptInfo(const struct DrmInfo *drmInfo, int acquireKeyWaitTime) +{ + DrmReturn err = eDRM_ERROR; + std::unique_lock lock(mMutex); + + mAcquireKeyWaitTime = acquireKeyWaitTime; + if (mDrmState == eDRM_ACQUIRING_KEY) + { + MW_LOG_WARN("AesDec:: acquiring key in progress"); + WaitForKeyAcquireCompleteUnlocked(mAcquireKeyWaitTime, err, lock ); + } + mDrmInfo = *drmInfo; + + if (!mDrmUrl.empty()) + { + if ((eDRM_KEY_ACQUIRED == mDrmState) && (drmInfo->keyURI == mDrmUrl)) + { + MW_LOG_TRACE("AesDec: same url:%s - not acquiring key", mDrmUrl.c_str()); + return eDRM_SUCCESS; + } + } + mDrmUrl = drmInfo->keyURI; + mDrmState = eDRM_ACQUIRING_KEY; + mPrevDrmState = eDRM_INITIALIZED; + this->GetCurlInitCb(mCurlInstance); + + if (licenseAcquisitionThreadStarted) + { + licenseAcquisitionThreadId.join(); + licenseAcquisitionThreadStarted = false; + } + + try + { + licenseAcquisitionThreadId = std::thread(&AesDec::acquire_key, this); + err = eDRM_SUCCESS; + licenseAcquisitionThreadStarted = true; +//TODO MW_LOG_INFO("Thread created for acquire_key [%zx]", GetPrintableThreadID(licenseAcquisitionThreadId)); + } + catch(const std::exception& e) + { + MW_LOG_ERR("AesDec:: thread create failed for acquire_key : %s", e.what()); + mDrmState = eDRM_KEY_FAILED; + licenseAcquisitionThreadStarted = false; + } + MW_LOG_INFO("AesDec: drmState:%d ", mDrmState); + return err; +} + +/** + * @brief Wait for key acquisition completion + */ +void AesDec::WaitForKeyAcquireCompleteUnlocked(int timeInMs, DrmReturn &err, std::unique_lock& lock ) +{ + MW_LOG_INFO( "waiting for key acquisition to complete,wait time:%d",timeInMs ); + if( std::cv_status::timeout == mCond.wait_for(lock, std::chrono::milliseconds(timeInMs)) ) // block until drm ready + { + MW_LOG_WARN("AesDec:: wait for key acquisition timed out"); + err = eDRM_KEY_ACQUISITION_TIMEOUT; + } +} + +/** + * @brief Decrypts an encrypted buffer + */ +DrmReturn AesDec::Decrypt( int bucketTypeIn, void *encryptedDataPtr, size_t encryptedDataLen,int timeInMs) +{ + DrmProfilerBucketType bucketType = (DrmProfilerBucketType)bucketTypeIn; + DrmReturn err = eDRM_ERROR; + + std::unique_lock lock(mMutex); + if (mDrmState == eDRM_ACQUIRING_KEY) + { + WaitForKeyAcquireCompleteUnlocked(timeInMs, err, lock); + } + if (mDrmState == eDRM_KEY_ACQUIRED) + { + MW_LOG_INFO("AesDec: Starting decrypt"); + unsigned char *decryptedDataBuf = (unsigned char *)malloc(encryptedDataLen); + int decryptedDataLen = 0; + if (decryptedDataBuf) + { + int decLen = (int)encryptedDataLen; + memset(decryptedDataBuf, 0, encryptedDataLen); + + this->ProfileUpdateDrmDecrypt(0, bucketType); + if(!EVP_DecryptInit_ex(OPEN_SSL_CONTEXT, EVP_aes_128_cbc(), NULL, (unsigned char*)m_ptr, mDrmInfo.iv)) + { + MW_LOG_ERR( "AesDec::EVP_DecryptInit_ex failed mDrmState = %d",(int)mDrmState); + } + else + { + if (!EVP_DecryptUpdate(OPEN_SSL_CONTEXT, decryptedDataBuf, &decLen, (const unsigned char*)encryptedDataPtr, (int)encryptedDataLen)) + { + MW_LOG_ERR("AesDec::EVP_DecryptUpdate failed mDrmState = %d",(int) mDrmState); + } + else + { + decryptedDataLen = decLen; + decLen = 0; + MW_LOG_INFO("AesDec: EVP_DecryptUpdate success decryptedDataLen = %d encryptedDataLen %d", (int) decryptedDataLen, (int)encryptedDataLen); + if (!EVP_DecryptFinal_ex(OPEN_SSL_CONTEXT, decryptedDataBuf + decryptedDataLen, &decLen)) + { + MW_LOG_ERR("AesDec::EVP_DecryptFinal_ex failed mDrmState = %d", + (int) mDrmState); + } + else + { + decryptedDataLen += decLen; + MW_LOG_INFO("AesDec: decrypt success"); + err = eDRM_SUCCESS; + } + } + } + this->ProfileUpdateDrmDecrypt(1, bucketType); + memcpy(encryptedDataPtr, decryptedDataBuf, encryptedDataLen); + free(decryptedDataBuf); + (void)decryptedDataLen; // Avoid a warning as this is only used in a log. + } + } + else + { + MW_LOG_ERR( "AesDec::key acquisition failure! mDrmState = %d",(int)mDrmState); + } + return err; +} + + +/** + * @brief Release drm session + */ +void AesDec::Release() +{ + DrmReturn err = eDRM_ERROR; + std::unique_lock lock(mMutex); + //We wait for license acquisition to complete. Once license acquisition is complete + //the appropriate state will be set to mDrmState and hence RestoreKeyState will be a no-op. + if ( ( mDrmState == eDRM_ACQUIRING_KEY || mPrevDrmState == eDRM_ACQUIRING_KEY ) && mDrmState != eDRM_KEY_FAILED ) + { + WaitForKeyAcquireCompleteUnlocked(mAcquireKeyWaitTime, err, lock ); + } + if (licenseAcquisitionThreadStarted) + { + licenseAcquisitionThreadId.join(); + licenseAcquisitionThreadStarted = false; + } + mCond.notify_all(); + if (-1 != mCurlInstance) + { + this->TerminateCurlInstanceCb(mCurlInstance); + + mCurlInstance = -1; + } +} + +/** + * @brief Cancel timed_wait operation drm_Decrypt + * + */ +void AesDec::CancelKeyWait() +{ + std::lock_guard guard(mMutex); + //save the current state in case required to restore later. + if (mDrmState != eDRM_KEY_FLUSH) + { + mPrevDrmState = mDrmState; + } + //required for demuxed assets where the other track might be waiting on mMutex lock. + mDrmState = eDRM_KEY_FLUSH; + mCond.notify_all(); +} + +/** + * @brief Restore key state post cleanup of + * audio/video TrackState in case DRM data is persisted + */ +void AesDec::RestoreKeyState() +{ + std::lock_guard guard(mMutex); + //In case somebody overwritten mDrmState before restore operation, keep that state + if (mDrmState == eDRM_KEY_FLUSH) + { + mDrmState = mPrevDrmState; + } +} + +std::shared_ptr AesDec::mInstance = nullptr; + +/** + * @brief Get singleton instance + */ +std::shared_ptr AesDec::GetInstance() +{ + std::lock_guard guard(instanceLock); + if (nullptr == mInstance) + { + mInstance = std::make_shared(); + } + return mInstance; +} + +/** + * @brief AesDec Constructor + * + */ +AesDec::AesDec() : mDrmState(eDRM_INITIALIZED), + mPrevDrmState(eDRM_INITIALIZED), mDrmUrl(""), + mCond(), mMutex(), mOpensslCtx(), + mDrmInfo(), mCurlInstance(-1), + licenseAcquisitionThreadId(), + licenseAcquisitionThreadStarted(false), + mAcquireKeyWaitTime(MAX_LICENSE_ACQ_WAIT_TIME) +{ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + OPEN_SSL_CONTEXT = EVP_CIPHER_CTX_new(); +#else + EVP_CIPHER_CTX_init(OPEN_SSL_CONTEXT); +#endif +} + + +/** + * @brief AesDec Destructor + */ +AesDec::~AesDec() +{ + CancelKeyWait(); + Release(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_CIPHER_CTX_free(OPEN_SSL_CONTEXT); +#else + EVP_CIPHER_CTX_cleanup(OPEN_SSL_CONTEXT); +#endif +} diff --git a/middleware/drm/aes/Aes.h b/middleware/drm/aes/Aes.h new file mode 100644 index 000000000..c9473c795 --- /dev/null +++ b/middleware/drm/aes/Aes.h @@ -0,0 +1,224 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _AES_H_ +#define _AES_H_ + +/** + * @file Aes.h + * @brief HLS AES drm decryptor + */ + +#include +#include "HlsDrmBase.h" +#include +#include +#include +#include +#include +#include + +#define MAX_LICENSE_ACQ_WAIT_TIME 12000 /**< 12 secs Increase from 10 to 12 sec */ + +#ifdef __APPLE__ +#define drm_pthread_setname(tid,name) pthread_setname_np(name) +#else +#define drm_pthread_setname(tid,name) pthread_setname_np(tid,name) +#endif +/** + * @class AesDec + * @brief Vanilla AES based DRM management + */ +class AesDec : public HlsDrmBase +{ +public: + /** + * @fn GetInstance + */ + static std::shared_ptr GetInstance(); + /** + * @fn SetMetaData + * + * @param metadata - Ignored + * + * @retval eDRM_SUCCESS + */ + DrmReturn SetMetaData( void* metadata,int trackType); + /** + * @fn GetState + * @retval DRMState + */ + DRMState GetState(); + /** + * @fn AcquireKey + * + * @param[in] metadata Ignored + * + * @retval None + */ + void AcquireKey(void *metadata,int trackType); + /** + * @fn SetDecryptInfo + * + * @param drmInfo Drm information + * @retval eDRM_SUCCESS on success + */ + DrmReturn SetDecryptInfo( const struct DrmInfo *drmInfo , int acquireKeyWaitTime); + /** + * @fn Decrypt + * @param bucketType Type of bucket for profiling + * @param encryptedDataPtr pointer to encrypted payload + * @param encryptedDataLen length in bytes of data pointed to by encryptedDataPtr + * @param timeInMs wait time + */ + /** + * @fn SetIV + * + * @param iv New address of initialization vector to use in Decrypt (because received new #EXT-X-KEY with IV) + * @retval eDRM_SUCCESS on success + */ + DrmReturn SetIV(unsigned char* iv); + DrmReturn Decrypt(int bucketType, void *encryptedDataPtr, size_t encryptedDataLen, int timeInMs); + /** + * @fn Release + */ + void Release(); + /** + * @fn CancelKeyWait + * + */ + void CancelKeyWait(); + /** + * @fn RestoreKeyState + */ + void RestoreKeyState(); + + /*Functions to support internal operations*/ + /** + * @brief key acquisition thread + * @retval NULL + */ + void acquire_key(); + /** + * @brief Acquire drm key from URI + */ + void AcquireKey(); + /** + * @fn SignalKeyAcquired + */ + void SignalKeyAcquired(); + /** + * @fn NotifyDRMError + * @param drmFailure drm error type + */ + void NotifyDRMError(DrmTuneFailure drmFailure); + /** + * @fn SignalDrmError + */ + void SignalDrmError(); + /** + * @fn WaitForKeyAcquireCompleteUnlocked + * @param[in] timeInMs timeout + * @param[out] err error on failure + */ + void WaitForKeyAcquireCompleteUnlocked(int timeInMs, DrmReturn &err, std::unique_lock& lock ); + /** + * @fn AesDec + * + */ + AesDec(); + /** + * @fn ~AesDec + */ + ~AesDec(); + AesDec(const AesDec&) = delete; + AesDec& operator=(const AesDec&) = delete; + + /* + *@brief Type definition for acquireLicense callback from application + */ + using NotifyCallback = std::function; + NotifyCallback NotifyDrmErrorCb; + /* + *@brief Registers acquireLicense callback from application + */ + void RegisterNotifyDrmErrorCb(const NotifyCallback Callback) { + NotifyDrmErrorCb = Callback; + }; + using TerminateCurlInstanceCallback = std::function; + TerminateCurlInstanceCallback TerminateCurlInstanceCb; + /* + *@brief Registers acquireLicense callback from application + */ + void RegisterTerminateCurlInstanceCb(const TerminateCurlInstanceCallback Callback) { + TerminateCurlInstanceCb = Callback; + }; + /** ProfileUpdate callback for updating the profile bucket type to application */ + using ProfileUpdateCallback = std::function; + ProfileUpdateCallback ProfileUpdateDrmDecrypt; + + /* + *@brief Registers ProfileUpdate callback from application + */ + void RegisterProfileUpdateCb(const ProfileUpdateCallback Callback){ + ProfileUpdateDrmDecrypt = Callback; + }; + + /** ProfileUpdate callback for getting the access key from application */ + using GetAccessKeyCallback = std::function; + GetAccessKeyCallback GetAccessKeyCb; + + /* + *@brief Registers GetAccessKey callback from application + */ + void RegisterGetAccessKeyCb(const GetAccessKeyCallback Callback){ + GetAccessKeyCb = Callback; + }; + /** ProfileUpdate callback for initiating the curl init from application */ + using GetCurlInitCallback = std::function; + GetCurlInitCallback GetCurlInitCb; + + /* + *@brief Registers GetAccessKey callback from application + */ + void RegisterGetCurlInitCb(const GetCurlInitCallback Callback){ + GetCurlInitCb = Callback; + }; +private: + + static std::shared_ptr mInstance; + std::condition_variable mCond; + std::mutex mMutex; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_CIPHER_CTX *mOpensslCtx; +#else + EVP_CIPHER_CTX mOpensslCtx; +#endif + DrmInfo mDrmInfo ; + char *m_ptr; + DRMState mDrmState; + DRMState mPrevDrmState; + std::string mDrmUrl; + int mCurlInstance; + int mAcquireKeyWaitTime; + std::thread licenseAcquisitionThreadId; + bool licenseAcquisitionThreadStarted; +}; + +#endif // _AES_H_ diff --git a/middleware/drm/helper/ClearKeyHelper.cpp b/middleware/drm/helper/ClearKeyHelper.cpp new file mode 100644 index 000000000..37c02c7e1 --- /dev/null +++ b/middleware/drm/helper/ClearKeyHelper.cpp @@ -0,0 +1,263 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file ClearKeyHelper.cpp + * @brief Helper functions for Clear key + */ + +#include "ClearKeyHelper.h" +#include "DrmJsonObject.h" + +#include "DrmConstants.h" +#include "PlayerLogManager.h" +/** + * @brief parse leading protocol from uri if present + * @param[in] uri manifest/ fragment uri + * @retval return pointer just past protocol (i.e. http://) if present (or) return NULL uri doesn't start with protcol + */ +static const char * ParseUriProtocol(const char *uri) +{ + if(NULL == uri) + { + MW_LOG_ERR("Empty URI"); + return NULL; + } + for(;;) + { + char ch = *uri++; + if( ch ==':' ) + { + if (uri[0] == '/' && uri[1] == '/') + { + return uri + 2; + } + break; + } + else if (isalnum (ch) || ch == '.' || ch == '-' || ch == '+') // other valid (if unlikely) characters for protocol + { // legal characters for uri protocol - continue + continue; + } + else + { + break; + } + } + return NULL; +} + +/** + * @brief Resolve file URL from the base and file path + */ +void resolveURL(std::string& dst, std::string base, const char *uri , bool bPropagateUriParams) +{ + if( ParseUriProtocol(uri) ) + { + dst = uri; + } + else + { + if(base.empty()) + { + MW_LOG_WARN("Empty base"); + return; + } + const char *baseStart = base.c_str(); + const char *basePtr = ParseUriProtocol(baseStart); + const char *baseEnd; + for(;;) + { + char c = *basePtr; + if( c==0 || c=='/' || c=='?' ) + { + baseEnd = basePtr; + break; + } + basePtr++; + } + + if( uri[0]!='/' && uri[0]!='\0' ) + { + for(;;) + { + char c = *basePtr; + if( c=='/' ) + { + baseEnd = basePtr; + } + else if( c=='?' || c==0 ) + { + break; + } + basePtr++; + } + } + dst = base.substr(0,baseEnd-baseStart); + if( uri[0]!='/' ) + { + dst += "/"; + } + dst += uri; + if( bPropagateUriParams ) + { + if (strchr(uri,'?') == 0) + { // uri doesn't have url parameters; copy from parents if present + const char *baseParams = strchr(basePtr,'?'); + if( baseParams ) + { + std::string params = base.substr(baseParams-baseStart); + dst.append(params); + } + } + } + } +} +static ClearKeyHelperFactory clearkey_helper_factory; + +const std::string ClearKeyHelper::CLEARKEY_OCDM_ID = "org.w3.clearkey"; + +const std::string& ClearKeyHelper::ocdmSystemId() const +{ + return CLEARKEY_OCDM_ID; +} + +void ClearKeyHelper::createInitData(std::vector& initData) const +{ + // For DASH the init data should have been extracted from the PSSH + // For HLS, we need to construct it + if (mDrmInfo.mediaFormat == eMEDIAFORMAT_DASH) + { + initData = this->mInitData; + } + else + { + DrmJsonObject jsonInitDataObj; + std::vector keyIds = {CLEARKEY_KEY_ID}; + + if (jsonInitDataObj.add("kids", keyIds)) + { + jsonInitDataObj.print(initData); + } + } +} + +bool ClearKeyHelper::parsePssh(const uint8_t* initData, uint32_t initDataLen) +{ + this->mInitData.assign(initData, initData + initDataLen); + + mKeyID.assign(initData + CLEARKEY_DASH_KEY_ID_OFFSET, initData + CLEARKEY_DASH_KEY_ID_OFFSET + CLEARKEY_DASH_KEY_ID_LEN); + + return true; +} + +void ClearKeyHelper::getKey(std::vector& keyID) const +{ + // For DASH the key should have been extracted from the PSSH + // For HLS, we return a fixed key, which we also place in the init data + if (mDrmInfo.mediaFormat == eMEDIAFORMAT_DASH) + { + keyID = this->mKeyID; + } + else + { + keyID.clear(); + (void)keyID.insert(keyID.begin(), CLEARKEY_KEY_ID.begin(), CLEARKEY_KEY_ID.end()); + } +} + +void ClearKeyHelper::generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const +{ + licenseRequest.method = LicenseRequest::POST; + + if(licenseRequest.url.empty()) + { + if (!mDrmInfo.keyURI.empty()) + { + std::string keyURI; + resolveURL(keyURI, mDrmInfo.manifestURL, mDrmInfo.keyURI.c_str(), mDrmInfo.bPropagateUriParams); + licenseRequest.url = keyURI; + } + else + { + licenseRequest.url = challengeInfo.url; + } + } + + if (NULL != challengeInfo.data) + { + licenseRequest.payload = challengeInfo.data->getData(); + } +} + +void ClearKeyHelper::transformLicenseResponse(std::shared_ptr licenseResponse) const +{ + // HLS requires the returned key to be transformed into a JWK. + // For DASH it will already be in JWK format + if ((mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS) || + (mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS_MP4)) + { + std::vector licenseResponseData( + reinterpret_cast(licenseResponse->getData().c_str()), + reinterpret_cast(licenseResponse->getData().c_str()) + licenseResponse->getDataLength()); + + std::vector keyId(CLEARKEY_KEY_ID.begin(), CLEARKEY_KEY_ID.end()); + + // Construct JSON Web Key (JWK) + DrmJsonObject keyInstance; + keyInstance.add("alg", "cbc"); // Hard coded to cbc for now + keyInstance.add("k", licenseResponseData, DrmJsonObject::ENCODING_BASE64_URL); + keyInstance.add("kid", keyId, DrmJsonObject::ENCODING_BASE64_URL); + + std::vector values = {&keyInstance}; + + DrmJsonObject keyObj; + keyObj.add("keys", values); + std::string printedJson = keyObj.print(); + + licenseResponse->setData( printedJson.c_str(), printedJson.length()); + } +} + +bool ClearKeyHelperFactory::isDRM(const struct DrmInfo& drmInfo) const +{ + return ((drmInfo.method == eMETHOD_AES_128) && + ((drmInfo.mediaFormat == eMEDIAFORMAT_DASH) || + (drmInfo.mediaFormat == eMEDIAFORMAT_HLS_MP4)) + ); +} + +DrmHelperPtr ClearKeyHelperFactory::createHelper(const struct DrmInfo& drmInfo) const +{ + MW_LOG_ERR("creating helper"); + if (isDRM(drmInfo)) + { + return std::make_shared(drmInfo); + } + else{ + MW_LOG_ERR("creating helper failed"); + } + return NULL; +} + +void ClearKeyHelperFactory::appendSystemId(std::vector& systemIds) const +{ + systemIds.push_back(CLEARKEY_UUID); +} + diff --git a/middleware/drm/helper/ClearKeyHelper.h b/middleware/drm/helper/ClearKeyHelper.h new file mode 100755 index 000000000..b902cbbc7 --- /dev/null +++ b/middleware/drm/helper/ClearKeyHelper.h @@ -0,0 +1,99 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +#ifndef _CLEARKEY_HELPER_H +#define _CLEARKEY_HELPER_H + +/** + * @file ClearKeyHelper.cpp + * @brief Implemented Clear key helper functions + */ + +#include + +#include "DrmHelper.h" + +/** + * @class ClearKeyHelper + * @brief Class handles the clear key license operations + */ +class ClearKeyHelper : public DrmHelper +{ +public: + const std::string& ocdmSystemId() const override; + + void createInitData(std::vector& initData) const override; + + bool parsePssh(const uint8_t* initData, uint32_t initDataLen) override; + + bool isClearDecrypt() const override { return true; } + + void getKey(std::vector& keyID) const override; + + virtual int getDrmCodecType() const override { return CODEC_TYPE; } + + void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const override; + + void transformLicenseResponse(std::shared_ptr licenseResponse) const override; + + virtual const std::string& friendlyName() const override { return FRIENDLY_NAME; }; + + ClearKeyHelper(const struct DrmInfo& drmInfo) : DrmHelper(drmInfo), mInitData(), mKeyID(), mPsshStr(), + CLEARKEY_KEY_ID("1"), FRIENDLY_NAME("Clearkey"), CODEC_TYPE(0), CLEARKEY_DASH_KEY_ID_OFFSET(32u), + CLEARKEY_DASH_KEY_ID_LEN(16u), KEY_ID_OFFSET(12), KEY_PAYLOAD_OFFSET(14), BASE_16(16) + {} + + ~ClearKeyHelper() { } + +private: + static const std::string CLEARKEY_OCDM_ID; + const std::string CLEARKEY_KEY_ID; + const std::string FRIENDLY_NAME; + const int CODEC_TYPE; + const size_t CLEARKEY_DASH_KEY_ID_OFFSET; // Offset in the PSSH to find the key ID for DASH + const size_t CLEARKEY_DASH_KEY_ID_LEN; // Length of the key ID for DASH + const int KEY_ID_OFFSET; + const int KEY_PAYLOAD_OFFSET; + const int BASE_16; + + std::string mPsshStr; + std::vector mInitData; + std::vector mKeyID; +}; + +/** + * @class ClearKeyHelperFactory + * @brief Helper Factory class to maintain Player DRM data + */ + +class ClearKeyHelperFactory : public DrmHelperFactory +{ +public: + static const int CLEARKEY_WEIGHTING = DEFAULT_WEIGHTING * 2; + + DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const; + + void appendSystemId(std::vector& systemIds) const; + + bool isDRM(const struct DrmInfo& drmInfo) const; + +public: + ClearKeyHelperFactory() : DrmHelperFactory(CLEARKEY_WEIGHTING) {}; +}; + +#endif //_CLEARKEY_HELPER_H diff --git a/middleware/drm/helper/DrmHelper.cpp b/middleware/drm/helper/DrmHelper.cpp new file mode 100755 index 000000000..ca95c2414 --- /dev/null +++ b/middleware/drm/helper/DrmHelper.cpp @@ -0,0 +1,61 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmHelper.cpp + * @brief Implemented Player DRM helper function + */ + +#include +#include +#include "PlayerLogManager.h" +#include "DrmHelper.h" + +bool DrmHelper::compare(DrmHelperPtr other) +{ + if (other == nullptr) return false; + if (mDrmInfo.systemUUID != other->mDrmInfo.systemUUID) return false; + if (mDrmInfo.mediaFormat != other->mDrmInfo.mediaFormat) return false; + if (ocdmSystemId() != other->ocdmSystemId()) return false; + if (getDrmMetaData() != other->getDrmMetaData()) return false; + + std::vector thisKeyId; + getKey(thisKeyId); + + std::vector otherKeyId; + other->getKey(otherKeyId); + std::map> otherKeyIds; + other->getKeys(otherKeyIds); + std::vector> keyIdVector; + if(otherKeyIds.empty()) + { + keyIdVector.push_back(std::move(otherKeyId)); + } + else + { + for(auto& keyId : otherKeyIds) + { + keyIdVector.push_back(keyId.second); + } + } + + if (!keyIdVector.empty() && (keyIdVector.end() == std::find(keyIdVector.begin(), keyIdVector.end(), thisKeyId))) return false; + + return true; +} diff --git a/middleware/drm/helper/DrmHelper.h b/middleware/drm/helper/DrmHelper.h new file mode 100755 index 000000000..f8df2cd0c --- /dev/null +++ b/middleware/drm/helper/DrmHelper.h @@ -0,0 +1,371 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef _DRM_HELPER_H +#define _DRM_HELPER_H + +/** + * @file DrmHelper.h + * @brief Implemented DRM helper functionalities + */ + +#include +#include +#include +#include +#include +#include + +#include "DrmData.h" +#include "DrmInfo.h" +#include "DrmUtils.h" +#include "DrmMemorySystem.h" + +/** + * @struct ChallengeInfo + * @brief challenge info to get the License + */ + +struct ChallengeInfo +{ + ChallengeInfo() : data(), url(), accessToken() {}; + std::shared_ptr data; /**< Challenge data returned from the DRM system */ + std::string url; /**< Challenge URL returned from the DRM system */ + std::string accessToken; /**< Access token required for the license request (if applicable) */ +}; + +/** + * @struct LicenseRequest + * @brief Holds the data to get the License + */ + +struct LicenseRequest +{ + LicenseRequest() : method(), url(), payload(), headers(),licenseAnonymousRequest(false) + {} + enum LicenseMethod + { + DRM_RETRIEVE, /**< Don't fetch the license, it will be handled externally by the DRM */ + GET, /**< Fetch license via HTTP GET request */ + POST /**< Fetch license via HTTP POST request */ + }; + + LicenseMethod method; + bool licenseAnonymousRequest; + std::string url; + std::string payload; + std::unordered_map> headers; +}; + +/** + * @class DrmHelper + * @brief DRM helper to handle DRM operations + */ +class DrmHelper; + +using DrmHelperPtr = std::shared_ptr; + +/** + * @class DrmHelper + * @brief DRM helper to handle DRM operations + */ +class DrmHelper +{ +public: + const uint32_t TIMEOUT_SECONDS; + const std::string EMPTY_DRM_METADATA; + + const std::string EMPTY_STRING; + DrmHelper(const struct DrmInfo drmInfo) : mDrmInfo(drmInfo), TIMEOUT_SECONDS(5000U), EMPTY_DRM_METADATA(), EMPTY_STRING() ,bOutputProtectionEnabled(false), protectionScheme() {} + DrmHelper(const DrmHelper&) = delete; + DrmHelper& operator=(const DrmHelper&) = delete; + + + uint32_t getProtectionScheme() + { + return protectionScheme; + } + + /** + * @brief Returns the OCDM system ID of the helper + * @return the OCDM system ID + */ + virtual const std::string& ocdmSystemId() const = 0; + + /** + * + * @param initData the Init Data to send to the CDM + */ + virtual void createInitData(std::vector& initData) const = 0; + + /** + * @brief Parse the optional PSSH data + * @param initData The init data from the PSSH + * @param initDataLen the length of initData + * @return + */ + virtual bool parsePssh(const uint8_t* initData, uint32_t initDataLen) = 0; + + /** + * @brief Determine if the DRM system needs to be in the clear or encrypted + * @return true if the data is clear, false if it should remain in the TEE + */ + virtual bool isClearDecrypt() const = 0; + + /** + * @brief Determine whether HDCP 2.2 protection is required to be active + * @return true if HDCP 2.2 protection is required, false otherwise + */ + virtual bool isHdcp22Required() const { return bOutputProtectionEnabled; } + + /** + * @brief Returns the content specific DRM metadata + * @return the DRM metadata + */ + virtual const std::string& getDrmMetaData() const {return EMPTY_DRM_METADATA;} + + /** + * @brief Sets the content specific DRM metadata + * @param the DRM metadata + */ + virtual void setDrmMetaData(const std::string& metaData) { } + + /** + * @brief Sets the default keyID + * @param the DRM cencData data + */ + virtual void setDefaultKeyID(const std::string& cencData) { } + + /** + * @brief Returns the DRM codec type for the helper, used in trace + * @return the DRM codec type + */ + virtual int getDrmCodecType() const { return 0; } + + /** + * @brief Get the amount of time in milliseconds to wait before aborting the wait + * for the license_challenge message to be received + * Default is TWO Seconds - 2000 + * @return the time to wait in milliseconds + */ + virtual uint32_t licenseGenerateTimeout() const { return TIMEOUT_SECONDS; } + + /** + * @brief Get the amount of time in milliseconds to wait before aborting the wait + * for the key_updated message to be received + * Default is TWO Seconds - 2000 + * @return the time to wait in milliseconds + */ + virtual uint32_t keyProcessTimeout() const { return TIMEOUT_SECONDS; } + + /** + * @brief Get the key ID + * @param keyID The key ID as a vector of binary data + */ + virtual void getKey(std::vector& keyID) const = 0; + + /** + * @brief Get the key IDs + * @param keyIDs The map containing Key ID vector of binary data + */ + virtual void getKeys(std::map>& keyIDs) const {} + + /** + * @brief Get the UUID + * @return the UUID + */ + virtual const std::string& getUuid() const { return mDrmInfo.systemUUID; } + + /** + * @brief Determines if decrypt should be called on clear samples + * @return Flag to indicate if should decrypt + */ + virtual bool isDecryptClearSamplesRequired() const { return mDrmInfo.bDecryptClearSamplesRequired; } + + /** + * @brief Determines if the DRM itself fetches the license or if PLAYER should use + * its own internal HTTP client to fetch the license + * Returning 'true' removes PLAYER calling generateLicenseRequest() on the CDM + * Default is to return false + * @return true if the DRM acquires the license, false if PLAYER should do it + */ + virtual bool isExternalLicense() const { return false; } + + /** + * @brief Generate the request details for the DRM license + * @param challengeInfo challenge information from the DRM system necessary to construct the license request + * @param licenseRequest license request data to populate + */ + virtual void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const = 0; + + /** + * @brief Transform the license response from the server into the necessary format for OCDM + * @param licenseResponse license response from the server to transform + */ + virtual void transformLicenseResponse(std::shared_ptr licenseResponse) const {} + + /** + * @brief Get the memory system used to transform data for transmission + * @return the memory system, or null if to send it as is to the ocdm wrapper + */ + virtual DRMMemorySystem* getMemorySystem() { return nullptr; } + + /** + * @fn compare + * @return true if the two helpers can be considered the same, false otherwise + */ + virtual bool compare(DrmHelperPtr other); + + /** + * @brief Cancels a DRM session + */ + virtual void cancelDrmSession() { } + + /** + * @brief Checks if the helper can cancel a session, or if the caller should do it + * @return true if the helper can cancel + */ + virtual bool canCancelDrmSession() { return false; } + + /** + * @brief Gets the friendly display name of the DRM + * @return friendly name + */ + virtual const std::string& friendlyName() const { return EMPTY_STRING; } + + /** + * @brief Set Output protection flag for the drmHelper + * @return None + */ + void setOutputProtectionFlag(bool bValue) { bOutputProtectionEnabled = bValue;} + + /** + * @brief Get propagateUri Parameter + * @return Flag to get propagate Manifest uri params in DRM value + */ + virtual bool getPropagateUriParam() const { return mDrmInfo.bPropagateUriParams; } + +public: + virtual ~DrmHelper() {} + +protected: + uint32_t protectionScheme; + const DrmInfo mDrmInfo; + bool bOutputProtectionEnabled; +}; + +/** + * @class DrmHelperFactory + * @brief Helper class to Maintain DRM data + */ + +class DrmHelperFactory +{ +public: + /** + * @brief Default weighting of a helper factory. + * Nominal scale of 0 to DEFAULT_WEIGHTING * 2 + * Larger weights have lower priority + */ + static const int DEFAULT_WEIGHTING = 50; + + /** + * @brief Determines if a helper class provides the identified DRM + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if this helper provides that DRM + */ + virtual bool isDRM(const struct DrmInfo& drmInfo) const = 0; + + /** + * @brief Build a helper class to support the identified DRM + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return the helper + */ + virtual DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const = 0; + + /** + * @brief Adds the system IDs supported by the DRM to a vector + * Used by the GStreamer plugins to advertise the DRM upstream to the pipeline + * @param systemIds the vector to use + */ + virtual void appendSystemId(std::vector& systemIds) const = 0; + + /** + * @brief Get the weighting for this helper factory, which determines its priority + * @return weighting value + */ + int getWeighting() { return mWeighting; } + + virtual ~DrmHelperFactory() {} + +protected: + DrmHelperFactory(int weighting = DEFAULT_WEIGHTING); + int mWeighting; +}; + + +/** + * @class DrmHelperEngine + * @brief Helper Engine for Player DRM operations + */ +class DrmHelperEngine +{ +private: + std::vector factories; + +public: + /** + * @brief DrmHelperEngine constructor + */ + DrmHelperEngine() : factories() {} + /** + * @fn hasDRM + * @param systemId the UUID from the PSSH or manifest + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return true if a DRM was found, false otherwise + */ + bool hasDRM(const struct DrmInfo& drmInfo) const; + + /** + * @fn createHelper + * @param drmInfo DrmInfo built by the HLS manifest parser + * @return the helper + */ + DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const; + + /** + * @fn getSystemIds + * @param ids vector to populate with supported IDs + */ + void getSystemIds(std::vector& ids) const; + + /** + * @fn getInstance + * @return DRM Helper Engine instance + */ + static DrmHelperEngine& getInstance(); + + /** + * @fn registerFactory + * @param factory helper factory instance to register + */ + void registerFactory(DrmHelperFactory* factory); +}; + +#endif //_DRM_HELPER_H diff --git a/middleware/drm/helper/DrmHelperFactory.cpp b/middleware/drm/helper/DrmHelperFactory.cpp new file mode 100755 index 000000000..2bd6d5c24 --- /dev/null +++ b/middleware/drm/helper/DrmHelperFactory.cpp @@ -0,0 +1,106 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DrmHelperFactory.cpp + * @brief DRM Helper Engine + */ + +#include +#include + +#include "DrmHelper.h" + +/* DRM Helper Engine */ + +/* Might want to consider double checked here */ + +/** + * @brief Get an instance of the DRM Helper Engine + */ +DrmHelperEngine& DrmHelperEngine::getInstance() +{ + static DrmHelperEngine instance; + return instance; +} + +/** + * @brief Register a Helper Factory + */ +void DrmHelperEngine::registerFactory(DrmHelperFactory* factory) +{ + factories.push_back(factory); + std::sort(factories.begin(), factories.end(), + [](DrmHelperFactory* a, DrmHelperFactory* b) { return (a->getWeighting() < b->getWeighting()); }); +} + +/** + * @brief Get the supported OCDM system IDs + */ +void DrmHelperEngine::getSystemIds(std::vector& ids) const +{ + ids.clear(); + for (auto f : factories ) + { + f->appendSystemId(ids); + } +} + +/** + * @brief Build a helper class to support the identified DRM + */ +DrmHelperPtr DrmHelperEngine::createHelper(const struct DrmInfo& drmInfo) const +{ + for (auto helper : factories) + { + if (true == helper->isDRM(drmInfo)) + { + return helper->createHelper(drmInfo); + } + } + + return NULL; +} + +/* DRM Helper Factory */ +/** + * @brief DrmHelperFactory constructor + */ +DrmHelperFactory::DrmHelperFactory(int weighting) : mWeighting(weighting) +{ + DrmHelperEngine::getInstance().registerFactory(this); +} + +/** + * @brief Determines whether the helper engine has a DRM helper available for the + * specified DrmInfo + */ +bool DrmHelperEngine::hasDRM(const struct DrmInfo& drmInfo) const +{ + for (auto helper : factories) + { + if (true == helper->isDRM(drmInfo)) + { + return true; + } + } + + return false; +} + diff --git a/middleware/drm/helper/PlayReadyHelper.cpp b/middleware/drm/helper/PlayReadyHelper.cpp new file mode 100755 index 000000000..8ef77fccc --- /dev/null +++ b/middleware/drm/helper/PlayReadyHelper.cpp @@ -0,0 +1,307 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayReadyHelper.cpp + * @brief play Ready DRM helper Engine + */ + +#include +#include + +#include "PlayReadyHelper.h" +#include "DrmJsonObject.h" +#include "DrmConstants.h" + +#include "_base64.h" + +#define KEYID_TAG_START "" +#define KEYID_TAG_END "" +#define PLAYREADY_VERSION "4.0.0.0" + +static PlayReadyHelperFactory playready_helper_factory; + +const std::string PlayReadyHelper::PLAYREADY_OCDM_ID = "com.microsoft.playready"; +const size_t PlayReadyHelper::PLAYREADY_DECODED_KEY_ID_LEN = 16U; +const size_t PlayReadyHelper::PLAYREADY_KEY_ID_LEN = 37U; + +const std::string& PlayReadyHelper::ocdmSystemId() const +{ + return PLAYREADY_OCDM_ID; +} + +void PlayReadyHelper::createInitData(std::vector& initData) const +{ + initData = this->mInitData; +} + + +/**< API Move to util class**/ +/** + * @brief find sub string in between string + * @return substring + */ +std::string PlayReadyHelper::findSubstr(std::string &data, std::string start, std::string end) +{ + std::string retVal = ""; + auto first = data.find(start); + if ((first != std::string::npos)) + { + std::string subStr = data.substr(first + start.size()); + auto last = subStr.find(end); + if ((last != std::string::npos)) + { + retVal = subStr.substr (0,last); + } + } + return retVal; +} + +/** + * @brief Extract keyID from given PSSH data. + * + * @return std::string keyId - copy by value + */ +#define PLAYREADY_VERSION_4_0_KID_START "" /**< KeyId represent as value for 4.0 version*/ +#define PLAYREADY_VERSION_4_X_KID_START " 0 */ +#define PLAYREADY_KID_END "" /**< KeyId node end **/ + +#define PLAYREADY_VERSION_4_0 "4.0.0.0" /**< Playready version 4.0 **/ +#define PLAYREADY_VERSION_4_1 "4.1.0.0" /**< Playready version 4.1 **/ +#define PLAYREADY_VERSION_4_2 "4.2.0.0" /**< Playready version 4.2 **/ +#define PLAYREADY_VERSION_4_3 "4.3.0.0" /**< Playready version 4.3 **/ +std::string PlayReadyHelper::extractKeyID() +{ + std::string propValueEnd = "\""; + std::string version = findSubstr(mStrInitDataFormated, "version=\"", propValueEnd); + std::string keyId = ""; + + MW_LOG_INFO ("PlayReady Version [%s]", version.c_str()); + + if(version == PLAYREADY_VERSION_4_0) + { + keyId = findSubstr(mStrInitDataFormated, PLAYREADY_VERSION_4_0_KID_START, PLAYREADY_KID_END); + } + else if((version == PLAYREADY_VERSION_4_1)) + { + /**< Key Id represent in protectinfo block without attribute; single keyId support + * + * + * + */ + + std::string protectInfo = findSubstr(mStrInitDataFormated, "", ""); + std::string keyTag = findSubstr(protectInfo, PLAYREADY_VERSION_4_X_KID_START, PLAYREADY_KID_END); + keyId = findSubstr(keyTag, "VALUE=\"", propValueEnd); + } + else if((version == PLAYREADY_VERSION_4_2)) + { + /**< Key Id represent in protectinfo block without attribute; multiple keyIds support + * + * + * + * + * + * + * + */ + std::string protectInfo = findSubstr(mStrInitDataFormated, "", ""); + std::string kids = findSubstr(protectInfo, "", ""); + /**< TODO: multiple keyIds support in Player; now taking first value only **/ + std::string keyTag = findSubstr(kids, PLAYREADY_VERSION_4_X_KID_START, PLAYREADY_KID_END); + keyId = findSubstr(keyTag, "VALUE=\"", propValueEnd); + } + else if((version == PLAYREADY_VERSION_4_3)) + { + /**< Key Id represent in protectinfo block with attribute; multiple keyIds support + * + * + * + * + * + * + * + */ + std::string protectInfo = findSubstr(mStrInitDataFormated, ""); + std::string kids = findSubstr(protectInfo, "", ""); + /**< TODO: multiple keyIds support in PLAYER; now taking first value only **/ + std::string keyTag = findSubstr(kids, PLAYREADY_VERSION_4_X_KID_START, PLAYREADY_KID_END); + keyId = findSubstr(keyTag, "VALUE=\"", propValueEnd); + } + else + { + MW_LOG_WARN ("Unsupported PSSH version MPD[%s]", version.c_str()); + } + + MW_LOG_INFO("Extracted Key ID: %s", keyId.c_str()); + //DumpBlob((const unsigned char*)keyId.c_str(), keyId.size()); + return keyId; + +} + +/** + * @brief Extract content meta data from given PSSH data. + * For example for content meta data, + * When strings are given as "ckm:policy xmlns:ckm="urn:ccp:ckm"" and "ckm:policy" + * we need the contents from here + */ +std::string PlayReadyHelper::extractMetaData() +{ + return findSubstr(mStrInitDataFormated, DRM_METADATA_TAG_START, DRM_METADATA_TAG_END); +} + +bool PlayReadyHelper::parsePssh(const uint8_t* initData, uint32_t initDataLen) +{ + int keyIdLen = 0; + bool res = false; + // Extract key + if (initData != NULL && initDataLen > 0) + { + this->mInitData.assign(initData, initData + initDataLen); + std::string keyData = ""; + char* cleanedPssh = (char*) malloc(initDataLen+1); + if (cleanedPssh) + { + int cleanedPsshLen = 0; + for(int itr = 0; itr < initDataLen; itr++) + { + if(initData[itr] != 0) + { + //cout<mStrInitDataFormated = std::string(cleanedPssh); + free(cleanedPssh); + cleanedPssh = NULL; + //Clear unwanted spaces from pssh data - time being not need + //std::remove(mStrInitDataFormated.begin(), mStrInitDataFormated.end(), ' '); + + keyData = extractKeyID(); + } + //MW_LOG_INFO("pr keyid: %s keyIdlen: %d", keydata, keyIdLen); + if (!keyData.empty()) + { + size_t decodedDataLen = 0; + unsigned char* decodedKeydata = base64_Decode(keyData.c_str(), &decodedDataLen, keyData.size()); + + if (decodedDataLen != PLAYREADY_DECODED_KEY_ID_LEN) + { + MW_LOG_ERR("Invalid key size found while extracting PR Decoded-KeyID-Length: %zu (PR KeyID: %s KeyID-Length: %d)", decodedDataLen, keyData.c_str(), keyIdLen); + } + else + { + unsigned char swappedKeydata[PLAYREADY_DECODED_KEY_ID_LEN] = {0}; + DrmUtils::convertEndianness(decodedKeydata, swappedKeydata); + unsigned char keyId[PLAYREADY_KEY_ID_LEN] = {0}; + uuid_t *keyiduuid = (uuid_t*)swappedKeydata; + uuid_unparse_lower(*keyiduuid, reinterpret_cast(keyId)); + MW_LOG_INFO("Extracted Key ID is %s", keyId); + mKeyID.assign(keyId, keyId + (PLAYREADY_KEY_ID_LEN-1)); /**< No need end null character in vector **/ + res = true; + } + + free(decodedKeydata); + } + else + { + MW_LOG_WARN("Bad DRM init data with Empty KeyID has received : %s!!", friendlyName().c_str()); + } + + /**< Extract the optional content metadata. + * No specification available based on version*/ + mContentMetaData = extractMetaData(); + } + else + { + MW_LOG_ERR("Invalid PSSH Data Received : NULL"); + } + + return res; +} + +void PlayReadyHelper::setDrmMetaData(const std::string& metaData) +{ + if (mContentMetaData.empty()) + { + mContentMetaData = metaData; + } +} + +void PlayReadyHelper::getKey(std::vector& keyID) const +{ + keyID = this->mKeyID; +} + +void PlayReadyHelper::generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const +{ + licenseRequest.method = LicenseRequest::POST; + + if (licenseRequest.url.empty()) + { + licenseRequest.url = challengeInfo.url; + } + + licenseRequest.headers = {{"Content-Type:", {"text/xml; charset=utf-8"}}}; + + if (!mContentMetaData.empty()) + { + std::vector challengeData(reinterpret_cast(challengeInfo.data->getData().c_str()),reinterpret_cast(challengeInfo.data->getData().c_str()) + challengeInfo.data->getDataLength()); + + DrmJsonObject comChallengeObj; + comChallengeObj.add("keySystem", "playReady"); + comChallengeObj.add("mediaUsage", "stream"); + comChallengeObj.add("licenseRequest", challengeData, DrmJsonObject::ENCODING_BASE64); + comChallengeObj.add("contentMetadata", mContentMetaData, DrmJsonObject::ENCODING_BASE64); + + if ((!challengeInfo.accessToken.empty()) && !licenseRequest.licenseAnonymousRequest) + { + comChallengeObj.add("accessToken", challengeInfo.accessToken); + } + + licenseRequest.payload = comChallengeObj.print(); + } + else if (challengeInfo.data) + { + licenseRequest.payload = challengeInfo.data->getData(); + } +} + +bool PlayReadyHelperFactory::isDRM(const struct DrmInfo& drmInfo) const +{ + return (((drmInfo.systemUUID == PLAYREADY_UUID) || (drmInfo.keyFormat == PlayReadyHelper::PLAYREADY_OCDM_ID)) + && ((drmInfo.mediaFormat == eMEDIAFORMAT_DASH) || (drmInfo.mediaFormat == eMEDIAFORMAT_HLS_MP4)) + ); +} + +DrmHelperPtr PlayReadyHelperFactory::createHelper(const struct DrmInfo& drmInfo) const +{ + if (isDRM(drmInfo)) + { + return std::make_shared(drmInfo); + } + return NULL; +} + +void PlayReadyHelperFactory::appendSystemId(std::vector& systemIds) const +{ + systemIds.push_back(PLAYREADY_UUID); +} diff --git a/middleware/drm/helper/PlayReadyHelper.h b/middleware/drm/helper/PlayReadyHelper.h new file mode 100755 index 000000000..5918f5500 --- /dev/null +++ b/middleware/drm/helper/PlayReadyHelper.h @@ -0,0 +1,102 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +#ifndef _PLAYREADY_HELPER_H +#define _PLAYREADY_HELPER_H + +/** + * @file PlayReadyHelper.h + * @brief Handles the operation for Play ready DRM operations + */ + +#include + +#include "DrmHelper.h" +#include "PlayerLogManager.h" +/** + * @class PlayReadyHelper + * @brief Handles the operation for Play ready DRM operations + */ + +class PlayReadyHelper : public DrmHelper +{ +public: + friend class PlayReadyHelperFactory; + + const std::string& ocdmSystemId() const override; + + void createInitData(std::vector& initData) const override; + + bool parsePssh(const uint8_t* initData, uint32_t initDataLen) override; + + bool isClearDecrypt() const override { return false; } + + bool isHdcp22Required() const override { return bOutputProtectionEnabled; } + + void setDrmMetaData( const std::string& metaData ) override; + + void getKey(std::vector& keyID) const override; + + virtual int getDrmCodecType() const override { return CODEC_TYPE; } + + void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const override; + + const std::string& getDrmMetaData() const override {return mContentMetaData;} + + virtual const std::string& friendlyName() const override { return FRIENDLY_NAME; }; + + PlayReadyHelper(const struct DrmInfo& drmInfo) : DrmHelper(drmInfo), FRIENDLY_NAME("PlayReady"), CODEC_TYPE(2), + mPsshStr(), + mInitData(), mKeyID(), mContentMetaData(), mStrInitDataFormated() + {} + + ~PlayReadyHelper() {} + +private: + std::string extractMetaData(); + std::string extractKeyID(); + std::string findSubstr(std::string &data, std::string start, std::string end); /**< move to utils */ + + static const std::string PLAYREADY_OCDM_ID; + static const size_t PLAYREADY_DECODED_KEY_ID_LEN; // Expected size of base64 decoded key ID from the PSSH + static const size_t PLAYREADY_KEY_ID_LEN; // PlayReady ID key length. A NULL character is included at the end + const std::string FRIENDLY_NAME; + const int CODEC_TYPE; + + std::string mPsshStr; + std::vector mInitData; + std::vector mKeyID; + std::string mContentMetaData; + std::string mStrInitDataFormated; /**< String init data after clean up*/ +}; + +/** + * @class PlayReadyHelperFactory + * @brief Handles operations to support play ready DRM + */ +class PlayReadyHelperFactory : public DrmHelperFactory +{ +public: + DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const; + + void appendSystemId(std::vector& systemIds) const; + + bool isDRM(const struct DrmInfo& drmInfo) const; +}; + +#endif //_PLAYREADY_HELPER_H diff --git a/middleware/drm/helper/VanillaDrmHelper.h b/middleware/drm/helper/VanillaDrmHelper.h new file mode 100644 index 000000000..83cc52ca9 --- /dev/null +++ b/middleware/drm/helper/VanillaDrmHelper.h @@ -0,0 +1,77 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file VanillaDrmHelper.h + * @brief Handles the operation foe Vanilla DRM + */ + +#ifndef VANILLADRMHELPER_H +#define VANILLADRMHELPER_H + +#include "DrmHelper.h" +#include +#include + +/** + * @class VanillaDrmHelper + * @brief Handles the operation foe Vanilla DRM + */ + +class VanillaDrmHelper : public DrmHelper +{ +private: + const std::string EMPTY_STRING; +public: + virtual const std::string& ocdmSystemId() const override { return EMPTY_STRING; }; + + virtual void createInitData(std::vector& initData){}; + + virtual bool parsePssh(const uint8_t* initData, uint32_t initDataLen) override { return false; }; + + virtual bool isClearDecrypt() { return true; }; + + virtual void setDrmMetaData(const std::string& metaData) override { } + + virtual int getDrmCodecType() const override { return CODEC_TYPE; } + + virtual void getKey(std::vector& keyID) { keyID.clear(); }; + + virtual bool isExternalLicense() const override { return true; }; + + virtual void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const override {}; + + virtual const std::string& friendlyName() const override { return FRIENDLY_NAME; } + + virtual void createInitData(std::vector& initData) const override {}; + + virtual bool isClearDecrypt() const override { return true; }; + + virtual void getKey(std::vector& keyID) const override {}; + + VanillaDrmHelper() : DrmHelper(DrmInfo {}), FRIENDLY_NAME("Vanilla_AES"), CODEC_TYPE(3), EMPTY_STRING() {} + +private: + const std::string FRIENDLY_NAME; + const int CODEC_TYPE; + +}; + +#endif /* VANILLADRMHELPER_H */ + diff --git a/middleware/drm/helper/VerimatrixHelper.cpp b/middleware/drm/helper/VerimatrixHelper.cpp new file mode 100755 index 000000000..e8ad96999 --- /dev/null +++ b/middleware/drm/helper/VerimatrixHelper.cpp @@ -0,0 +1,151 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include +#include + +#include "VerimatrixHelper.h" +#include "DrmUtils.h" +#include "DrmConstants.h" + +#define KEYURL_TAG_START "" + +static VerimatrixHelperFactory verimatrix_helper_factory; + +const std::string VerimatrixHelper::VERIMATRIX_OCDM_ID = "com.verimatrix.ott"; + +bool VerimatrixHelper::parsePssh(const uint8_t* initData, uint32_t initDataLen) +{ + this->mInitData.assign(initData, initData+initDataLen); + + if(mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS) + { + MW_LOG_WARN("mediaFormat is not DASH"); + return true; + } + + if(initDataLen < VERIMATRIX_PSSH_DATA_POSITION) + { + MW_LOG_WARN("initDataLen less than %d", VERIMATRIX_PSSH_DATA_POSITION); + return false; + } + + initData += VERIMATRIX_PSSH_DATA_POSITION; + initDataLen -= VERIMATRIX_PSSH_DATA_POSITION; + char *init = new char[initDataLen + 1]; + memcpy(init, initData, initDataLen); + init[initDataLen] = 0; + + std::string pssh(init); + delete []init; + + MW_LOG_WARN("pssh %s", pssh.c_str()); + + size_t sp = pssh.find(KEYURL_TAG_START); + size_t ep = pssh.find(KEYURL_TAG_END); + if((sp == std::string::npos) || (ep == std::string::npos)) + { + MW_LOG_WARN("not found KeyUrl TAG"); + return false; + } + + sp += strlen(KEYURL_TAG_START); + + std::string keyfile = pssh.substr(sp, ep - sp); + MW_LOG_WARN("keyfile %s", keyfile.c_str()); + + mKeyID.assign((uint8_t *)keyfile.c_str(), (uint8_t *)keyfile.c_str() + keyfile.length()); + + return true; +} + + +void VerimatrixHelper::setDrmMetaData(const std::string& metaData) +{ + mContentMetadata = metaData; +} + +const std::string& VerimatrixHelper::ocdmSystemId() const +{ + return VERIMATRIX_OCDM_ID; +} + +void VerimatrixHelper::createInitData(std::vector& initData) const +{ + const char *init; + + if(mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS) + init = "{\"contentType\" : \"HLS\"}"; + else if(mDrmInfo.mediaFormat == eMEDIAFORMAT_DASH) + init = "{\"contentType\" : \"DASH\"}"; + else + MW_LOG_WARN("unknown mediaFormat %d", mDrmInfo.mediaFormat); + + initData.assign((uint8_t *)init, (uint8_t *)init + strlen(init)); +} + +void VerimatrixHelper::getKey(std::vector& keyID) const +{ + keyID.clear(); + if(mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS) + keyID.insert(keyID.begin(), mDrmInfo.keyURI.begin(), mDrmInfo.keyURI.end()); + else if(mDrmInfo.mediaFormat == eMEDIAFORMAT_DASH) + keyID.insert(keyID.begin(), mKeyID.begin(), mKeyID.end()); + else + MW_LOG_WARN("unknown mediaFormat %d", mDrmInfo.mediaFormat); +} + +void VerimatrixHelper::generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const +{ + licenseRequest.method = LicenseRequest::DRM_RETRIEVE; + licenseRequest.url = ""; + licenseRequest.payload = ""; +} + +void VerimatrixHelper::transformLicenseResponse(std::shared_ptr licenseResponse) const +{ + if(mDrmInfo.mediaFormat == eMEDIAFORMAT_HLS) + licenseResponse->setData((unsigned char*)mDrmInfo.keyURI.c_str(), mDrmInfo.keyURI.length()); + else if(mDrmInfo.mediaFormat == eMEDIAFORMAT_DASH) + licenseResponse->setData((unsigned char*)mKeyID.data(), mKeyID.size()); + else + MW_LOG_WARN("unknown mediaFormat %d", mDrmInfo.mediaFormat); +} + +bool VerimatrixHelperFactory::isDRM(const struct DrmInfo& drmInfo) const +{ + return (((VERIMATRIX_UUID == drmInfo.systemUUID) || (VerimatrixHelper::VERIMATRIX_OCDM_ID == drmInfo.keyFormat)) + && ((drmInfo.mediaFormat == eMEDIAFORMAT_DASH) || (drmInfo.mediaFormat == eMEDIAFORMAT_HLS)) + ); +} + +DrmHelperPtr VerimatrixHelperFactory::createHelper(const struct DrmInfo& drmInfo) const +{ + if (isDRM(drmInfo)) + { + return std::make_shared(drmInfo); + } + return NULL; +} + +void VerimatrixHelperFactory::appendSystemId(std::vector& systemIds) const +{ + systemIds.push_back(VERIMATRIX_UUID); +} diff --git a/middleware/drm/helper/VerimatrixHelper.h b/middleware/drm/helper/VerimatrixHelper.h new file mode 100755 index 000000000..6c1cc23de --- /dev/null +++ b/middleware/drm/helper/VerimatrixHelper.h @@ -0,0 +1,83 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _VERIMATRIX_DRM_HELPER_H +#define _VERIMATRIX_DRM_HELPER_H + +#include + +#include "DrmHelper.h" + +class VerimatrixHelper: public DrmHelper +{ +public: + friend class VerimatrixHelperFactory; + + virtual const std::string& ocdmSystemId() const; + + void createInitData(std::vector& initData) const; + + bool parsePssh(const uint8_t* initData, uint32_t initDataLen); + + bool isClearDecrypt() const { return false; } + + bool isExternalLicense() const { return true; }; + + void getKey(std::vector& keyID) const; + + const std::string& getDrmMetaData() const {return mContentMetadata;} + + void setDrmMetaData(const std::string& metaData); + + virtual int getDrmCodecType() const { return CODEC_TYPE; } + + void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const; + + void transformLicenseResponse(std::shared_ptr licenseResponse) const; + + virtual const std::string& friendlyName() const override { return FRIENDLY_NAME; }; + + VerimatrixHelper(const struct DrmInfo& drmInfo) : DrmHelper(drmInfo), FRIENDLY_NAME("Verimatrix"), + CODEC_TYPE(1), VERIMATRIX_PSSH_DATA_POSITION(52), + mInitData(), mKeyID(), mContentMetadata() + {} + + ~VerimatrixHelper() { } + +private: + static const std::string VERIMATRIX_OCDM_ID; + const std::string FRIENDLY_NAME; + const int CODEC_TYPE; + const uint8_t VERIMATRIX_PSSH_DATA_POSITION; + + std::vector mInitData; + std::vector mKeyID; + std::string mContentMetadata; +}; + +class VerimatrixHelperFactory : public DrmHelperFactory +{ + DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const; + + void appendSystemId(std::vector& systemIds) const; + + bool isDRM(const struct DrmInfo& drmInfo) const; +}; + + +#endif //_VERIMATRIX_DRM_HELPER_H diff --git a/middleware/drm/helper/WidevineDrmHelper.cpp b/middleware/drm/helper/WidevineDrmHelper.cpp new file mode 100755 index 000000000..0f2075915 --- /dev/null +++ b/middleware/drm/helper/WidevineDrmHelper.cpp @@ -0,0 +1,271 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file WidevineDrmHelper.cpp + * @brief Handles the Widevine DRM helper functions + */ + +#include +#include + +#include "WidevineDrmHelper.h" +#include "DrmUtils.h" +#include "PlayerLogManager.h" +#include "DrmConstants.h" + +#define MultiChar_Constant(TEXT) ( \ +(static_cast(TEXT[0]) << 0x18) | \ +(static_cast(TEXT[1]) << 0x10) | \ +(static_cast(TEXT[2]) << 0x08) | \ +(static_cast(TEXT[3]) << 0x00) ) + + +static WidevineDrmHelperFactory widevine_helper_factory; + +const std::string WidevineDrmHelper::WIDEVINE_OCDM_ID = "com.widevine.alpha"; + +#define READ_U32(buf) \ + ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | buf[3]; buf+=4; + +static long ParseMultiInt( const unsigned char **ppData, const unsigned char *fin ) +{ + const unsigned char *psshData = *ppData; + long iVal = 0; + int shift = 0; + while( psshData>24; + if( psshDataVer == 0 ) + { + uint32_t sz = READ_U32(psshData); + if( fin - psshData != sz ) + { + MW_LOG_ERR( "unexpected size %d expected %d", sz, (int)(fin-psshData) ); + } + long iVal; + while( psshData0 && &psshData[fieldSize] <= fin ) + { std::vector keyId; + keyId.assign( psshData, &psshData[fieldSize] ); + mKeyIDs[kidCount++] = keyId; + rc = true; + } + psshData += fieldSize; + } + break; + + case 0x32: // Policy (deprecated) + case 0x2a: // Track Type (deprecated) + case 0x1a: // Provider (deprecated) + { + int fieldSize = *psshData++; + MW_LOG_WARN( "0x%02x: '%.*s'\n", (int)fieldType, fieldSize, psshData ); + psshData += fieldSize; + } + break; + + default: + // unknown + break; + } + } + } + else if( psshDataVer == 1 ) + { + kidCount = READ_U32(psshData); + uint8_t fieldSize = 16; + for( int i=0; i keyId; + keyId.assign( psshData, &psshData[fieldSize] ); + mKeyIDs[i]=keyId; + psshData += fieldSize; + rc = true; + } + } + else + { + MW_LOG_ERR("unsupported PSSH version: %u", psshDataVer); + } + } + } + } + return rc; +} + +void WidevineDrmHelper::setDrmMetaData(const std::string& metaData) +{ + mContentMetadata = metaData; +} + +void WidevineDrmHelper::setDefaultKeyID(const std::string& cencData) +{ + std::vector defaultKeyID(cencData.begin(), cencData.end()); + if(!mKeyIDs.empty()) + { + for(auto& it : mKeyIDs) + { + if(defaultKeyID == it.second) + { + mDefaultKeySlot = it.first; + MW_LOG_WARN("setDefaultKeyID : %s slot : %d", cencData.c_str(), mDefaultKeySlot); + } + } + } +} + + +const std::string& WidevineDrmHelper::ocdmSystemId() const +{ + return WIDEVINE_OCDM_ID; +} + +void WidevineDrmHelper::createInitData(std::vector& initData) const +{ + initData = this->mInitData; +} + +void WidevineDrmHelper::getKey(std::vector& keyID) const +{ + MW_LOG_WARN("WidevineDrmHelper::getKey defaultkey: %d mKeyIDs.size:%zu", mDefaultKeySlot, mKeyIDs.size()); + if ((mDefaultKeySlot >= 0) && (mDefaultKeySlot < mKeyIDs.size())) + { + keyID = this->mKeyIDs.at(mDefaultKeySlot); + } + else if (mKeyIDs.size() > 0) + { + keyID = this->mKeyIDs.at(0); + } + else + { + MW_LOG_ERR("No key"); + } +} + +void WidevineDrmHelper::getKeys(std::map>& keyIDs) const +{ + keyIDs = this->mKeyIDs; +} + +void WidevineDrmHelper::generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const +{ + licenseRequest.method = LicenseRequest::POST; + + if (licenseRequest.url.empty()) + { + licenseRequest.url = challengeInfo.url; + } + + licenseRequest.headers = {{"Content-Type:", {"application/octet-stream"}}}; + + licenseRequest.payload= challengeInfo.data->getData(); +} + +bool WidevineDrmHelperFactory::isDRM(const struct DrmInfo& drmInfo) const +{ + return (((WIDEVINE_UUID == drmInfo.systemUUID) || (WidevineDrmHelper::WIDEVINE_OCDM_ID == drmInfo.keyFormat)) + && ((drmInfo.mediaFormat == eMEDIAFORMAT_DASH) || (drmInfo.mediaFormat == eMEDIAFORMAT_HLS_MP4)) + ); +} + +DrmHelperPtr WidevineDrmHelperFactory::createHelper(const struct DrmInfo& drmInfo) const +{ + + if (isDRM(drmInfo)) + { + MW_LOG_ERR("creating helper"); + return std::make_shared(drmInfo); + } + else + MW_LOG_ERR("failed to create helper"); + return NULL; +} + +void WidevineDrmHelperFactory::appendSystemId(std::vector& systemIds) const +{ + systemIds.push_back(WIDEVINE_UUID); +} diff --git a/middleware/drm/helper/WidevineDrmHelper.h b/middleware/drm/helper/WidevineDrmHelper.h new file mode 100755 index 000000000..a64c3af61 --- /dev/null +++ b/middleware/drm/helper/WidevineDrmHelper.h @@ -0,0 +1,98 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _WIDEVINE_DRM_HELPER_H +#define _WIDEVINE_DRM_HELPER_H + +/** + * @file WidevineDrmHelper.h + * @brief Handles the operation for Wide vine DRM operation + */ + +#include + +#include "DrmHelper.h" + +/** + * @class WidevineDrmHelper + * @brief Handles the operation for Wide vine DRM operation + */ + +class WidevineDrmHelper: public DrmHelper +{ +public: + friend class WidevineDrmHelperFactory; + + virtual const std::string& ocdmSystemId() const override; + + void createInitData(std::vector& initData) const override; + + bool parsePssh(const uint8_t* initData, uint32_t initDataLen) override; + + bool isClearDecrypt() const override { return false; } + + bool isExternalLicense() const override { return false; }; + + void getKey(std::vector& keyID) const override; + + void getKeys(std::map>& keyIDs) const override; + + const std::string& getDrmMetaData() const override {return mContentMetadata;} + + void setDrmMetaData(const std::string& metaData) override; + + void setDefaultKeyID(const std::string& cencData) override; + + virtual int getDrmCodecType() const override { return CODEC_TYPE; } + + void generateLicenseRequest(const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest) const override; + + virtual const std::string& friendlyName() const override { return FRIENDLY_NAME; }; + + WidevineDrmHelper(const struct DrmInfo& drmInfo) : DrmHelper(drmInfo), FRIENDLY_NAME("Widevine"), + CODEC_TYPE(1), mInitData(), mKeyID(), mKeyIDs(), mContentMetadata(), mDefaultKeySlot(-1) + {} + + ~WidevineDrmHelper() { } + +private: + static const std::string WIDEVINE_OCDM_ID; + const std::string FRIENDLY_NAME; + const int CODEC_TYPE; + std::vector mInitData; + std::vector mKeyID; + std::map> mKeyIDs; + std::string mContentMetadata; + int mDefaultKeySlot; +}; + +/** + * @class WidevineDrmHelperFactory + * @brief Helps to handle widevine DRM + */ +class WidevineDrmHelperFactory : public DrmHelperFactory +{ + DrmHelperPtr createHelper(const struct DrmInfo& drmInfo) const; + + void appendSystemId(std::vector& systemIds) const; + + bool isDRM(const struct DrmInfo& drmInfo) const; +}; + + +#endif //_WIDEVINE_DRM_HELPER_H diff --git a/middleware/drm/ocdm/OcdmBasicSessionAdapter.cpp b/middleware/drm/ocdm/OcdmBasicSessionAdapter.cpp new file mode 100755 index 000000000..c57a60642 --- /dev/null +++ b/middleware/drm/ocdm/OcdmBasicSessionAdapter.cpp @@ -0,0 +1,79 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file OcdmBasicSessionAdapter.cpp + * @brief Handles operations on OCDM session + */ + +#include "OcdmBasicSessionAdapter.h" +#include "PlayerLogManager.h" + +int OCDMBasicSessionAdapter::decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, + uint32_t payloadDataSize, uint8_t **ppOpaqueData) +{ + if (!verifyOutputProtection()) + { + return HDCP_COMPLIANCE_CHECK_FAILURE; + } + + std::lock_guard guard(decryptMutex); + + uint8_t *dataToSend = const_cast(payloadData); + uint32_t sizeToSend = payloadDataSize; + std::vector vdata; + + if (m_drmHelper->getMemorySystem() != nullptr) + { + if (!m_drmHelper->getMemorySystem()->encode(payloadData, payloadDataSize, vdata)) + { + MW_LOG_WARN("Failed to encode memory for transmission"); + return -1; + } + sizeToSend = (uint32_t)vdata.size(); + dataToSend = vdata.data(); + } + + EncryptionScheme encScheme = AesCtr_Cenc; + EncryptionPattern pattern = {0}; + /* CID:313823 - Waiting while holding a lock, got detected due to usage of external API. It may be replaced if approach is redesigned in future */ + int retvalue = opencdm_session_decrypt(m_pOpenCDMSession, + dataToSend, + sizeToSend, + encScheme, pattern, + f_pbIV, f_cbIV, + m_keyId.data(), m_keyId.size(), 0 ); + if (retvalue != 0) + { + if (m_drmHelper->getMemorySystem() != nullptr) + { + m_drmHelper->getMemorySystem()->terminateEarly(); + } + MW_LOG_INFO("decrypt returned : %d", retvalue); + } + else if (m_drmHelper->getMemorySystem() != nullptr) + { + if (!m_drmHelper->getMemorySystem()->decode(dataToSend, sizeToSend, const_cast(payloadData), payloadDataSize)) + { + MW_LOG_WARN("Failed to decode memory for transmission"); + return -1; + } + } + return retvalue; +} diff --git a/middleware/drm/ocdm/OcdmBasicSessionAdapter.h b/middleware/drm/ocdm/OcdmBasicSessionAdapter.h new file mode 100755 index 000000000..df52bb10d --- /dev/null +++ b/middleware/drm/ocdm/OcdmBasicSessionAdapter.h @@ -0,0 +1,40 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file OcdmBasicSessionAdapter.cpp + * @brief Handles operations on OCDM session + */ +#include "opencdmsessionadapter.h" +#include "DrmHelper.h" + +/** + * @class OCDMBasicSessionAdapter + * @brief OCDM session Adapter + */ +class OCDMBasicSessionAdapter : public OCDMSessionAdapter +{ +public: + OCDMBasicSessionAdapter(DrmHelperPtr drmHelper, DrmCallbacks *drmCallbacks) + : OCDMSessionAdapter(drmHelper, drmCallbacks) + {}; + ~OCDMBasicSessionAdapter() {}; + + int decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData); +}; diff --git a/middleware/drm/ocdm/OcdmGstSessionAdapter.cpp b/middleware/drm/ocdm/OcdmGstSessionAdapter.cpp new file mode 100755 index 000000000..1f541811b --- /dev/null +++ b/middleware/drm/ocdm/OcdmGstSessionAdapter.cpp @@ -0,0 +1,432 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file OcdmGstSessionAdapter.cpp + * @brief File holds operations on OCDM gst sessions + */ + +#include +#include +#include "OcdmGstSessionAdapter.h" +#include "PlayerUtils.h" +#include +#include +#include +#include +#include + +#include "gst/video/gstvideotimecode.h" +#include "gst/video/gstvideometa.h" + +#define USEC_PER_SEC 1000000 +static inline uint64_t GetCurrentTimeStampInUSecs() +{ + struct timeval timeStamp; + uint64_t retVal = 0; + + gettimeofday(&timeStamp, NULL); + + // Convert timestamp to Micro Seconds + retVal = (uint64_t)(((uint64_t) timeStamp.tv_sec * USEC_PER_SEC) + timeStamp.tv_usec); + + return retVal; +} + +static inline uint64_t GetCurrentTimeStampInMSec() +{ + return GetCurrentTimeStampInUSecs() / 1000; +} +#define LOG_DECRYPT_STATS 1 +#define DECRYPT_AVG_TIME_THRESHOLD 10.0 //10 milliseconds +#ifdef LOG_DECRYPT_STATS +#define MAX_THREADS 10 +#define INTERVAL 120 + +/** + * @struct DecryptStats + * @brief Holds decryption profile stats + */ +struct DecryptStats +{ + uint64_t nBytesInterval; + uint64_t nTimeInterval; + uint64_t nBytesTotal; + uint64_t nTimeTotal; + uint64_t nCallsTotal; + pthread_t threadID; + +}; +#endif // LOG_DECRYPT_STATS +#define SEC_SIZE size_t +void LogPerformanceExt(const char *strFunc, uint64_t msStart, uint64_t msEnd, SEC_SIZE nDataSize) +{ + uint64_t delta = msEnd - msStart; + //CID: 107327,26,25,24,23 - Removed the unused initialized variables : bThreshold,nDataMin,nRestart,nRateMin,nTimeMin + +#ifdef LOG_DECRYPT_STATS + { + static DecryptStats stats[MAX_THREADS] = { 0 }; + int idx = 0; + while (idx < MAX_THREADS) + { + if (stats[idx].threadID == pthread_self()) + { + break; + } + idx++; + } + if (idx == MAX_THREADS) + { + // new thread + idx = 0; + while (idx < MAX_THREADS) + { + if (stats[idx].threadID == 0) + { + // empty slot + stats[idx].threadID = pthread_self(); + break; + } + idx++; + } + } + if (idx == MAX_THREADS) + { + MW_LOG_WARN("All slots allocated!!!, idx = %d, clearing the array.", idx); + memset(stats, 0, sizeof(DecryptStats) * MAX_THREADS); + return; + } + + if (nDataSize > 0) + { + stats[idx].nBytesInterval += (uint64_t) nDataSize; + stats[idx].nTimeInterval += delta; + stats[idx].nCallsTotal++; + + if (stats[idx].nCallsTotal % INTERVAL == 0) + { + stats[idx].nBytesTotal += stats[idx].nBytesInterval; + stats[idx].nTimeTotal += stats[idx].nTimeInterval; + double avgTime = (double) stats[idx].nTimeTotal / (double) stats[idx].nCallsTotal; + if (avgTime >= DECRYPT_AVG_TIME_THRESHOLD) + { + MW_LOG_WARN("%s Thread ID %zx (%d) Avg Time %0.2lf ms, Avg Bytes %" PRIu64 " calls (%" PRIu64 ") Interval avg time %0.2lf, Interval avg bytes %" PRIu64, + strFunc, GetThreadID(stats[idx].threadID), idx, avgTime, stats[idx].nBytesTotal / stats[idx].nCallsTotal, stats[idx].nCallsTotal, (double) stats[idx].nTimeInterval / (double) INTERVAL, stats[idx].nBytesInterval / INTERVAL); + } + stats[idx].nBytesInterval = 0; + stats[idx].nTimeInterval = 0; + + } + } + } +#endif //LOG_DECRYPT_STATS +} + +class BitStreamState +{ + private: + const guint8 *ptr; + gsize bit_offset; + public: + BitStreamState(const BitStreamState &L): + ptr(L.ptr), + bit_offset(L.bit_offset) {} // copy constructor + BitStreamState & operator=(const BitStreamState &L) + { + ptr = L.ptr; + bit_offset = L.bit_offset; + return *this; + } // assignment + BitStreamState(const guint8 *ptr ): + ptr(ptr), + bit_offset(0) + { + } + ~BitStreamState() + { + this->ptr = NULL; + this->bit_offset = 0; + } + + int Read( int bit_count = 1 ) + { + int rc = 0; + while( bit_count-- && ptr) + { + rc <<= 1; + int mask = 0x80>>(bit_offset&0x7); + if( ptr[bit_offset/8] & mask ) + { + rc |= 1; + } + bit_offset++; + } + return rc; + } +}; + +/** + * @brief Extract SEI + */ +void OCDMGSTSessionAdapter::ExtractSEI( GstBuffer *buffer) +{ + GstMapInfo info; + const guint8 *ptr; + gsize len; + if (buffer) + { + gst_buffer_map( buffer, &info, (GstMapFlags)(GST_MAP_READ) ); + ptr = info.data; + len = info.size; + } + else + { + MW_LOG_WARN("Invalid Buffer Input - NULL"); + gst_buffer_unmap(buffer, &info); + return; + } + + if (len > 2048) + len = 2048; + + for( int i=0; iisDecryptClearSamplesRequired()) + ) + { + MW_LOG_INFO("Skip decrypt of clear sample"); + retValue = 0; + } + else + { + std::lock_guard guard(decryptMutex); + uint64_t start_decrypt_time = GetCurrentTimeStampInMSec(); + + /* Added GST_IS_CAPS check also before passing gst caps to OCDM decrypt() as gst_caps_is_empty returns false when caps object is not of + type GST_TYPE_CAPS. This will avoid crash when caps is not of type GST_TYPE_CAPS. */ + if (OCDMGSTSessionDecrypt && !gst_caps_is_empty(caps) && GST_IS_CAPS(caps)) + { + GstProtectionMeta* protectionMeta = reinterpret_cast(gst_buffer_get_protection_meta(buffer)); + + if (protectionMeta != nullptr) { + gst_structure_set (protectionMeta->info, "subsample_count", G_TYPE_UINT, subSampleCount, "subsamples", GST_TYPE_BUFFER, subSamplesBuffer, "iv", GST_TYPE_BUFFER, ivBuffer, "kid", GST_TYPE_BUFFER, keyIDBuffer, "initWithLast15", G_TYPE_UINT, 0, NULL); + } else { + GstStructure *crypto_info = gst_structure_new ("protection_meta_info","subsample_count", G_TYPE_UINT, subSampleCount, "subsamples", GST_TYPE_BUFFER, subSamplesBuffer, "iv", GST_TYPE_BUFFER, ivBuffer, "kid", GST_TYPE_BUFFER, keyIDBuffer, "initWithLast15", G_TYPE_UINT, 0, NULL); + gst_buffer_add_protection_meta (buffer, crypto_info); + } + retValue = OCDMGSTSessionDecrypt(m_pOpenCDMSession, buffer, caps); + } + else + /* CID:328751 - Waiting while holding a lock, got detected due to usage of external API. It may be replaced if approach is redesigned in future */ + retValue = opencdm_gstreamer_session_decrypt(m_pOpenCDMSession, buffer, subSamplesBuffer, subSampleCount, ivBuffer, keyIDBuffer, 0); + uint64_t end_decrypt_time = GetCurrentTimeStampInMSec(); + if (retValue != 0) + { + GstMapInfo keyIDMap; + if (gst_buffer_map(keyIDBuffer, &keyIDMap, (GstMapFlags) GST_MAP_READ) == true) + { + uint8_t *mappedKeyID = reinterpret_cast(keyIDMap.data); + uint32_t mappedKeyIDSize = static_cast(keyIDMap.size); + #ifdef USE_THUNDER_OCDM_API_0_2 + KeyStatus keyStatus = opencdm_session_status(m_pOpenCDMSession, mappedKeyID, mappedKeyIDSize); + #else + KeyStatus keyStatus = opencdm_session_status(m_pOpenCDMSession, mappedKeyID,mappedKeyIDSize ); + #endif + MW_LOG_INFO("OCDMSessionAdapter: decrypt returned : %d key status is : %d", retValue, keyStatus); + #ifdef USE_THUNDER_OCDM_API_0_2 + if (keyStatus == OutputRestricted){ + #else + if(keyStatus == KeyStatus::OutputRestricted){ + #endif + retValue = HDCP_OUTPUT_PROTECTION_FAILURE; + } + #ifdef USE_THUNDER_OCDM_API_0_2 + else if (keyStatus == OutputRestrictedHDCP22){ + #else + else if(keyStatus == KeyStatus::OutputRestrictedHDCP22){ + #endif + retValue = HDCP_COMPLIANCE_CHECK_FAILURE; + } + gst_buffer_unmap(keyIDBuffer, &keyIDMap); + } + } + + GstMapInfo mapInfo; + if (gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) + { + if (mapInfo.size > 0) + { + LogPerformanceExt(__FUNCTION__, start_decrypt_time, end_decrypt_time, mapInfo.size); + } + gst_buffer_unmap(buffer, &mapInfo); + } + } + } + return retValue; +} + +int OCDMGSTSessionAdapter::decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData) +{ + int retValue = -1; + + if (m_pOpenCDMSession) + { + if (!verifyOutputProtection()) + { + return HDCP_COMPLIANCE_CHECK_FAILURE; + } + else + { + std::lock_guard guard(decryptMutex); + EncryptionScheme encScheme = AesCtr_Cenc; + EncryptionPattern pattern = {0}; + /* CID:313718 - Waiting while holding a lock, got detected due to usage of external API. It may be replaced if approach is redesigned in future */ + retValue = opencdm_session_decrypt(m_pOpenCDMSession, (uint8_t *)payloadData, payloadDataSize, encScheme, pattern, f_pbIV, f_cbIV, NULL, 0, 0); + if (retValue != 0) + { +#ifdef USE_THUNDER_OCDM_API_0_2 + KeyStatus keyStatus = opencdm_session_status(m_pOpenCDMSession, NULL, 0); +#else + KeyStatus keyStatus = opencdm_session_status(m_pOpenCDMSession, NULL, 0); +#endif + MW_LOG_INFO("OCDMSessionAdapter:%s : decrypt returned : %d key status is : %d", __FUNCTION__, retValue, keyStatus); +#ifdef USE_THUNDER_OCDM_API_0_2 + if (keyStatus == OutputRestricted) +#else + if(keyStatus == KeyStatus::OutputRestricted) +#endif + { + retValue = HDCP_OUTPUT_PROTECTION_FAILURE; + } +#ifdef USE_THUNDER_OCDM_API_0_2 + else if (keyStatus == OutputRestrictedHDCP22) +#else + else if(keyStatus == KeyStatus::OutputRestrictedHDCP22) +#endif + { + retValue = HDCP_COMPLIANCE_CHECK_FAILURE; + } + } + } + } + return retValue; +} + diff --git a/middleware/drm/ocdm/OcdmGstSessionAdapter.h b/middleware/drm/ocdm/OcdmGstSessionAdapter.h new file mode 100755 index 000000000..5d0c4473b --- /dev/null +++ b/middleware/drm/ocdm/OcdmGstSessionAdapter.h @@ -0,0 +1,58 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file OcdmGstSessionAdapter.h + * @brief File holds operations on OCDM gst sessions + */ + + +#include +#include +#include +#include "opencdmsessionadapter.h" +#include "PlayerLogManager.h" + +/** + * @class OCDMGSTSessionAdapter + * @brief OCDM Gstreamer session to decrypt + */ + +class OCDMGSTSessionAdapter : public OCDMSessionAdapter +{ + void ExtractSEI( GstBuffer *buffer); +public: + OCDMGSTSessionAdapter(DrmHelperPtr drmHelper, DrmCallbacks *drmCallbacks) : OCDMSessionAdapter(drmHelper, drmCallbacks) +, OCDMGSTSessionDecrypt(nullptr) + { + const char* ocdmgstsessiondecrypt = "opencdm_gstreamer_session_decrypt_buffer"; + OCDMGSTSessionDecrypt = (OpenCDMError(*)(struct OpenCDMSession*, GstBuffer*, GstCaps*))dlsym(RTLD_DEFAULT, ocdmgstsessiondecrypt); + if (OCDMGSTSessionDecrypt) + MW_LOG_WARN("Has opencdm_gstreamer_session_decrypt_buffer"); + else + MW_LOG_WARN("No opencdm_gstreamer_session_decrypt_buffer found"); + }; + ~OCDMGSTSessionAdapter() {}; + + int decrypt(GstBuffer* keyIDBuffer, GstBuffer* ivBuffer, GstBuffer* buffer, unsigned subSampleCount, GstBuffer* subSamplesBuffer, GstCaps* caps); + int decrypt(const uint8_t *f_pbIV, uint32_t f_cbIV, const uint8_t *payloadData, uint32_t payloadDataSize, uint8_t **ppOpaqueData); +private: + OpenCDMError(*OCDMGSTSessionDecrypt)(struct OpenCDMSession*, GstBuffer*, GstCaps*); + +}; diff --git a/middleware/drm/ocdm/opencdmsessionadapter.cpp b/middleware/drm/ocdm/opencdmsessionadapter.cpp new file mode 100644 index 000000000..0c9b8a6ba --- /dev/null +++ b/middleware/drm/ocdm/opencdmsessionadapter.cpp @@ -0,0 +1,422 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file opencdmsessionadapter.cpp + * @brief Handles operation with OCDM session to handle DRM License data + */ +#include "opencdmsessionadapter.h" + +#include "DrmHelper.h" +#include "PlayerUtils.h" + +#include "ProcessHandler.h" +#include "PlayerExternalsInterface.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" + +#include +#include +#define LICENSE_RENEWAL_MESSAGE_TYPE "1" + +/** + * @fn OCDMSessionAdapter + * @brief OCDMSessionAdapter constructor + */ +OCDMSessionAdapter::OCDMSessionAdapter(DrmHelperPtr drmHelper, DrmCallbacks *callbacks) : + DrmSession(drmHelper->ocdmSystemId()), + m_eKeyState(KEY_INIT), + m_pOpenCDMSystem(NULL), + m_pOpenCDMSession(NULL), + m_pOutputProtection(NULL), + decryptMutex(), + m_sessionID(), + m_challenge(), + timeBeforeCallback(0), + m_challengeReady(), + m_challengeSize(0), + m_keyStatus(InternalError), + m_keyStateIndeterminate(false), + m_keyStatusReady(), + m_OCDMSessionCallbacks(), + m_destUrl(), + m_drmHelper(drmHelper), + m_drmCallbacks(callbacks), + m_keyStatusWait(), + m_keyId(), + m_keyStored() +{ + MW_LOG_WARN("OCDMSessionAdapter :: enter "); + MW_LOG_WARN("OCDMSessionAdapter :: key process timeout is %d", drmHelper->keyProcessTimeout()); + + initDRMSystem(); + + // Get output protection pointer + m_pOutputProtection = PlayerExternalsInterface::GetPlayerExternalsInterfaceInstance(); + MW_LOG_WARN("OCDMSessionAdapter :: exit "); +} + + +void OCDMSessionAdapter::initDRMSystem() +{ + std::lock_guard guard(decryptMutex); + MW_LOG_WARN("initDRMSystem :: enter "); + if (m_pOpenCDMSystem == nullptr) { +#ifdef USE_THUNDER_OCDM_API_0_2 + m_pOpenCDMSystem = opencdm_create_system(m_keySystem.c_str()); +#else + m_pOpenCDMSystem = opencdm_create_system(); +#endif + if (m_pOpenCDMSystem == nullptr) { + MW_LOG_ERR("opencdm_create_system() FAILED"); + } + } + MW_LOG_WARN("initDRMSystem :: exit "); +} + + +OCDMSessionAdapter::~OCDMSessionAdapter() +{ + MW_LOG_WARN("[HHH]OCDMSessionAdapter destructor called! keySystem %s", m_keySystem.c_str()); + clearDecryptContext(); + + if (m_pOpenCDMSystem) { +#ifdef USE_THUNDER_OCDM_API_0_2 + opencdm_destruct_system(m_pOpenCDMSystem); +#endif + m_pOpenCDMSystem = NULL; + } + +} + + +void OCDMSessionAdapter::generateDRMSession(const uint8_t *f_pbInitData, + uint32_t f_cbInitData, std::string &customData) +{ + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + + std::lock_guard guard(decryptMutex); + if (m_pOpenCDMSystem == nullptr) + { + MW_LOG_WARN("OpenCDM system not present, unable to generate DRM session"); + m_eKeyState = KEY_ERROR; + } + else + { + memset(&m_OCDMSessionCallbacks, 0, sizeof(m_OCDMSessionCallbacks)); + timeBeforeCallback = GetCurrentTimeMS(); + m_OCDMSessionCallbacks.process_challenge_callback = [](OpenCDMSession* session, void* userData, const char destUrl[], const uint8_t challenge[], const uint16_t challengeSize) { + OCDMSessionAdapter* userSession = reinterpret_cast(userData); + userSession->timeBeforeCallback = ((GetCurrentTimeMS())-(userSession->timeBeforeCallback)); + MW_LOG_WARN( "Duration for process_challenge_callback %lld",(userSession->timeBeforeCallback)); + userSession->processOCDMChallenge(destUrl, challenge, challengeSize); + }; + + m_OCDMSessionCallbacks.key_update_callback = [](OpenCDMSession* session, void* userData, const uint8_t key[], const uint8_t keySize) { + OCDMSessionAdapter* userSession = reinterpret_cast(userData); + userSession->keyUpdateOCDM(key, keySize); + }; + + m_OCDMSessionCallbacks.error_message_callback = [](OpenCDMSession* session, void* userData, const char message[]) { + }; + + m_OCDMSessionCallbacks.keys_updated_callback = [](const OpenCDMSession* session, void* userData) { + OCDMSessionAdapter* userSession = reinterpret_cast(userData); + userSession->keysUpdatedOCDM(); + }; + const unsigned char *customDataMessage = customData.empty() ? nullptr:reinterpret_cast(customData.c_str()) ; + const uint16_t customDataMessageLength = customData.length(); + MW_LOG_INFO("data length : %d: ", customDataMessageLength); +#ifdef USE_THUNDER_OCDM_API_0_2 + OpenCDMError ocdmRet = opencdm_construct_session(m_pOpenCDMSystem, LicenseType::Temporary, "cenc", +#else + OpenCDMError ocdmRet = opencdm_construct_session(m_pOpenCDMSystem, m_keySystem.c_str(), LicenseType::Temporary, "cenc", +#endif + const_cast(f_pbInitData), f_cbInitData, + customDataMessage, customDataMessageLength, + &m_OCDMSessionCallbacks, + static_cast(this), + &m_pOpenCDMSession); + if (ocdmRet != ERROR_NONE) + { + MW_LOG_ERR("Error constructing OCDM session. OCDM err=0x%x", ocdmRet); + m_eKeyState = KEY_ERROR; + } + } +} + + +void OCDMSessionAdapter::processOCDMChallenge(const char destUrl[], const uint8_t challenge[], const uint16_t challengeSize) { + + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + + const std::string challengeData(reinterpret_cast(challenge), challengeSize); + const std::set individualisationTypes = {"individualization-request", "3"}; + const std::string delimiter(":Type:"); + const size_t delimiterPos = challengeData.find(delimiter); + const std::string messageType = challengeData.substr(0, delimiterPos); + + // Check if this message should be forwarded using a DRM callback. + // Example message: individualization-request:Type:(payload) + if ((delimiterPos != std::string::npos) && (individualisationTypes.count(messageType) > 0)) + { + MW_LOG_WARN("processOCDMChallenge received message with type=%s", messageType.c_str()); + + if (m_drmCallbacks) + { + m_drmCallbacks->Individualization(challengeData.substr(delimiterPos + delimiter.length())); + } + } + else + { + // Assuming this is a standard challenge callback + m_challenge = challengeData; + MW_LOG_WARN("processOCDMChallenge challenge = %s", m_challenge.c_str()); + + m_destUrl.assign(destUrl); + MW_LOG_WARN("processOCDMChallenge destUrl = %s (default value used as drm server)", m_destUrl.c_str()); + + m_challengeReady.signal(); + } + + if(messageType == LICENSE_RENEWAL_MESSAGE_TYPE) + { + if (m_drmCallbacks) + m_drmCallbacks->LicenseRenewal(m_drmHelper,static_cast (this)); + } +} + +void OCDMSessionAdapter::keyUpdateOCDM(const uint8_t key[], const uint8_t keySize) { + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + if (m_pOpenCDMSession) { + m_keyStatus = opencdm_session_status(m_pOpenCDMSession, key, keySize); + m_keyStateIndeterminate = false; + } + else { + m_keyStored.clear(); + m_keyStored.assign(key, key+keySize); + m_keyStateIndeterminate = true; + } + +} + +void OCDMSessionAdapter::keysUpdatedOCDM() { + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + m_keyStatusReady.signal(); +} + + +DrmData * OCDMSessionAdapter::generateKeyRequest(string& destinationURL, uint32_t timeout) +{ + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + DrmData * result = NULL; + + m_eKeyState = KEY_ERROR; + + if (m_challengeReady.wait(timeout) == true) { + if (m_challenge.empty() != true) { + std::string delimiter (":Type:"); + std::string requestType (m_challenge.substr(0, m_challenge.find(delimiter))); + if ( (requestType.size() != 0) && (requestType.size() != m_challenge.size()) ) { + (void) m_challenge.erase(0, m_challenge.find(delimiter) + delimiter.length()); + } + + result = new DrmData(m_challenge.c_str(), m_challenge.length()); + destinationURL.assign((m_destUrl.c_str())); + MW_LOG_WARN("destinationURL is %s (default value used as drm server)", destinationURL.c_str()); + m_eKeyState = KEY_PENDING; + } + else { + MW_LOG_WARN("Empty keyRequest"); + } + } else { + MW_LOG_WARN("Timed out waiting for keyRequest"); + } + return result; +} + + +int OCDMSessionAdapter::processDRMKey(DrmData* key, uint32_t timeout) +{ + MW_LOG_INFO("at %p, with %p, %p", this , m_pOpenCDMSystem, m_pOpenCDMSession); + int retValue = -1; + const uint8_t* keyMessage = NULL; + uint16_t keyMessageLength = 0; + + OpenCDMError status = OpenCDMError::ERROR_NONE; + + if (key) + { + keyMessage = (const uint8_t *)key->getData().c_str(); + keyMessageLength = key->getDataLength(); + } + + if (keyMessage) + { + MW_LOG_INFO("Calling opencdm_session_update, key length=%u", keyMessageLength); + status = opencdm_session_update(m_pOpenCDMSession, keyMessage, keyMessageLength); + } + else + { + // If no key data has been provided then this suggests the key acquisition + // will be performed by the DRM implementation itself. Hence there is no + // need to call opencdm_session_update + MW_LOG_INFO("NULL key data provided, assuming external key acquisition"); + } + + if (status == OpenCDMError::ERROR_NONE) { + if (m_keyStatusReady.wait(timeout) == true) { + MW_LOG_WARN("Key Status updated"); + } + // The key could be signalled ready before the session is even created, so we need to check we didn't miss it + if (m_keyStateIndeterminate) { + m_keyStatus = opencdm_session_status(m_pOpenCDMSession, m_keyStored.data(), m_keyStored.size()); + m_keyStateIndeterminate = false; + MW_LOG_WARN("Key arrived early, new state is %d", m_keyStatus); + } +#ifdef USE_THUNDER_OCDM_API_0_2 + if (m_keyStatus == Usable) { +#else + if (m_keyStatus == KeyStatus::Usable) { +#endif + MW_LOG_WARN("processKey: Key Usable!"); + m_eKeyState = KEY_READY; + retValue = 0; + } +#ifdef USE_THUNDER_OCDM_API_0_2 + else if(m_keyStatus == HWError) +#else + else if(m_keyStatus == KeyStatus::HWError) +#endif + { + // SAGE Hang .. Need to restart the wpecdmi process and then self kill player to recover + MW_LOG_WARN("processKey: Update() returned HWError.Restarting process..."); + ProcessHandler processHandler; + // In Release another process handles opencdm which needs to be restarts .In Sprint this process is not available. + // So check if process exists before killing it . + if (processHandler.KillProcess("WPEFramework")) /** Current OCDM process **/ + { + MW_LOG_WARN("OCDM HWError reported.. Killed the process WPEFramework for recovery.."); + } + else + { + if(processHandler.KillProcess("WPEcdmi")) /** Backword compatibility **/ + { + MW_LOG_WARN("OCDM HWError reported.. Killed the process WPEcdmi for recovery.."); + } + } + + // wait for 5sec for all the logs to be flushed + sleep(5); + // Now kill self + processHandler.SelfKill(); + } + else { +#ifdef USE_THUNDER_OCDM_API_0_2 + if(m_keyStatus == OutputRestricted) +#else + if(m_keyStatus == KeyStatus::OutputRestricted) +#endif + { + MW_LOG_WARN("processKey: Update() Output restricted keystatus: %d", (int) m_keyStatus); + retValue = HDCP_OUTPUT_PROTECTION_FAILURE; + } +#ifdef USE_THUNDER_OCDM_API_0_2 + else if(m_keyStatus == OutputRestrictedHDCP22) +#else + else if(m_keyStatus == KeyStatus::OutputRestrictedHDCP22) +#endif + { + MW_LOG_WARN("processKey: Update() Output Compliance error keystatus: %d\n", (int) m_keyStatus); + retValue = HDCP_COMPLIANCE_CHECK_FAILURE; + } + else + { + MW_LOG_WARN("processKey: Update() returned keystatus: %d\n", (int) m_keyStatus); + retValue = (int) m_keyStatus; + } + m_eKeyState = KEY_ERROR; + } + } + m_keyStatusWait.signal(); + return retValue; +} + + +bool OCDMSessionAdapter::waitForState(KeyState state, const uint32_t timeout) +{ + if (m_eKeyState == state) { + return true; + } + if (!m_keyStatusWait.wait(timeout)) { + return false; + } + return m_eKeyState == state; +} + + +KeyState OCDMSessionAdapter::getState() +{ + return m_eKeyState; +} + + +void OCDMSessionAdapter:: clearDecryptContext() +{ + MW_LOG_WARN("[HHH] clearDecryptContext."); + + std::lock_guard guard(decryptMutex); + + if (m_pOpenCDMSession) { + opencdm_session_close(m_pOpenCDMSession); + opencdm_destruct_session(m_pOpenCDMSession); + m_pOpenCDMSession = NULL; + } + m_eKeyState = KEY_INIT; +} + +void OCDMSessionAdapter::setKeyId(const std::vector& keyId) +{ + m_keyId = keyId; +} + +bool OCDMSessionAdapter::verifyOutputProtection() +{ + if (m_drmHelper->isHdcp22Required() && m_pOutputProtection->IsSourceUHD()) + { + // Source material is UHD + if (!m_pOutputProtection->isHDCPConnection2_2()) + { + // UHD and not HDCP 2.2 + MW_LOG_WARN("UHD source but not HDCP 2.2. FAILING decrypt"); + return false; + } + } + + return true; +} diff --git a/middleware/drm/ocdm/opencdmsessionadapter.h b/middleware/drm/ocdm/opencdmsessionadapter.h new file mode 100644 index 000000000..d633c4f75 --- /dev/null +++ b/middleware/drm/ocdm/opencdmsessionadapter.h @@ -0,0 +1,146 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef OpenCDMSessionAdapter_h +#define OpenCDMSessionAdapter_h + +/** + * @file opencdmsessionadapter.h + * @brief Handles operation with OCDM session to handle DRM License data + */ + +#include "PlayerExternalsInterface.h" +#include "DrmSession.h" +#include "DrmHelper.h" + +#include "open_cdm.h" +#include "open_cdm_adapter.h" +#include +#include "DrmCallbacks.h" + +using namespace std; + +/** + * @class Event + * @brief class to DRM Event handle + */ + +class Event { +private: + bool signalled; //TODO: added to handle the events fired before calling wait, need to recheck + std::mutex lock; + std::condition_variable condition; +public: + Event() : signalled(false), lock(), condition() { + } + virtual ~Event() { + } + + inline bool wait(const uint32_t waitTime) + { + bool ret = true; + std::unique_lock _lock(lock); + if (!signalled) { + if (waitTime == 0) { + condition.wait(_lock); + } else { + if( std::cv_status::timeout == condition.wait_for(_lock,std::chrono::milliseconds(waitTime)) ) + { + ret = false; + } + } + } + signalled = false; + return ret; + } + + inline void signal() + { + std::unique_lock _lock(lock); + signalled = true; + condition.notify_all(); + } +}; + +/** + * @class OCDMSessionAdapter + * @brief Open CDM DRM session + */ +class OCDMSessionAdapter : public DrmSession +{ +protected: + std::mutex decryptMutex; + + KeyState m_eKeyState; + + OpenCDMSession* m_pOpenCDMSession; +#ifdef USE_THUNDER_OCDM_API_0_2 + struct OpenCDMSystem* m_pOpenCDMSystem; +#else + struct OpenCDMAccessor* m_pOpenCDMSystem; +#endif + OpenCDMSessionCallbacks m_OCDMSessionCallbacks; + std::shared_ptr m_pOutputProtection; + + std::string m_challenge; + uint16_t m_challengeSize; + + std::string m_destUrl; + KeyStatus m_keyStatus; + bool m_keyStateIndeterminate; + std::vector m_keyStored; + + Event m_challengeReady; + Event m_keyStatusReady; + Event m_keyStatusWait; + string m_sessionID; + + std::vector m_keyId; + + DrmHelperPtr m_drmHelper; + DrmCallbacks *m_drmCallbacks; + + bool verifyOutputProtection(); +public: + void processOCDMChallenge(const char destUrl[], const uint8_t challenge[], const uint16_t challengeSize); + void keysUpdatedOCDM(); + void keyUpdateOCDM(const uint8_t key[], const uint8_t keySize); + long long timeBeforeCallback; + +private: + void initDRMSystem(); + +public: + OCDMSessionAdapter(DrmHelperPtr drmHelper, DrmCallbacks *callbacks = nullptr); + ~OCDMSessionAdapter(); + OCDMSessionAdapter(const OCDMSessionAdapter&) = delete; + OCDMSessionAdapter& operator=(const OCDMSessionAdapter&) = delete; + void generateDRMSession(const uint8_t *f_pbInitData, + uint32_t f_cbInitData, std::string &customData) override; + DrmData * generateKeyRequest(string& destinationURL, uint32_t timeout) override; + int processDRMKey(DrmData* key, uint32_t timeout) override; + KeyState getState() override; + void clearDecryptContext() override; +#if defined(USE_OPENCDM_ADAPTER) + void setKeyId(const std::vector& keyId) override; +#endif + bool waitForState(KeyState state, const uint32_t timeout) override; +}; + +#endif diff --git a/middleware/drm/processProtectionHls.cpp b/middleware/drm/processProtectionHls.cpp new file mode 100755 index 000000000..791c3c12f --- /dev/null +++ b/middleware/drm/processProtectionHls.cpp @@ -0,0 +1,259 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* \file processProtection.cpp +* +* Process protection is for handling process protection of open cdm drm +* for HSL Streaming +* Functionalities are parse the file , get the drm type , PSSH data. +* Create DRM Session in thread +* +*/ +#include "_base64.h" +#include "DrmSession.h" +#include "DrmHelper.h" + +#include "PlayerLogManager.h" +#include +#include +using namespace std; + + +/** + * Global config data + */ +shared_ptr ProcessContentProtection(std::string attrName, bool propagateURIParam , bool isSamplesRequired); + +/** + * Local APIs declarations + */ +static int GetFieldValue(string &attrName, string keyName, string &valuePtr); +static int getPsshData(string attrName, string &psshData); +static shared_ptr getDrmHelper(string attrName , bool bPropagateUriParams, bool bDecryptClearSamplesRequired); + +/** + * @brief Return the string value, from the input KEY="value" + * @param [in] attribute list to be searched + * @param [in] Key name to be checked to get the value + * @param [out] value of the key + * @return none + */ +static int GetFieldValue(string &attrName, string keyName, string &valuePtr){ + + int valueStartPos = 0; + int valueEndPos = 0; + int keylen = (int)keyName.length(); + int status = DRM_API_FAILED; + int found = 0, foundpos = 0; + + MW_LOG_TRACE("Entering.."); + + while ( (foundpos = (int)attrName.find(keyName,found)) != std::string::npos) + { + MW_LOG_TRACE("keyName = %s", + keyName.c_str()); + + valueStartPos = foundpos + keylen; + if (attrName.at(valueStartPos) == '=') + { + string valueTempPtr = attrName.substr(valueStartPos+1); + + MW_LOG_TRACE("valueTempPtr = %s", + valueTempPtr.c_str()); + + /* update start position based on substring */ + valueStartPos = 0; + if (valueTempPtr.at(0) == '"') + { + valueTempPtr = valueTempPtr.substr(1); + valueEndPos = (int)valueTempPtr.find('"'); + } + else if ( (valueEndPos = (int)valueTempPtr.find(',')) == std::string::npos) + { + /*look like end string*/ + valueEndPos = (int)valueTempPtr.length(); + } + + valuePtr = valueTempPtr.substr(valueStartPos, valueEndPos); + MW_LOG_INFO("Value found : %s for Key : %s", + valuePtr.c_str(), keyName.c_str()); + status = DRM_API_SUCCESS; + break; + } + else + { + MW_LOG_TRACE("Checking next occurrence of %s= in %s", + keyName.c_str(), attrName.c_str()); + found = valueStartPos+1; + } + } + + if(DRM_API_SUCCESS != status) + { + MW_LOG_ERR("Could not able to find %s in %s", + keyName.c_str(), attrName.c_str()); + } + + return status; +} + +/** + * @brief API to get the PSSH Data from the manifest attribute list, getPsshData + * @param [in] Attribute list + * @param [out] pssData as reference + * @return status of the API + */ +static int getPsshData(string attrName, string &psshData){ + + int status = GetFieldValue(attrName, "URI", psshData ); + if(DRM_API_SUCCESS != status){ + MW_LOG_ERR("Could not able to get psshData from manifest" + ); + return status; + } + /* Split string based on , and get the PSSH Data */ + psshData = psshData.substr(psshData.find(',')+1); + + return status; +} + +/** + * @brief API to get the DRM helper from the manifest attribute list, getDrmType + * @param [in] Attribute list + * + * @return DrmHelper - DRM Helper (nullptr in case of unexpected behavior) + */ +static DrmHelperPtr getDrmHelper(string attrName , bool bPropagateUriParams, bool bDecryptClearSamplesRequired){ + + string systemId = ""; + + if(DRM_API_SUCCESS != GetFieldValue(attrName, "KEYFORMAT", systemId )){ + MW_LOG_ERR("Could not able to receive key id from manifest" + ); + return nullptr; + } + + /** Remove urn:uuid: from it */ + if (systemId.find("urn:uuid:") != std::string::npos){ + systemId = systemId.substr(strlen("urn:uuid:")); + } + DrmInfo drmInfo; + drmInfo.mediaFormat = eMEDIAFORMAT_HLS_MP4; + drmInfo.systemUUID = std::move(systemId); + drmInfo.bPropagateUriParams = bPropagateUriParams; + drmInfo.bDecryptClearSamplesRequired = bDecryptClearSamplesRequired; + return DrmHelperEngine::getInstance().createHelper(drmInfo); +} + +/** + * @brief Process content protection of track + * @param attrName attribute value + * @param propagateURIParam propagate URI value + * @param isSamplesRequired boolean for sample required + * @return shared_ptr to the DrmHelper instance + */ +shared_ptr ProcessContentProtection( std::string attrName, bool propagateURIParam , bool isSamplesRequired) +{ + /* StreamAbstraction_HLS* context; */ + /* Pseudo code for ProcessContentProtection in HLS is below + * 1. Create DrmHelper object based on attribute value + * 2. Get pssh data from manifest (extract URI value) + * 3. Set PSSH data to DrmHelper object + */ + shared_ptr finalDrmHelper = nullptr; + unsigned char* data = NULL; + size_t dataLength = 0; + int status = DRM_API_FAILED; + string psshDataStr = ""; + char* psshData = NULL; + + do + { + shared_ptr drmHelper = getDrmHelper(attrName, propagateURIParam, isSamplesRequired); + if (nullptr == drmHelper) + { + MW_LOG_ERR("Failed to get DRM type/helper from manifest!"); + break; + } + + status = getPsshData(std::move(attrName), psshDataStr); + if (DRM_API_SUCCESS != status) + { + MW_LOG_ERR("Failed to get PSSH Data from manifest!"); + break; + } + psshData = (char*) malloc(psshDataStr.length() + 1); + memset(psshData, 0x00 , psshDataStr.length() + 1); + strncpy(psshData, psshDataStr.c_str(), psshDataStr.length()); + + if(drmHelper->friendlyName().compare("Verimatrix") == 0) + { + MW_LOG_WARN( "Verimatrix DRM" ); + data = (unsigned char *)psshData; + dataLength = psshDataStr.length(); + } + else + { + data = base64_Decode(psshData, &dataLength); + /* No more use */ + free(psshData); + psshData = NULL; + } + + if (dataLength == 0) + { + MW_LOG_ERR("Could not able to retrive DRM data from PSSH"); + break; + } + if (PlayerLogManager::isLogLevelAllowed(mLOGLEVEL_TRACE)) + { + MW_LOG_TRACE("content metadata from manifest; length %zu", dataLength); + printf("*****************************************************************\n"); + for (int i = 0; i < dataLength; i++) + { + printf("%c", data[i]); + } + printf("\n*****************************************************************\n"); + for (int i = 0; i < dataLength; i++) + { + printf("%02x ", data[i]); + } + printf("\n*****************************************************************\n"); + + } + if (!drmHelper->parsePssh(data, (uint32_t)dataLength)) + { + MW_LOG_ERR("Failed to get key Id from manifest"); + break; + } + // After processing the PSSH information, return the drmHelper + finalDrmHelper = std::move(drmHelper); + } while(0); + + if(data) + { + free(data); //CID:128617 - Resource leak + } + return finalDrmHelper; +} + +/** + * EOF + */ diff --git a/middleware/externals/CMakeLists.txt b/middleware/externals/CMakeLists.txt new file mode 100644 index 000000000..b1a886a1f --- /dev/null +++ b/middleware/externals/CMakeLists.txt @@ -0,0 +1,172 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.5) + +project(playerfbinterface_LIB) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +option(DISABLE_SECURITY_TOKEN "Disable security token" OFF) + +if(DISABLE_SECURITY_TOKEN) + add_definitions(-DDISABLE_SECURITY_TOKEN) +endif() + +set(MW_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../") + +find_package(PkgConfig REQUIRED) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) +pkg_check_modules(GSTREAMERBASE REQUIRED gstreamer-app-1.0) +pkg_check_modules(GSTREAMERVIDEO REQUIRED gstreamer-video-1.0) + +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${MW_ROOT}/playerLogManager) +include_directories(${MW_ROOT}/baseConversion) +include_directories(${MW_ROOT}/playerJsonObject) +include_directories(${MW_ROOT}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/rdk) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/contentsecuritymanager) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/contentsecuritymanager/IFirebolt) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/rdk/IIarm) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/rdk/IFirebolt) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/IFirebolt) + +option(DISABLE_SECURITY_TOKEN "Disable security token" OFF) + +if(DISABLE_SECURITY_TOKEN) + add_definitions(-DDISABLE_SECURITY_TOKEN) +endif() + +# uncomment below to build additional drm support in simulator +# set(CMAKE_USE_OPENCDM_ADAPTER TRUE) +# set(CMAKE_USE_OPENCDM_ADAPTER_MOCKS TRUE) +# set(CMAKE_USE_THUNDER_OCDM_API_0_2 TRUE) +# set(CMAKE_USE_SECCLIENT TRUE) +# set(CMAKE_USE_SECCLIENT_MOCKS TRUE) +set(LIB_EXT_DEPENDS ${LIB_EXT_DEPENDS} ${GSTREAMER_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES}) + +# Library source files +set(EXT_SOURCES PlayerExternalsInterface.cpp PlayerRfc.cpp PlayerExternalUtils.cpp) +set(SECMGR_HEADERS contentsecuritymanager/ContentSecurityManager.h ) + +set(THUNDER_INTERFACE_SRCS PlayerThunderInterface.cpp) + +#include WPEFRAMEWORK stuff +if (CMAKE_WPEFRAMEWORK_REQUIRED) + message("CMAKE_WPEFRAMEWORK_REQUIRED set") + list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/") + set(LIB_EXT_DEFINES "${LIB_EXT_DEFINES} -DUSE_CPP_THUNDER_PLUGIN_ACCESS") + set(THUNDER_INTERFACE_SRCS ${THUNDER_INTERFACE_SRCS} rdk/PlayerThunderAccess.cpp Module.cpp) + find_package(WPEFramework REQUIRED) + + if (WPEFRAMEWORK_FOUND) + message("WPEFRAMEWORK_FOUND = ${WPEFRAMEWORK_FOUND}") + include_directories(SYSTEM ${WPEFRAMEWORK_INCLUDE_DIRS}) + list(APPEND LIB_EXT_DEPENDS ${WPEFRAMEWORK_LIBRARIES}) + else() + if (USE_THUNDER_R4) + message(FATAL_ERROR "WPEFrameworkCOM not found") + else() + message(FATAL_ERROR "WPEFrameworkProtocols not found") + endif() + endif() +endif() + +set(SECMGR_SOURCES contentsecuritymanager/PlayerMemoryUtils.cpp + contentsecuritymanager/PlayerSecInterface.cpp + contentsecuritymanager/ContentSecurityManager.cpp + contentsecuritymanager/ContentSecurityManagerSession.cpp + ${MW_ROOT}/PlayerScheduler.cpp) + +if(CMAKE_USE_SECCLIENT OR CMAKE_USE_SECMANAGER) + set(SECMGR_SOURCES ${SECMGR_SOURCES} contentsecuritymanager/IFirebolt/ContentProtectionFirebolt.cpp) + if(CMAKE_USE_SECMANAGER) + set(SECMGR_SOURCES ${SECMGR_SOURCES} contentsecuritymanager/ThunderAccessPlayer.cpp contentsecuritymanager/SecManagerThunder.cpp) + endif() +endif() + +#IARM/RFC +if(CMAKE_IARM_MGR) + message("PLAYER IARM_MGR set") + set(LIB_EXT_DEFINES "${LIB_EXT_DEFINES} -DIARM_MGR=1") + set(EXT_SOURCES "${EXT_SOURCES}" rdk/PlayerExternalsRdkInterface.cpp) + set(EXT_SOURCES "${EXT_SOURCES}" rdk/IIarm/DeviceIARMInterface.cpp) + set(EXT_SOURCES "${EXT_SOURCES}" rdk/IFirebolt/DeviceFireboltInterface.cpp) + set(EXT_SOURCES "${EXT_SOURCES}" IFirebolt/FireboltInterface.cpp) + list(APPEND LIB_EXT_DEPENDS -lIARMBus -lds -ldshalcli) +endif() + +if(CMAKE_AAMP_RFC_REQUIRED) + message("PLAYER RFC set") + set(LIB_EXT_DEFINES "${LIB_EXT_DEFINES} -DPLAYER_RFC_ENABLED") + list(APPEND LIB_EXT_DEPENDS -ltr181api) +endif() + +#secmanager/client macros +if(CMAKE_USE_SECCLIENT) + message("CMAKE_USE_SECCLIENT set") + set(LIB_EXT_DEPENDS "${LIB_EXT_DEPENDS} -lFireboltSDK") + set(LIB_EXT_DEFINES "${LIB_EXT_DEFINES} -DUSE_SECCLIENT") + list(APPEND LIB_EXT_DEPENDS -lSecClient ) +endif() +if(CMAKE_USE_SECMANAGER) + message("CMAKE_USE_SECMANAGER set") + set(LIB_EXT_DEPENDS "${LIB_EXT_DEPENDS} -lFireboltSDK") + set(LIB_EXT_DEFINES "${LIB_EXT_DEFINES} -DUSE_SECMANAGER") +endif() + +# Define the shared library +if(NOT TARGET playerfbinterface) + add_library(playerfbinterface SHARED ${EXT_SOURCES} ${SECMGR_SOURCES} ${THUNDER_INTERFACE_SRCS}) +endif() + +# Set initial public headers for the library +set_target_properties(playerfbinterface PROPERTIES PUBLIC_HEADER + "PlayerThunderInterface.h;PlayerThunderAccessBase.h;PlayerExternalsInterfaceBase.h;PlayerExternalsInterface.h;PlayerRfc.h;contentsecuritymanager/ContentSecurityManagerSession.h;contentsecuritymanager/PlayerSecInterface.h;contentsecuritymanager/ContentSecurityManager.h;contentsecuritymanager/PlayerMemoryUtils.h;Module.h") + +if(CMAKE_IARM_MGR) + # Append additional header(s) for IARM_MGR + set_property(TARGET playerfbinterface APPEND PROPERTY PUBLIC_HEADER "rdk/PlayerExternalsRdkInterface.h") +endif() + +if(CMAKE_WPEFRAMEWORK_REQUIRED) + # Append additional header(s) for WPEFramework usage + set_property(TARGET playerfbinterface APPEND PROPERTY PUBLIC_HEADER "rdk/PlayerThunderAccess.h") +endif() + +set_target_properties(playerfbinterface PROPERTIES COMPILE_FLAGS "${LIB_EXT_DEFINES}") + +string(STRIP "${LIB_EXT_DEPENDS}" LIB_EXT_DEPENDS) + +target_link_libraries(playerfbinterface ${LIB_EXT_DEPENDS}) +target_link_libraries(playerfbinterface playerlogmanager) +target_link_libraries(playerfbinterface baseconversion) +target_link_libraries(playerfbinterface playerjsonobject) +if(CMAKE_USE_SECCLIENT OR CMAKE_USE_SECMANAGER) + find_package(FireboltAamp CONFIG REQUIRED) + target_link_libraries(playerfbinterface FireboltAamp::FireboltAamp) +endif() + +# Install the library and its headers +install(TARGETS playerfbinterface + DESTINATION lib + PUBLIC_HEADER DESTINATION include) diff --git a/middleware/externals/IFirebolt/FireboltInterface.cpp b/middleware/externals/IFirebolt/FireboltInterface.cpp new file mode 100644 index 000000000..8b2b33347 --- /dev/null +++ b/middleware/externals/IFirebolt/FireboltInterface.cpp @@ -0,0 +1,124 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file FireboltInterface.cpp + * @brief Firebolt common interface + */ + +#include "FireboltInterface.h" +#include "PlayerLogManager.h" + +#include +#include + +std::shared_ptr s_pFireboltInterface = nullptr; + +std::mutex mFireboltInterfaceConnectionMutex; +std::condition_variable mFireboltInterfaceConnectionCV; + +std::shared_ptr FireboltInterface::GetInstance() +{ + if(nullptr == s_pFireboltInterface) + { + s_pFireboltInterface = std::shared_ptr(new FireboltInterface()); + } + return s_pFireboltInterface; +} + +FireboltInterface::FireboltInterface() +{ + const char* firebolt_endpoint = std::getenv("FIREBOLT_ENDPOINT"); + + if (!firebolt_endpoint) { + MW_LOG_ERR("FIREBOLT_ENDPOINT not set; cannot initialize Firebolt"); + return; + } + std::string url = firebolt_endpoint; + if (!CreateFireboltInstance(url)) + { + MW_LOG_ERR("Failed to create FireboltInstance URL: [%s]", url.c_str()); + return; + } + /*Wait Time is 500 millisecond*/ + std::unique_lock mLock(mFireboltInterfaceConnectionMutex); + if (!mFireboltInterfaceConnectionCV.wait_for(mLock, std::chrono::milliseconds(500), [this] { return mIsConnected; })) { + MW_LOG_ERR("Firebolt Core To Be Initialized URL: [%s] Failed(Timeout) after 500ms", url.c_str()); + return; + } + + MW_LOG_WARN("Firebolt ContentProtection initialized with URL: [%s]", url.c_str()); +} + +bool FireboltInterface::CreateFireboltInstance(const std::string &url) +{ + const std::string config = "{\ + \"waitTime\": 5000,\ + \"logLevel\": \"Info\",\ + \"workerPool\":{\ + \"queueSize\": 8,\ + \"threadCount\": 3\ + },\ + \"wsUrl\": " + url + + "}"; + + auto callback = [this](bool connected, Firebolt::Error error) { + this->ConnectionChanged(connected, static_cast(error)); + }; + mIsConnected = false; + MW_LOG_ERR("CreateFireboltInstance url: %s -- config : %s", url.c_str(), config.c_str()); + Firebolt::Error errorInitialize = Firebolt::IFireboltAampAccessor::Instance().Initialize(config); + if (errorInitialize != Firebolt::Error::None) + { + MW_LOG_ERR("Failed to create FireboltInstance InitializeError:\"%d\"", static_cast(errorInitialize)); + return false; + } + auto errorConnect = Firebolt::IFireboltAampAccessor::Instance().Connect(callback); + if (!errorConnect) + { + MW_LOG_ERR("Failed to create FireboltInstance ConnectError:\"%d\"", static_cast(errorConnect.error())); + return false; + } + mListenerId = *errorConnect; + MW_LOG_INFO("Firebolt Instance created successfully, Connected to Firebolt!"); + return true; +} + +void FireboltInterface::ConnectionChanged(const bool connected, int error) +{ + MW_LOG_WARN("Firebolt connection changed. Connected: %d Error : %d", connected, error); + { + std::lock_guard lock(mFireboltInterfaceConnectionMutex); + mIsConnected = connected; + } + mFireboltInterfaceConnectionCV.notify_one(); +} + +void FireboltInterface::DestroyFireboltInstance() +{ + MW_LOG_WARN("Destroying Firebolt instance"); + Firebolt::IFireboltAampAccessor::Instance().Disconnect(mListenerId); +} + +FireboltInterface::~FireboltInterface() +{ + Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().unsubscribeAll(); + Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().unsubscribeAll(); + DestroyFireboltInstance(); +} \ No newline at end of file diff --git a/middleware/externals/IFirebolt/FireboltInterface.h b/middleware/externals/IFirebolt/FireboltInterface.h new file mode 100644 index 000000000..6714199cb --- /dev/null +++ b/middleware/externals/IFirebolt/FireboltInterface.h @@ -0,0 +1,57 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file FireboltInterface.h + * @brief Firebolt common interface + */ + +#pragma once + +#include "fireboltaamp.h" + +#include + +class FireboltInterface{ + + public: + + FireboltInterface(const FireboltInterface&) = delete; + + FireboltInterface& operator=(const FireboltInterface&) = delete; + + static std::shared_ptr GetInstance(); + + ~FireboltInterface(); + + private: + + bool mIsConnected = false; + + unsigned int mListenerId; + + FireboltInterface(); + + bool CreateFireboltInstance(const std::string &url); + + void ConnectionChanged(const bool connected, int error); + + void DestroyFireboltInstance(); + +}; \ No newline at end of file diff --git a/middleware/externals/Module.cpp b/middleware/externals/Module.cpp new file mode 100644 index 000000000..59d233cd1 --- /dev/null +++ b/middleware/externals/Module.cpp @@ -0,0 +1,31 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file Module.cpp + * @brief Declaration of module name + */ + +#include "Module.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#include +#pragma GCC diagnostic pop + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/middleware/externals/Module.h b/middleware/externals/Module.h new file mode 100644 index 000000000..6e32f0aa2 --- /dev/null +++ b/middleware/externals/Module.h @@ -0,0 +1,34 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file Module.h + * @brief Declaration of module name + */ + +#pragma once + + +#ifndef MODULE_NAME +#define MODULE_NAME playerfbinterface +#endif + + +#undef EXTERNAL +#define EXTERNAL diff --git a/middleware/externals/PlayerExternalUtils.cpp b/middleware/externals/PlayerExternalUtils.cpp new file mode 100644 index 000000000..bc7294a40 --- /dev/null +++ b/middleware/externals/PlayerExternalUtils.cpp @@ -0,0 +1,68 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file playerExternalUtils.cpp + * @brief Utilities for external's functionalities + */ + +#include "PlayerExternalUtils.h" + +#include +#include "PlayerLogManager.h" + +/** + * Hack to check if code is running in container environment. + * @return True if running in container environment, false otherwise. + */ +bool IsContainerEnvironment(void) +{ + static bool isContainer; + static bool isValid; + if( !isValid ) + { + struct stat buffer; + if (stat("/etc/device.properties", &buffer) == 0) + { // if we can access file, infer that are are NOT running in container + MW_LOG_MIL("not running in container environment"); + isContainer = false; + } + else + { // if we cannot access file, infer that we ARE running in container + MW_LOG_WARN("detected container environment"); + isContainer = true; + } + isValid = true; + } + return isContainer; +} + +/** + * @brief Sleep for given milliseconds + */ +void ms_sleep(int milliseconds) +{ + struct timespec req, rem; + if (milliseconds > 0) + { + req.tv_sec = milliseconds / 1000; + req.tv_nsec = (milliseconds % 1000) * 1000000; + nanosleep(&req, &rem); + } +} diff --git a/middleware/externals/PlayerExternalUtils.h b/middleware/externals/PlayerExternalUtils.h new file mode 100644 index 000000000..cc7120dc9 --- /dev/null +++ b/middleware/externals/PlayerExternalUtils.h @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file playerExternalUtils.h + * @brief Utilities for external's functionalities + */ + +#ifndef PLAYER_EXTERNAL_UTILS +#define PLAYER_EXTERNAL_UTILS + +#include +#include + +#define MW_PRE_LOGGER_LOG(fmt, ...) \ + do { \ + std::printf("[MIDDLEWARE] %s:%d %s: " fmt, __FILE__, __LINE__, \ + __func__ , ##__VA_ARGS__); \ + std::fflush(stdout); \ + } while (0) + + +/** + * Hack to check if code is running in container environment. + * @return True if running in container environment, false otherwise. + */ +bool IsContainerEnvironment(void); +/** + * @brief Sleep for given milliseconds + */ +void ms_sleep(int milliseconds); +#endif diff --git a/middleware/externals/PlayerExternalsInterface.cpp b/middleware/externals/PlayerExternalsInterface.cpp new file mode 100644 index 000000000..ebb6d2634 --- /dev/null +++ b/middleware/externals/PlayerExternalsInterface.cpp @@ -0,0 +1,150 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerExternalsInterface.cpp + * @brief Output protection management for Player + */ + +#include "PlayerExternalsInterface.h" +#include "PlayerExternalUtils.h" + +#ifdef IARM_MGR +#include "PlayerExternalsRdkInterface.h" +#endif + +/**< Static variable for singleton */ +std::shared_ptr PlayerExternalsInterface::s_pPlayerOP = NULL; + +/** + * @brief PlayerExternalsInterface Constructor + */ +PlayerExternalsInterface::PlayerExternalsInterface() +{ +#ifdef IARM_MGR + MW_PRE_LOGGER_LOG("Device API IARM/Firebolt\n"); + m_pIarmInterface = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); +#else + MW_PRE_LOGGER_LOG("Device API FAKE\n"); + m_pIarmInterface = std::shared_ptr(new FakePlayerExternalsInterface()); +#endif + +} + +/** + * @brief PlayerExternalsInterface Destructor + */ +PlayerExternalsInterface::~PlayerExternalsInterface() +{ + m_pIarmInterface = nullptr; + s_pPlayerOP = NULL; +} + +void PlayerExternalsInterface::Initialize() +{ + if(s_pPlayerOP != NULL) + { + MW_PRE_LOGGER_LOG("PlayerExternalsInterface::Initialize\n"); + m_pIarmInterface->Initialize(); + } + else + { + MW_PRE_LOGGER_LOG("PlayerExternalsInterface not found to initialize\n"); + } +} + +/** + * @brief Check if source is UHD using video decoder dimensions + */ +bool PlayerExternalsInterface::IsSourceUHD() +{ + return m_pIarmInterface->IsSourceUHD(); +} + +/** + * @brief gets display resolution + */ +void PlayerExternalsInterface::GetDisplayResolution(int &width, int &height) +{ + m_pIarmInterface->GetDisplayResolution(width, height); +} + +/** + * @brief Check if PlayerExternalsInterfaceInstance active + */ +bool PlayerExternalsInterface::IsPlayerExternalsInterfaceInstanceActive() +{ + bool retval = false; + + if(s_pPlayerOP != NULL) { + retval = true; + } + return retval; +} + +/** + * @brief Singleton for object creation + */ +std::shared_ptr PlayerExternalsInterface::GetPlayerExternalsInterfaceInstance() +{ + if(s_pPlayerOP == NULL) { + s_pPlayerOP = std::shared_ptr(new PlayerExternalsInterface()); + } + + return s_pPlayerOP; +} + +/** + * @brief gets paramName TR181 config + */ +char * PlayerExternalsInterface::GetTR181PlayerConfig(const char * paramName, size_t & iConfigLen) +{ + char * sRet = nullptr; + sRet = m_pIarmInterface->GetTR181Config(paramName, iConfigLen); + return sRet; +} + +/** + * @brief gets current active interface + */ +bool PlayerExternalsInterface::GetActiveInterface() +{ + bool bRet = false; + bRet = m_pIarmInterface->GetActiveInterface(); + return bRet; +} + +/** + * @brief checks if Wifi Curl Header ought to be configured + */ +bool PlayerExternalsInterface::IsConfigWifiCurlHeader() +{ + bool bRet = false; +#ifdef IARM_MGR + bRet = true; +#else + bRet = false; +#endif + return bRet; +} + +void PlayerExternalsInterface::SetUseFireBoltSDK(bool t_use_firebolt_sdk) +{ + m_pIarmInterface->SetUseFireBoltSDK(t_use_firebolt_sdk); +} diff --git a/middleware/externals/PlayerExternalsInterface.h b/middleware/externals/PlayerExternalsInterface.h new file mode 100644 index 000000000..ab9261366 --- /dev/null +++ b/middleware/externals/PlayerExternalsInterface.h @@ -0,0 +1,201 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerExternalsInterface.h + * @brief Output protection management for Player + */ + +#ifndef PlayerExternalsInterface_h +#define PlayerExternalsInterface_h + +#include +#include +#include +#include + +#include "PlayerExternalsInterfaceBase.h" + + +#undef __in +#undef __out +#undef __reserved + + +//used for FakePlayerExternalsInterface only, mimics dsmgr params +#define PLAYER_dsHDCP_VERSION_MAX 30 +#define PLAYER_dsHDCP_VERSION_2X 22 +#define PLAYER_dsHDCP_VERSION_1X 14 +typedef int playerDsHdcpProtocolVersion_t; + +class FakePlayerExternalsInterface : public PlayerExternalsInterfaceBase +{ + playerDsHdcpProtocolVersion_t m_hdcpCurrentProtocol; + public: + FakePlayerExternalsInterface(){SetHDMIStatus();} + + void Initialize() override {} + + /** + * @fn GetDisplayResolution + * @brief Get current resolution's display width and height + * @param[out] width width of current resolution + * @param[out] height height of current resolution + */ + void GetDisplayResolution(int &width, int &height) override{} + + /** + * @fn SetHDMIStatus + * @brief Checks Display Settings and sets HDMI parameters like video output resolution, HDCP protocol + */ + void SetHDMIStatus() override{ + m_hdcpCurrentProtocol = PLAYER_dsHDCP_VERSION_1X; + m_isHDCPEnabled = true; + } + + /** + * @fn GetTR181Config + * @brief Gets appropriate TR181 Config + * @param[in] paramName String of name of the parameter to be retrieved + * @param[out] iConfigLen Length of config retrieved + * @return Parameter config retrieved + */ + char * GetTR181Config(const char * paramName, size_t & iConfigLen) override{return nullptr;} + + /** + * @fn isHDCPConnection2_2 + * @brief Is current HDCP protocol 2_2 + * @return True if current HDCP protocol is 2_2. False, if not. + */ + bool isHDCPConnection2_2() override{ return m_hdcpCurrentProtocol == PLAYER_dsHDCP_VERSION_2X; } + + /** + * @fn GetActiveInterface + * @brief Is current active interface wifi? + * @return True if wifi. False, if not. + */ + bool GetActiveInterface()override{return false;} + + void SetUseFireBoltSDK(bool t_use_firebolt_sdk) override {} + + ~FakePlayerExternalsInterface(){} +}; + +/** + * @class PlayerExternalsInterface + * @brief Class to enforce HDCP authentication + */ +class PlayerExternalsInterface +{ + +private: + + + std::shared_ptr m_pIarmInterface; + + static std::shared_ptr s_pPlayerOP; + + /** + * @fn PlayerExternalsInterface + */ + PlayerExternalsInterface(); + + + +public: + + /** + * @fn ~PlayerExternalsInterface + */ + virtual ~PlayerExternalsInterface(); + /** + * @brief Copy constructor disabled + * + */ + PlayerExternalsInterface(const PlayerExternalsInterface&) = delete; + /** + * @brief assignment operator disabled + * + */ + PlayerExternalsInterface& operator=(const PlayerExternalsInterface&) = delete; + + void Initialize(); + + + + + char * GetTR181PlayerConfig(const char * paramName, size_t & iConfigLen); + + // State functions + + /** + * @brief Check if HDCP is 2.2 + * @retval true if 2.2 false otherwise + */ + bool isHDCPConnection2_2() { return m_pIarmInterface->isHDCPConnection2_2(); } + /** + * @fn IsSourceUHD + * @retval true, if source is UHD, otherwise false + */ + bool IsSourceUHD(); + + /** + * @fn GetDisplayResolution + * @param[out] width : Display Width + * @param[out] height : Display height + */ + void GetDisplayResolution(int &width, int &height); + + /** + * @brief Set GstElement + * @param element Gst element to set + */ + void setGstElement(GstElement *element) { m_pIarmInterface->setGstElement(element); } + + // Singleton for object creation + + /** + * @fn GetPlayerExternalsInterfaceInstance + * @retval PlayerExternalsInterface object + */ + static std::shared_ptr GetPlayerExternalsInterfaceInstance(); + /** + * @fn IsPlayerExternalsInterfaceInstanceActive + * @retval true or false + */ + static bool IsPlayerExternalsInterfaceInstanceActive(); + /** + * @fn GetActiveInterface + * @brief Is current active interface wifi? + * @return True if wifi. False, if not. + */ + bool GetActiveInterface(); + + /** + * @fn IsConfigWifiCurlHeader + * @brief Routine to find if IARM is supported in platform + */ + bool IsConfigWifiCurlHeader(); + + + void SetUseFireBoltSDK(bool t_use_firebolt_sdk); + +}; + +#endif // PlayerExternalsInterface_h diff --git a/middleware/externals/PlayerExternalsInterfaceBase.h b/middleware/externals/PlayerExternalsInterfaceBase.h new file mode 100644 index 000000000..374393c1a --- /dev/null +++ b/middleware/externals/PlayerExternalsInterfaceBase.h @@ -0,0 +1,143 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerExternalsInterfaceBase.h + * @brief Base class for player interface with Externals + */ + +#ifndef PLAYER_EXTERNALS_INTERFACE_BASE_H +#define PLAYER_EXTERNALS_INTERFACE_BASE_H + + +#include +#include + +#include + +#include "PlayerLogManager.h" + +#define UHD_WIDTH 3840 +#define UHD_HEIGHT 2160 + +//base class for externals interface +class PlayerExternalsInterfaceBase +{ + protected: + bool m_isHDCPEnabled; + int m_displayWidth; + int m_displayHeight; + + int m_sourceWidth; + int m_sourceHeight; + + GstElement* m_gstElement; + + + public: + + PlayerExternalsInterfaceBase():m_sourceWidth(0),m_sourceHeight(0),m_gstElement(nullptr){} + + virtual void Initialize() = 0; + /** + * @fn IsSourceUHD + * @brief Finds out if source is of UHD resolution + * @return True if UHD. False if not UHD. + */ + bool IsSourceUHD() + { + bool retVal = false; + + // DEBUG_FUNC; + static gint sourceHeight = 0; + static gint sourceWidth = 0; + + if(m_gstElement == NULL) { + // Value not set, since there is no + // decoder yet the output size can not + // be determined + return false; + } + + g_object_get(m_gstElement, "video_height", &sourceHeight, NULL); + g_object_get(m_gstElement, "video_width", &sourceWidth, NULL); + + if(sourceWidth != m_sourceWidth || sourceHeight != m_sourceHeight) { + MW_LOG_WARN("viddec (%p) --> says width %d, height %d", m_gstElement, sourceWidth, sourceHeight); + m_sourceWidth = sourceWidth; + m_sourceHeight = sourceHeight; + } + if(sourceWidth != 0 && sourceHeight != 0 && + (sourceWidth >= UHD_WIDTH || sourceHeight >= UHD_HEIGHT) ) { + // Source Material is UHD + retVal = true; + } + return retVal; + } + + /** + * @fn setGstElement + * @brief Set Video decoder Gst Element for UHD identification + */ + void setGstElement(GstElement *element) { m_gstElement = element; } + + /** + * @fn GetDisplayResolution + * @brief Get current resolution's display width and height + * @param[out] width width of current resolution + * @param[out] height height of current resolution + */ + virtual void GetDisplayResolution(int &width, int &height){} + + /** + * @fn SetHDMIStatus + * @brief Checks Display Settings and sets HDMI parameters like video output resolution, HDCP protocol + */ + virtual void SetHDMIStatus(){} + + /** + * @fn GetTR181Config + * @brief Gets appropriate TR181 Config + * @param[in] paramName String of name of the parameter to be retrieved + * @param[out] iConfigLen Length of config retrieved + * @return Parameter config retrieved + */ + virtual char * GetTR181Config(const char * paramName, size_t & iConfigLen){return nullptr;} + + /** + * @fn isHDCPConnection2_2 + * @brief Is current HDCP protocol 2_2 + * @return True if current HDCP protocol is 2_2. False, if not. + */ + virtual bool isHDCPConnection2_2() { return false;} + + /** + * @fn GetActiveInterface + * @brief Is current active interface wifi? + * @return True if wifi. False, if not. + */ + virtual bool GetActiveInterface(){return false;} + + virtual ~PlayerExternalsInterfaceBase(){} + + virtual void SetUseFireBoltSDK(bool t_use_firebolt_sdk) = 0; + +}; + +#endif diff --git a/middleware/externals/PlayerRfc.cpp b/middleware/externals/PlayerRfc.cpp new file mode 100644 index 000000000..3fd8a2cd9 --- /dev/null +++ b/middleware/externals/PlayerRfc.cpp @@ -0,0 +1,74 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2015 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerRfc.cpp + * @brief APIs to get RFC configured data + */ + +#include +#include +#include +#ifdef PLAYER_RFC_ENABLED +#include "tr181api.h" +#endif +#include "PlayerRfc.h" +#include "PlayerExternalUtils.h" + +#include "PlayerLogManager.h" + +namespace RFCSettings +{ + /** + * @brief Fetch data from RFC + * @param CallerId and Parameter to be fetched + * @retval std::string host value + */ + std::string readRFCValue(const std::string& parameter,const char* playerName){ + std::string strhost=""; + #ifdef PLAYER_RFC_ENABLED + if(!IsContainerEnvironment()) + { + TR181_ParamData_t param = {0}; + + //getParam() wrongly char* not const char*; cast to avoid compilation error + tr181ErrorCode_t status = getParam((char *)playerName, parameter.c_str(), ¶m); + + if (tr181Success == status) + { + MW_LOG_INFO("RFC Parameter for %s is %s type = %d", parameter.c_str(), param.value, param.type); + strhost = std::string(param.value); + } + else if (tr181ValueIsEmpty == status) + { + // NO RFC is set , which is success case + MW_LOG_TRACE("RFC Parameter : %s is not set", parameter.c_str()); + } + else + { + MW_LOG_ERR("get RFC Parameter for %s Failed : %s type = %d", parameter.c_str(), getTR181ErrorString(status), param.type); + } + } + #endif + return strhost; + } +} +/** + * EOF + */ diff --git a/middleware/externals/PlayerRfc.h b/middleware/externals/PlayerRfc.h new file mode 100644 index 000000000..8aaa0621c --- /dev/null +++ b/middleware/externals/PlayerRfc.h @@ -0,0 +1,36 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerRfc.h + * @brief Aamp RFC header files + */ +#ifndef _PLAYER_RFC_H +#define _PLAYER_RFC_H + +namespace RFCSettings{ + /** + * @fn getRFCValue + * @brief Fetch data from RFC + * @param Parameter to be fetched + * @retval std::string host value + */ + std::string readRFCValue(const std::string& parameter,const char* playerName); +} +#endif diff --git a/middleware/externals/PlayerThunderAccessBase.h b/middleware/externals/PlayerThunderAccessBase.h new file mode 100644 index 000000000..8916f1de0 --- /dev/null +++ b/middleware/externals/PlayerThunderAccessBase.h @@ -0,0 +1,412 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerThunderInterface.cpp + * @brief base class for interface between player and thunder + */ + +#ifndef PLAYER_THUNDER_INTERFACE_BASE +#define PLAYER_THUNDER_INTERFACE_BASE + +#include +#include +#include + +/* + *@enum PlayerThunderAccessPlugin : Supported Plugins + */ +enum PlayerThunderAccessPlugin +{ + AVINPUT, + MEDIAPLAYER, + MEDIASETTINGS, + RDKSHELL, + RMF, + DS, + SECMANAGER, + WATERMARK, + HDMIINPUT, + COMPOSITEINPUT +}; + +/* + *@enum PlayerThunderAccessShim : Supported Shim + */ +enum PlayerThunderAccessShim +{ + VIDEOIN_SHIM, + OTA_SHIM, + RMF_SHIM +}; + +/* + *@struct PlayerVideoStreamInfoData video stream data + */ +struct PlayerVideoStreamInfoData +{ + bool progressive; + double frameRateN; + double frameRateD; + int width; + int height; +}; + +/* + *@struct PlayerStatusData player status data + */ +struct PlayerStatusData{ + + std::string currState; + std::string eventUrl; + std::string reasonString; + std::string ratingString; + int ssi; + + bool vid_progressive; + float vid_frameRateN; + float vid_frameRateD; + int vid_aspectRatioWidth; + int vid_aspectRatioHeight; + int vid_width; + int vid_height; + std::string vid_codec; + std::string vid_hdrType; + int vid_bitrate; + + std::string aud_codec; + std::string aud_mixType; + bool aud_isAtmos; + int aud_bitrate; + +}; + +/* + *@struct PlayerAudioData player audio data + */ +struct PlayerAudioData{ + + std::string language; + std::string contentType; + std::string name; + std::string type; + int pk; + std::string mixType; + + PlayerAudioData(std::string lang, std::string content_Type, std::string nm, std::string t, int pk_int, std::string mix_Type) + : language(lang), + contentType(content_Type), + name(nm), + type(t), + pk(pk_int), + mixType(mix_Type) + { + } + +}; + +/* + *@struct PlayerPreferredAudioData player preferred audio data + */ + +struct PlayerPreferredAudioData{ + + std::string preferredLanguagesString; + std::string pluginPreferredLanguagesString; + std::string preferredRenditionString; + std::string pluginPreferredRenditionString; +}; + +/* + *@struct PlayerTextData player text data + */ +struct PlayerTextData{ + + std::string type; + std::string language; + int ccServiceNumber; + std::string ccType; + std::string name; + int pk; + + PlayerTextData(std::string t, std::string lang, int ccSer, std::string cc_Type, std::string nm, int pk_int) + :type(t), + language(lang), + ccServiceNumber(ccSer), + ccType(cc_Type), + name(nm), + pk(pk_int) + { + } + +}; + +/** + * @class PlayerThunderAccessBase + * @brief Template Thunder Plugin Access from Player + */ +class PlayerThunderAccessBase +{ +public: + /** + * @fn PlayerThunderAccessBase + * @note Security token acquisition, controller object creation + * @param callsign plugin callsign + */ + PlayerThunderAccessBase(PlayerThunderAccessPlugin callsign) {} + + /** + * @fn ~PlayerThunderAccessBase + * @note clean up + */ + virtual ~PlayerThunderAccessBase(){} + + /** + * @brief PlayerThunderAccessBase copy constructor disabled + */ + PlayerThunderAccessBase(const PlayerThunderAccessBase&) = delete; + + /** + * @brief PlayerThunderAccessBase assignment disabled + */ + PlayerThunderAccessBase& operator=(const PlayerThunderAccessBase&) = delete; + + /** + * @brief ActivatePlugin + * @note Plugin activation and Remote object creation + * @param Plugin Callsign + * @retval true on success + * @retval false on failure + */ + virtual bool ActivatePlugin() = 0; + + /** + * @fn UnSubscribeEvent + * @note unSubscribe event data for the specific plugin + * @param eventName Event name + * @retval true on success + * @retval false on failure + */ + virtual bool UnSubscribeEvent (std::string eventName) = 0; + + /** + * @fn SetVideoRectangle + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @param videoInputType string video input type + * @param shim shim to set video rectangle for + * @retval true if success + * @retval false if failure + */ + virtual bool SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) = 0; + + /** + * @fn SetPreferredAudioLanguages_OTA + * @param data player's input on preferred languages + * @param shim shim to set preferred language for + */ + virtual void SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) = 0; + + /** + * @fn RegisterAllEventsVideoin + * @param OnSignalChangedCb callback for when signal change + * @param OnInputStatusChangedCb callback for when input status changes + */ + virtual void RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) = 0; + + /** + * @fn UnRegisterAllEventsVideoin + */ + virtual void UnRegisterAllEventsVideoin() = 0; + + /** + * @fn StartHelper + * @param port port number + * @param videoInputType string video input type + */ + virtual void StartHelperVideoin(int port, std::string videoInputType) = 0; + /** + * @fn StopHelper + * @param videoInputType string video input type + */ + virtual void StopHelperVideoin(std::string videoInputType) = 0; + + /** + * @fn RegisterEventOnVideoStreamInfoUpdateHdmiin + */ + virtual void RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) = 0; + + /** + * @fn RegisterOnPlayerStatusOta + * @param onPlayerStatusCb callback for player when player status update + */ + virtual void RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) = 0; + + /** + * @fn ReleaseOta + */ + virtual void ReleaseOta() = 0; + + /** + * @fn StartOta + * @param url string containing url + * @param waylandDisplay string wayland display + * @param preferredLanguagesString player's preferred languages + * @param atsc_preferredLanguagesString ATSC preferred languages + * @param preferredRenditionString player's preferred rendition + * @param atsc_preferredRenditionString ATSC preferred rendition + */ + virtual void StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) = 0; + + /** + * @fn StopOta + */ + virtual void StopOta() = 0; + + /** + * @fn GetAudioTracks + * @param audData vector to be filled with audio data + * @return string of current track pk + */ + virtual std::string GetAudioTracksOta(std::vector audData) = 0; + + /** + * @fn SetAudioTrackOta + * @param index audio index to be set + * @param primaryKey primary key + * @return string of audioTrackIndex on success, empty string on failure + */ + virtual std::string SetAudioTrackOta(int index, int primaryKey) = 0; + + /** + * @fn GetTextTracksOta + * @param txtData vector to be filled with text data + * @return true if successful, false if failed + */ + virtual bool GetTextTracksOta(std::vector txtData) = 0; + + /** + * @fn DisableContentRestrictionsOta + * + * @param[in] grace - seconds from current time, grace period, grace = -1 will allow an unlimited grace period + * @param[in] time - seconds from current time,time till which the channel need to be kept unlocked + * @param[in] eventChange - disable restriction handling till next program event boundary + */ + virtual void DisableContentRestrictionsOta(long grace, long time, bool eventChange) = 0; + + /** + * @fn EnableContentRestrictions + * + */ + virtual void EnableContentRestrictionsOta() = 0; + + /** + * @fn InitRmf + * @return true if successful, false if failed + */ + virtual bool InitRmf() = 0; + + /** + * @fn StartRmf + * @param url url string + * @param onPlayerStatusHandlerCb player status handler callback + * @param onPlayerErrorHandlerCb player error handler callback + * @return true if successful, false if failed + */ + virtual bool StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) = 0; + + /** + * @fn StopRmf + * + */ + virtual void StopRmf() = 0; + + /** + * @fn DeleteWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + virtual bool DeleteWatermark(int layerID) = 0; + + /** + * @fn CreateWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + virtual bool CreateWatermark(int layerID) = 0; + + /** + * @fn ShowWatermark + * @param opacity opacity + * @return true if successful, false if failed + */ + virtual bool ShowWatermark(int opacity) = 0; + + /** + * @fn HideWatermark + * @return true if successful, false if failed + */ + virtual bool HideWatermark() = 0; + + /** + * @fn UpdateWatermark + * @param layerID layed id + * @param sharedMemoryKey key of shared mem + * @param size size + * @return true if successful, false if failed + */ + virtual bool UpdateWatermark(int layerID, int sharedMemoryKey, int size) = 0; + + /** + * @fn GetMetaDataWatermark + * @return string of meta data if successful, empty string if failed + */ + virtual std::string GetMetaDataWatermark() = 0; + + /** + * @fn PersistentStoreSaveWatermark + * @param base64Image image data + * @param metaData metaData + * @return true if successful, false if failed + */ + virtual bool PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) = 0; + + /** + * @fn PersistentStoreLoadWatermark + * @param layerID layer id + * @return true if successful, false if failed + */ + virtual bool PersistentStoreLoadWatermark(int layerID) = 0; + + /** + * @fn IsThunderAccess + * @return true if thunder access available + * @return false f thunder access not available + */ + virtual bool IsThunderAccess() = 0; + + +protected: + std::string pluginCallsign; +}; + +#endif \ No newline at end of file diff --git a/middleware/externals/PlayerThunderInterface.cpp b/middleware/externals/PlayerThunderInterface.cpp new file mode 100644 index 000000000..f4a4289c8 --- /dev/null +++ b/middleware/externals/PlayerThunderInterface.cpp @@ -0,0 +1,457 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerThunderInterface.cpp + * @brief interface between player and thunder + */ + +#include "PlayerThunderInterface.h" + +#ifdef USE_CPP_THUNDER_PLUGIN_ACCESS +#include "PlayerThunderAccess.h" +#endif + +/** + * @class FakeThunderAccess + * @brief Fake Thunder Plugin Access from Player + */ +class FakeThunderAccess : public PlayerThunderAccessBase +{ +public: + /** + * @fn FakeThunderAccess + * @note Security token acquisition, controller object creation + * @param callsign plugin callsign + */ + FakeThunderAccess(PlayerThunderAccessPlugin callsign): PlayerThunderAccessBase(callsign){} + + /** + * @fn ~FakeThunderAccess + * @note clean up + */ + ~FakeThunderAccess(){} + + /** + * @brief FakeThunderAccess copy constructor disabled + */ + FakeThunderAccess(const FakeThunderAccess&) = delete; + + /** + * @brief FakeThunderAccess assignment disabled + */ + FakeThunderAccess& operator=(const FakeThunderAccess&) = delete; + + /** + * @brief ActivatePlugin + * @note Plugin activation and Remote object creation + * @param Plugin Callsign + * @retval true on success + * @retval false on failure + */ + bool ActivatePlugin() override {return false;} + + /** + * @fn UnSubscribeEvent + * @note unSubscribe event data for the specific plugin + * @param eventName Event name + * @retval true on success + * @retval false on failure + */ + bool UnSubscribeEvent (std::string eventName) override {return false;} + + /** + * @fn SetVideoRectangle + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @param videoInputType string video input type + * @param shim shim to set video rectangle for + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) override {return false;} + + /** + * @fn SetPreferredAudioLanguages_OTA + * @param data player's input on preferred languages + * @param shim shim to set preferred language for + */ + void SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) override {} + + /** + * @fn RegisterAllEventsVideoin + * @param OnSignalChangedCb callback for when signal change + * @param OnInputStatusChangedCb callback for when input status changes + */ + void RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) override {} + + /** + * @fn UnRegisterAllEventsVideoin + */ + void UnRegisterAllEventsVideoin() override{}; + + /** + * @fn StartHelper + * @param videoInputType string video input type + * @param port port number + */ + void StartHelperVideoin(int port, std::string videoInputType) override{} + /** + * @fn StopHelper + * @param videoInputType string video input type + */ + void StopHelperVideoin(std::string videoInputType) override {} + + /** + * @fn RegisterEventOnVideoStreamInfoUpdateHdmiin + */ + void RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) override {} + + /** + * @fn RegisterOnPlayerStatusOta + * @param onPlayerStatusCb callback for player when player status update + */ + void RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) override {} + + /** + * @fn StartOta + * @param url string containing url + * @param waylandDisplay string wayland display + * @param preferredLanguagesString player's preferred languages + * @param atsc_preferredLanguagesString ATSC preferred languages + * @param preferredRenditionString player's preferred rendition + * @param atsc_preferredRenditionString ATSC preferred rendition + */ + void StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) override {} + + /** + * @fn StopOta + */ + void StopOta() override {} + + /** + * @fn ReleaseOta + */ + void ReleaseOta() override {} + + /** + * @fn GetAudioTracks + * @param audData vector to be filled with audio data + * @return string of current track pk + */ + std::string GetAudioTracksOta(std::vector audData) override + { + std::string ret = ""; + return ret; + } + + /** + * @fn SetAudioTrackOta + * @param index audio index to be set + * @param primaryKey primary key + * @return string of audioTrackIndex on success, empty string on failure + */ + std::string SetAudioTrackOta(int index, int primaryKey) override + { + std::string ret = ""; + return ret; + } + + /** + * @fn GetTextTracksOta + * @param txtData vector to be filled with text data + * @return true if successful, false if failed + */ + bool GetTextTracksOta(std::vector txtData) override {return false;} + + /** + * @fn DisableContentRestrictionsOta + * + * @param[in] grace - seconds from current time, grace period, grace = -1 will allow an unlimited grace period + * @param[in] time - seconds from current time,time till which the channel need to be kept unlocked + * @param[in] eventChange - disable restriction handling till next program event boundary + */ + void DisableContentRestrictionsOta(long grace, long time, bool eventChange) override {}; + + /** + * @fn EnableContentRestrictions + * + */ + void EnableContentRestrictionsOta() override {} + + /** + * @fn InitRmf + * @return true if successful, false if failed + */ + bool InitRmf() override {return false;} + + /** + * @fn StartRmf + * @param url url string + * @param onPlayerStatusHandlerCb player status handler callback + * @param onPlayerErrorHandlerCb player error handler callback + * @return true if successful, false if failed + */ + bool StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) override {return false;} + + /** + * @fn StopRmf + * + */ + void StopRmf() override {} + + /** + * @fn DeleteWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool DeleteWatermark(int layerID) override {return false;} + + /** + * @fn CreateWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool CreateWatermark(int layerID) override {return false;} + + /** + * @fn ShowWatermark + * @param opacity opacity + * @return true if successful, false if failed + */ + bool ShowWatermark(int opacity) override {return false;} + + /** + * @fn HideWatermark + * @return true if successful, false if failed + */ + bool HideWatermark() override {return false;} + + /** + * @fn UpdateWatermark + * @param layerID layed id + * @param sharedMemoryKey key of shared mem + * @param size size + * @return true if successful, false if failed + */ + bool UpdateWatermark(int layerID, int sharedMemoryKey, int size) override {return false;} + + /** + * @fn GetMetaDataWatermark + * @return string of meta data if successful, empty string if failed + */ + std::string GetMetaDataWatermark() override + { + std::string metaData = ""; + return metaData; + } + + /** + * @fn PersistentStoreSaveWatermark + * @param base64Image image data + * @param metaData metaData + * @return true if successful, false if failed + */ + bool PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) override {return false;} + + /** + * @fn PersistentStoreLoadWatermark + * @param layerID layer id + * @return true if successful, false if failed + */ + bool PersistentStoreLoadWatermark(int layerID) override {return false;} + + /** + * @fn IsThunderAccess + * @return true if thunder access available + * @return false f thunder access not available + */ + bool IsThunderAccess() override {return false;} + + +}; + + +PlayerThunderInterface::PlayerThunderInterface(PlayerThunderAccessPlugin callsign) +{ +#ifdef USE_CPP_THUNDER_PLUGIN_ACCESS + m_pThunderAccess = new PlayerThunderAccess(callsign); +#else + m_pThunderAccess = new FakeThunderAccess(callsign); +#endif +} + +PlayerThunderInterface::~PlayerThunderInterface() +{ + delete m_pThunderAccess; +} + +bool PlayerThunderInterface::ActivatePlugin() +{ + return m_pThunderAccess->ActivatePlugin(); +} + +bool PlayerThunderInterface::UnSubscribeEvent(std::string eventName) +{ + return m_pThunderAccess->UnSubscribeEvent(std::move(eventName)); +} + +bool PlayerThunderInterface::SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) +{ + return m_pThunderAccess->SetVideoRectangle(x, y, w, h, std::move(videoInputType), shim); +} + + +void PlayerThunderInterface::RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) +{ + m_pThunderAccess->RegisterAllEventsVideoin(std::move(OnSignalChangedCb), std::move(OnInputStatusChangedCb)); +} + +void PlayerThunderInterface::UnRegisterAllEventsVideoin() +{ + m_pThunderAccess->UnRegisterAllEventsVideoin(); +} + +void PlayerThunderInterface::StartHelperVideoin(int port, std::string videoInputType) +{ + m_pThunderAccess->StartHelperVideoin(port, std::move(videoInputType)); +} + +void PlayerThunderInterface::StopHelperVideoin(std::string videoInputType) +{ + m_pThunderAccess->StopHelperVideoin(std::move(videoInputType)); +} + +void PlayerThunderInterface::RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) +{ + m_pThunderAccess->RegisterEventOnVideoStreamInfoUpdateHdmiin(std::move(videoInfoUpdatedMethodCb)); +} + +void PlayerThunderInterface::RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) +{ + m_pThunderAccess->RegisterOnPlayerStatusOta(std::move(onPlayerStatusCb)); +} + +void PlayerThunderInterface::ReleaseOta() +{ + m_pThunderAccess->ReleaseOta(); +} + +void PlayerThunderInterface::StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) +{ + m_pThunderAccess->StartOta(std::move(url), std::move(waylandDisplay), std::move(preferredLanguagesString), std::move(atsc_preferredLanguagesString), std::move(preferredRenditionString), std::move(atsc_preferredRenditionString)); +} + +void PlayerThunderInterface::StopOta() +{ + m_pThunderAccess->StopOta(); +} + +void PlayerThunderInterface::SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) +{ + m_pThunderAccess->SetPreferredAudioLanguages(std::move(data), shim); +} + +std::string PlayerThunderInterface::GetAudioTracksOta(std::vector audData) +{ + return m_pThunderAccess->GetAudioTracksOta(std::move(audData)); +} + +std::string PlayerThunderInterface::SetAudioTrackOta(int index, int primaryKey) +{ + return m_pThunderAccess->SetAudioTrackOta(index, primaryKey); +} + +bool PlayerThunderInterface::GetTextTracksOta(std::vector txtData) +{ + return m_pThunderAccess->GetTextTracksOta(std::move(txtData)); +} + +void PlayerThunderInterface::DisableContentRestrictionsOta(long grace, long time, bool eventChange) +{ + return m_pThunderAccess->DisableContentRestrictionsOta(grace, time, eventChange); +} + +void PlayerThunderInterface::EnableContentRestrictionsOta() +{ + return m_pThunderAccess->EnableContentRestrictionsOta(); +} + +bool PlayerThunderInterface::InitRmf() +{ + return m_pThunderAccess->InitRmf(); +} + +bool PlayerThunderInterface::StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) +{ + return m_pThunderAccess->StartRmf(std::move(url), std::move(onPlayerStatusHandlerCb), std::move(onPlayerErrorHandlerCb)); +} + +void PlayerThunderInterface::StopRmf() +{ + m_pThunderAccess->StopRmf(); +} + +bool PlayerThunderInterface::DeleteWatermark(int layerID) +{ + return m_pThunderAccess->DeleteWatermark(layerID); +} + +bool PlayerThunderInterface::CreateWatermark(int layerID) +{ + return m_pThunderAccess->CreateWatermark(layerID); +} + +bool PlayerThunderInterface::ShowWatermark(int opacity) +{ + return m_pThunderAccess->ShowWatermark(opacity); +} + +bool PlayerThunderInterface::HideWatermark() +{ + return m_pThunderAccess->HideWatermark(); +} + +bool PlayerThunderInterface::UpdateWatermark(int layerID, int sharedMemoryKey, int size) +{ + return m_pThunderAccess->UpdateWatermark(layerID, sharedMemoryKey, size); +} + +std::string PlayerThunderInterface::GetMetaDataWatermark() +{ + return m_pThunderAccess->GetMetaDataWatermark(); +} + +bool PlayerThunderInterface::PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) +{ + return m_pThunderAccess->PersistentStoreSaveWatermark(base64Image, std::move(metaData)); +} + +bool PlayerThunderInterface::PersistentStoreLoadWatermark(int layerID) +{ + return m_pThunderAccess->PersistentStoreLoadWatermark(layerID); +} + +bool PlayerThunderInterface::IsThunderAccess() +{ + return m_pThunderAccess->IsThunderAccess(); +} \ No newline at end of file diff --git a/middleware/externals/PlayerThunderInterface.h b/middleware/externals/PlayerThunderInterface.h new file mode 100644 index 000000000..54db5e8ca --- /dev/null +++ b/middleware/externals/PlayerThunderInterface.h @@ -0,0 +1,306 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerThunderInterface.h + * @brief interface between player and thunder + */ + +#ifndef PLAYER_THUNDER_INTERFACE_H +#define PLAYER_THUNDER_INTERFACE_H + +#include "PlayerThunderAccessBase.h" + +/** + * @class PlayerThunderInterface + * @brief Interface Thunder for Player + */ +class PlayerThunderInterface +{ +public: + /** + * @fn PlayerThunderInterface + * @note Security token acquisition, controller object creation + * @param callsign plugin callsign + */ + PlayerThunderInterface(PlayerThunderAccessPlugin callsign); + + /** + * @fn ~PlayerThunderInterface + * @note clean up + */ + ~PlayerThunderInterface(); + + /** + * @brief PlayerThunderInterface copy constructor disabled + */ + PlayerThunderInterface(const PlayerThunderInterface&) = delete; + + /** + * @brief PlayerThunderInterface assignment disabled + */ + PlayerThunderInterface& operator=(const PlayerThunderInterface&) = delete; + + /** + * @brief ActivatePlugin + * @note Plugin activation and Remote object creation + * @param Plugin Callsign + * @retval true on success + * @retval false on failure + */ + bool ActivatePlugin(); + + /** + * @fn UnSubscribeEvent + * @note unSubscribe event data for the specific plugin + * @param eventName Event name + * @retval true on success + * @retval false on failure + */ + bool UnSubscribeEvent (std::string eventName); + + /** + * @fn SetVideoRectangle + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @param videoInputType string video input type + * @param shim shim to set video rectangle for + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim); + + /** + * @fn SetPreferredAudioLanguages_OTA + * @param data player's input on preferred languages + * @param shim shim to set preferred language for + */ + void SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim); + + //-----------------VIDEOIN APIs begin------------------- + + /** + * @fn RegisterAllEventsVideoin + * @param OnSignalChangedCb callback for when signal change + * @param OnInputStatusChangedCb callback for when input status changes + */ + void RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb); + + /** + * @fn UnRegisterAllEventsVideoin + */ + void UnRegisterAllEventsVideoin(); + + /** + * @fn StartHelper + * @param port port number + * @param videoInputType string video input type + */ + void StartHelperVideoin(int port, std::string videoInputType); + + /** + * @fn StopHelper + * @param videoInputType string video input type + */ + void StopHelperVideoin(std::string videoInputType); + + //-----------------VIDEOIN APIs end----------------- + + //-----------------HDMIIN APIs begin---------------- + + /** + * @fn RegisterEventOnVideoStreamInfoUpdateHdmiin + */ + void RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb); + + //-----------------HDMIIN APIs end------------------ + + //-----------------OTA APIs begin------------------- + + /** + * @fn RegisterOnPlayerStatusOta + * @param onPlayerStatusCb callback for player when player status update + */ + void RegisterOnPlayerStatusOta(std::function onPlayerStatusCb); + + /** + * @fn ReleaseOta + */ + void ReleaseOta(); + + /** + * @fn StartOta + * @param url string containing url + * @param waylandDisplay string wayland display + * @param preferredLanguagesString player's preferred languages + * @param atsc_preferredLanguagesString ATSC preferred languages + * @param preferredRenditionString player's preferred rendition + * @param atsc_preferredRenditionString ATSC preferred rendition + */ + void StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString); + + /** + * @fn StopOta + */ + void StopOta(); + + /** + * @fn GetAudioTracks + * @param audData vector to be filled with audio data + * @return string of current track pk + */ + std::string GetAudioTracksOta(std::vector audData); + + /** + * @fn SetAudioTrackOta + * @param index audio index to be set + * @param primaryKey primary key + * @return string of audioTrackIndex on success, empty string on failure + */ + std::string SetAudioTrackOta(int index, int primaryKey); + + /** + * @fn GetTextTracksOta + * @param txtData vector to be filled with text data + * @return true if successful, false if failed + */ + bool GetTextTracksOta(std::vector txtData); + + /** + * @fn DisableContentRestrictionsOta + * + * @param[in] grace - seconds from current time, grace period, grace = -1 will allow an unlimited grace period + * @param[in] time - seconds from current time,time till which the channel need to be kept unlocked + * @param[in] eventChange - disable restriction handling till next program event boundary + */ + void DisableContentRestrictionsOta(long grace, long time, bool eventChange); + + /** + * @fn EnableContentRestrictions + * + */ + void EnableContentRestrictionsOta(); + + //-----------------OTA APIs end------------------- + + //-----------------RMF APIs begin----------------- + + /** + * @fn InitRmf + * @return true if successful, false if failed + */ + bool InitRmf(); + + /** + * @fn StartRmf + * @param url url string + * @param onPlayerStatusHandlerCb player status handler callback + * @param onPlayerErrorHandlerCb player error handler callback + * @return true if successful, false if failed + */ + bool StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb); + + /** + * @fn StopRmf + * + */ + void StopRmf(); + + //-----------------RMF APIs end------------------- + + //--------------Watermark APIs begins------------- + + /** + * @fn DeleteWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool DeleteWatermark(int layerID); + + /** + * @fn CreateWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool CreateWatermark(int layerID); + + /** + * @fn ShowWatermark + * @param opacity opacity + * @return true if successful, false if failed + */ + bool ShowWatermark(int opacity); + + /** + * @fn HideWatermark + * @return true if successful, false if failed + */ + bool HideWatermark(); + + /** + * @fn UpdateWatermark + * @param layerID layed id + * @param sharedMemoryKey key of shared mem + * @param size size + * @return true if successful, false if failed + */ + bool UpdateWatermark(int layerID, int sharedMemoryKey, int size); + + /** + * @fn GetMetaDataWatermark + * @return string of meta data if successful, empty string if failed + */ + std::string GetMetaDataWatermark(); + + /** + * @fn PersistentStoreSaveWatermark + * @param base64Image image data + * @param metaData metaData + * @return true if successful, false if failed + */ + bool PersistentStoreSaveWatermark(const char* base64Image, std::string metaData); + + /** + * @fn PersistentStoreLoadWatermark + * @param layerID layer id + * @return true if successful, false if failed + */ + bool PersistentStoreLoadWatermark(int layerID); + + //--------------Watermark APIs end---------------- + + /** + * @fn IsThunderAccess + * @return true if thunder access available + * @return false f thunder access not available + */ + bool IsThunderAccess(); + +private: + + PlayerThunderAccessBase* m_pThunderAccess = nullptr; + +}; + + + +#endif \ No newline at end of file diff --git a/middleware/externals/contentsecuritymanager/ContentSecurityManager.cpp b/middleware/externals/contentsecuritymanager/ContentSecurityManager.cpp new file mode 100755 index 000000000..cfd108115 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ContentSecurityManager.cpp @@ -0,0 +1,221 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.m + */ + +/** + * @file ContentSecurityManager.cpp + * @brief Class impl for ContentSecurityManager + */ + +#include "ContentProtectionFirebolt.h" +#include "ContentSecurityManager.h" +#include "PlayerLogManager.h" +#include +#include "_base64.h" +#include // For PRId64 +#include +#include +#include +#if defined(USE_SECMANAGER) +#include "SecManagerThunder.h" +#endif + +#define SECMANAGER_CALL_SIGN "org.rdk.SecManager.1" +#define WATERMARK_PLUGIN_CALLSIGN "org.rdk.Watermark.1" +//#define RDKSHELL_CALLSIGN "org.rdk.RDKShell.1" //need to be used instead of WATERMARK_PLUGIN_CALLSIGN if RDK Shell is used for rendering watermark + +static ContentSecurityManager *Instance = nullptr; /**< singleton instance*/ + +std::function ContentSecurityManager::SendWatermarkSessionEvent_CB; + +/* mutex GetInstance() & DestroyInstance() to improve thread safety + * There is still a race between using the pointer returned from GetInstance() and calling DestroyInstance()*/ +static std::mutex InstanceMutex; +static bool mUseFireboltSDK = false; + +/** + * @brief To get ContentSecurityManager instance + */ +ContentSecurityManager* ContentSecurityManager::GetInstance() +{ + std::lock_guard lock{InstanceMutex}; + if(Instance == nullptr) + { +/* Firebolt is applicable to all builds which uses either secmanager or secclient */ +#if defined(USE_SECCLIENT) || defined(USE_SECMANAGER) + if(mUseFireboltSDK) + { + Instance = new ContentProtectionFirebolt(); + } + else + { +#if defined(USE_SECMANAGER) + Instance = new SecManagerThunder(); +#endif + } +#else + Instance = new FakeSecManager(); +#endif + } + return Instance; +} + +/** + * @brief To release ContentSecurityManager singelton instance + */ +void ContentSecurityManager::DestroyInstance() +{ + std::lock_guard lock{InstanceMutex}; + if (Instance) + { + delete Instance; + Instance = nullptr; + } +} + +/** + * @brief To acquire an access token from auth service + */ +bool ContentSecurityManager::getSessionToken(std::string &token) +{ + return false; +} + +/** + * @brief To indicate whether application support firebolt capability + */ +void ContentSecurityManager::UseFireboltSDK(bool status) +{ + MW_LOG_INFO("Set Use Firebolt SDK as %d",status); + mUseFireboltSDK = status; +} + +std::size_t ContentSecurityManager::getInputSummaryHash(const char* moneyTraceMetadata[][2], const char* contentMetadata, + size_t contMetaLen, const char* licenseRequest, const char* keySystemId, + const char* mediaUsage, const char* accessToken, bool isVideoMuted) +{ + std::stringstream ss; + ss<< moneyTraceMetadata[0][1]<{}(InputSummary); + ss<<"SecManager input summary hash: "<(playback_speed), static_cast(playback_position)); + return rpcResult; +} + +/** + * @brief To set Watermark Session callback + */ +void ContentSecurityManager::setWatermarkSessionEvent_CB(const std::function& callback) +{ + SendWatermarkSessionEvent_CB = callback; + return; +} + +/** + * @brief To set Watermark Session callback + */ +std::function& ContentSecurityManager::getWatermarkSessionEvent_CB( ) +{ + return SendWatermarkSessionEvent_CB; +} + diff --git a/middleware/externals/contentsecuritymanager/ContentSecurityManager.h b/middleware/externals/contentsecuritymanager/ContentSecurityManager.h new file mode 100755 index 000000000..d8c32110e --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ContentSecurityManager.h @@ -0,0 +1,281 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file ContentSecurityManager.h + * @brief Class to communicate with SecManager Thunder plugin + */ + +#ifndef __CONTENT_SECURITY_MANAGER_H__ +#define __CONTENT_SECURITY_MANAGER_H__ + +#include +#include +#include "ContentSecurityManagerSession.h" +#include "PlayerScheduler.h" +#include "PlayerMemoryUtils.h" +#include "PlayerExternalUtils.h" +#include +#include +#include +#include +#include + +//Secmanager/Content Protection error class codes +#define CONTENT_SECURITY_MANAGER_DRM_FAILURE 200 +#define CONTENT_SECURITY_MANAGER_WM_FAILURE 300 /**< If secmanager couldn't initialize watermark service */ + +//Secmanager/Content Protection error reason codes +#define CONTENT_SECURITY_MANAGER_DRM_GEN_ERR_NONE 0 +#define CONTENT_SECURITY_MANAGER_DRM_GEN_FAILURE 1 /**< General or internal failure */ +#define CONTENT_SECURITY_MANAGER_SERVICE_TIMEOUT 3 +#define CONTENT_SECURITY_MANAGER_SERVICE_CON_FAILURE 4 +#define CONTENT_SECURITY_MANAGER_SERVICE_BUSY 5 +#define CONTENT_SECURITY_MANAGER_ACCTOKEN_EXPIRED 8 +#define CONTENT_SECURITY_MANAGER_ENTITLEMENT_FAILURE 102 + +#define MAX_LICENSE_REQUEST_ATTEMPTS 2 + +/** + * @class ContentSecurityManager + * @brief Class to get License from Sec Manager + */ +class ContentSecurityManager : public PlayerScheduler +{ +public: + //allow access to ContentSecurityManager::ReleaseSession() + friend ContentSecurityManagerSession::SessionManager::~SessionManager(); + /** + * @fn GetInstance + * + * @return ContentSecurityManager instance + */ + static ContentSecurityManager* GetInstance(); + + /** + * @fn DestroyInstance + */ + static void DestroyInstance(); + + /** + * @fn AcquireLicense + * + * @param[in] licenseUrl - url to fetch license from + * @param[in] moneyTraceMetadata - moneytrace info + * @param[in] accessAttributes - accessAttributes info + * @param[in] contentMetadata - content metadata info + * @param[in] licenseRequest - license challenge info + * @param[in] keySystemId - unique system ID of drm + * @param[in] mediaUsage - indicates whether its stream or download license request + * @param[in] accessToken - access token info + * @param[out] sessionId - session ID object of current session + * @param[out] licenseResponse - license response + * @param[out] licenseResponseLength - len of license response + * @param[out] statusCode - license fetch status code + * @param[out] reasonCode - license fetch reason code + * @return bool - true if license fetch successful, false otherwise + */ + virtual bool AcquireLicense( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contentMetadataLen, + const char* licenseRequest, size_t licenseRequestLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accessTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, + int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime); + + + /** + * @fn UpdateSessionState + * + * @param[in] sessionId - session id + * @param[in] active - true if session is active, false otherwise + */ + virtual bool UpdateSessionState(int64_t sessionId, bool active); + + /** + * @fn setVideoWindowSize + * + * @param[in] sessionId - session id + * @param[in] video_width - video width + * @param[in] video_height - video height + */ + virtual bool setVideoWindowSize(int64_t sessionId, int64_t video_width, int64_t video_height); + /** + * @fn setPlaybackSpeedState + * + * @param[in] sessionId - session id + * @param[in] playback_speed - playback speed + * @param[in] playback_position - playback position + */ + virtual bool setPlaybackSpeedState(int64_t sessionId, int64_t playback_speed, int64_t playback_position); + + /** + * @fn ReleaseSession - this should only be used by ContentSecurityManagerSession::SessionManager::~SessionManager(); + * + * @param[in] sessionId - session id + */ + virtual void ReleaseSession(int64_t sessionId); + + /** + * @fn getSessionToken + * + * @param[out] token - access token + */ + virtual bool getSessionToken(std::string &token); + + /** + * @fn SendWatermarkSessionEvent_CB + */ + static std::function SendWatermarkSessionEvent_CB; + + /** + * @fn UseFireboltSDK + * @brief To indicate whether application support firebolt capability + */ + static void UseFireboltSDK(bool status); + + /** + * @fn setWatermarkSessionEvent_CB + * @param[in] callback - callback function + * @return void + * @brief Set callback function for watermark session + */ + static void setWatermarkSessionEvent_CB(const std::function& callback); + + /** + * @fn getWatermarkSessionEvent_CB + * @return std::function& + * @brief Get callback function for watermark session + */ + static std::function& getWatermarkSessionEvent_CB( ); + + static std::size_t getInputSummaryHash(const char* moneyTraceMetadata[][2], const char* contentMetadata, + size_t contMetaLen, const char* licenseRequest, const char* keySystemId, + const char* mediaUsage, const char* accessToken, bool isVideoMuted); +protected: + + /* Run AcquireLicenseOpenOrUpdate is the old AcquireLicense code + * It is used by AcquireLicense() to for opening sessions & for calling update when this is required*/ + virtual bool AcquireLicenseOpenOrUpdate( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contentMetadataLen, + const char* licenseRequest, size_t licenseRequestLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accessTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, + int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) { return false; } + /** + * @brief Sets DRM session state (e.g., active/inactive) + * @param sessionId DRM session ID + * @param active Whether the session should be marked active + * @return true on success, false otherwise + */ + virtual bool SetDrmSessionState(int64_t sessionId, bool active) { return false; } + /** + * @brief Closes an existing DRM session + * @param sessionId DRM session ID + * @return true if closed successfully + */ + virtual void CloseDrmSession(int64_t sessionId) {} + /** + * @brief Sets playback state for watermark alignment + * @param sessionId Session ID + * @param speed Playback rate (1.0 = normal) + * @param position Current position in seconds + * @return true if command succeeded + */ + virtual bool SetPlaybackPosition(int64_t sessionId, float speed, int32_t position) { return false; } + /** + * @fn setWindowSize + * + * @param[in] sessionId - session id + * @param[in] video_width - video width + * @param[in] video_height - video height + */ + virtual bool setWindowSize(int64_t sessionId, int64_t video_width, int64_t video_height) { return false; }; + + /** + * @fn ContentSecurityManager + */ + ContentSecurityManager(){}; + + /** + * @fn ~ContentSecurityManager + */ + ~ContentSecurityManager(){}; + /** + * @brief Copy constructor disabled + * + */ + ContentSecurityManager(const ContentSecurityManager&) = delete; + /** + * @brief assignment operator disabled + * + */ + ContentSecurityManager* operator=(const ContentSecurityManager&) = delete; +}; + +/** + * @class FakeSecManager + * @brief Dummy no-op fallback implementation for unsupported platforms + */ +class FakeSecManager : public ContentSecurityManager +{ +public: + /** + * @fn FakeSecManager + */ + FakeSecManager() = default; + + /** + * @brief Destructor + */ + ~FakeSecManager() = default; + + FakeSecManager(const FakeSecManager&) = delete; + FakeSecManager& operator=(const FakeSecManager&) = delete; + bool AcquireLicense( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contentMetadataLen, + const char* licenseRequest, size_t licenseRequestLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accessTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, + int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) override + { + return false; + } + + bool UpdateSessionState(int64_t sessionId, bool active) override + { + return false; + } + + bool setPlaybackSpeedState(int64_t sessionId, int64_t speed, int64_t position) override + { + return false; + } + bool setVideoWindowSize(int64_t sessionId, int64_t video_width, int64_t video_height) override + { + return false; + } + void ReleaseSession(int64_t sessionId) override + { + + } +}; +#endif /* __CONTENT_SECURITY_MANAGER_H__ */ diff --git a/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.cpp b/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.cpp new file mode 100644 index 000000000..f7ff2e416 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.cpp @@ -0,0 +1,155 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.m +*/ + +/** + * @file ContentSecurityManagerSession.cpp + * @brief Class impl for ContentSecurityManagerSession + */ + +#include "ContentSecurityManager.h" +#include "PlayerLogManager.h" +std::shared_ptr ContentSecurityManagerSession::SessionManager::getInstance(int64_t sessionID, std::size_t inputSummaryHash) +{ + std::shared_ptr returnValue; + + static std::mutex instancesMutex; + std::lock_guard lock{instancesMutex}; + static std::map> instances; + + //Remove pointers to expired instances + { + std::vector keysToRemove; + for (auto i : instances) + { + if(i.second.expired()) + { + keysToRemove.push_back(i.first); + } + } + if(keysToRemove.size()) + { + std::stringstream ss; + ss<<"ContentSecurityManagerSession: "<0) + { + if(instances.count(sessionID)>0) + { + //get an existing pointer which may be no longer valid + returnValue = instances[sessionID].lock(); + + if(!returnValue) + { + //unexpected + MW_LOG_WARN("ContentSecurityManagerSession: session ID %" PRId64 " reused or session closed too early.", + sessionID); + } + } + + if(returnValue) + { + if(returnValue->getInputSummaryHash()!=inputSummaryHash) + { + //this should only occur after a successful updatePlaybackSession + MW_LOG_MIL("ContentSecurityManagerSession: session ID %" PRId64 " input data changed.", sessionID); + returnValue->setInputSummaryHash(inputSummaryHash); + } + } + else + { + /* where an existing, valid instance is not available for sessionID + * create a new instance & save a pointer to it for possible future reuse*/ + returnValue.reset(new SessionManager{sessionID, inputSummaryHash}); + instances[sessionID] = returnValue; + MW_LOG_WARN("ContentSecurityManagerSession: new instance created for ID:%" PRId64 ", %zu instances total.", + sessionID, + instances.size()); + } + } + else + { + MW_LOG_WARN("ContentSecurityManagerSession: invalid ID:%" PRId64 ".", sessionID); + } + + return returnValue; +} + +ContentSecurityManagerSession::SessionManager::~SessionManager() +{ + if(mID>0) + { + ContentSecurityManager::GetInstance()->ReleaseSession(mID); + } +} +void ContentSecurityManagerSession::SessionManager::setInputSummaryHash(std::size_t inputSummaryHash) +{ + mInputSummaryHash=inputSummaryHash; + std::stringstream ss; + ss<<"Input summary hash updated to: "<lock(sessionIdMutex); + int64_t ID = CONTENT_SECURITY_MGR_INVALID_SESSION_ID; + if(mpSessionManager) + { + ID = mpSessionManager->getID(); + } + + return ID; +} + +std::size_t ContentSecurityManagerSession::getInputSummaryHash() +{ + std::lock_guardlock(sessionIdMutex); + std::size_t hash=0; + if(mpSessionManager) + { + hash = mpSessionManager->getInputSummaryHash(); + } + + return hash; +} + diff --git a/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.h b/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.h new file mode 100755 index 000000000..14b4890b2 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ContentSecurityManagerSession.h @@ -0,0 +1,148 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file ContentSecurityManagerSession.h + * @brief Class to Represents an contentsecurity manager session + */ + +#ifndef __CONTENT_SECURITY_MANAGER_SESSION_H__ +#define __CONTENT_SECURITY_MANAGER_SESSION_H__ + +#include +#include +#include +#include +#include + +#define CONTENT_SECURITY_MGR_INVALID_SESSION_ID (-1) + +class ContentSecurityManager; + +/** + * @brief Represents an player sec manager session, + * Sessions are automatically closed there are no ContentSecurityManagerSession objects that reference it*/ +class ContentSecurityManagerSession +{ + /* The coupling between ContentSecurityManager & ContentSecurityManagerSession is not ideal from an architecture standpoint but + * it minimises changes to existing ContentSecurityManager code: + * ~SessionManager() calls ContentSecurityManager::ReleaseSession() + * ContentSecurityManager::acquireLicence() creates instances of ContentSecurityManagerSession*/ + friend ContentSecurityManager; +private: + /** + * @brief Responsible for closing the corresponding sec manager sessions when it is no longer used + */ + class SessionManager + { + private: + int64_t mID; //set once undermutex in constructor + std::atomic mInputSummaryHash; //can be changed by setInputSummaryHash + SessionManager(int64_t sessionID, std::size_t inputSummaryHash); + + public: + /** + * @fn getInstance + * @brief + * Get a shared pointer to an object corresponding to the sessionID, creating a new one if required + */ + static std::shared_ptr getInstance(int64_t sessionID, std::size_t inputSummaryHash); + + int64_t getID(){return mID;} + std::size_t getInputSummaryHash(){return mInputSummaryHash.load();} + void setInputSummaryHash(std::size_t inputSummaryHash); + + //calls ContentSecurityManager::ReleaseSession() on mID + ~SessionManager(); + }; + + std::shared_ptr mpSessionManager; + mutable std::mutex sessionIdMutex; + +public: + /** + * @brief constructor for valid objects + * this will cause ContentSecurityManager::ReleaseSession() to be called on sessionID + * when the last ContentSecurityManagerSession, referencing is destroyed + * this is only intended to be used in ContentSecurityManager::acquireLicence() + * it is the responsibility of ContentSecurityManager::acquireLicence() to ensure sessionID is valid + */ + ContentSecurityManagerSession(int64_t sessionID, std::size_t inputSummaryHash); + + /** + * @brief constructor for an invalid object*/ + ContentSecurityManagerSession(): mpSessionManager(), sessionIdMutex() {}; + + //allow copying, the secManager session will only be closed when all copies have gone out of scope + ContentSecurityManagerSession(const ContentSecurityManagerSession& other): mpSessionManager(), sessionIdMutex() + { + std::lock(sessionIdMutex, other.sessionIdMutex); + std::lock_guard thisLock(sessionIdMutex, std::adopt_lock); + std::lock_guard otherLock(other.sessionIdMutex, std::adopt_lock); + mpSessionManager=other.mpSessionManager; + } + ContentSecurityManagerSession& operator=(const ContentSecurityManagerSession& other) + { + std::lock(sessionIdMutex, other.sessionIdMutex); + std::lock_guard thisLock(sessionIdMutex, std::adopt_lock); + std::lock_guard otherLock(other.sessionIdMutex, std::adopt_lock); + mpSessionManager=other.mpSessionManager; + return *this; + } + + /** + * @fn getSessionID + * @brief + * returns the session ID value for use with JSON API + * The returned value should not be used outside the lifetime of + * the ContentSecurityManagerSession on which this method is called + * otherwise the session may be closed before the ID can be used + */ + int64_t getSessionID(void) const; + std::size_t getInputSummaryHash(); + + bool isSessionValid(void) const + { + std::lock_guardlock(sessionIdMutex); + return (mpSessionManager.use_count()!=0); + } + void setSessionInvalid(void) + { + std::lock_guardlock(sessionIdMutex); + mpSessionManager.reset(); + } + + std::string ToString() + { + std::stringstream ss; + ss<<"Session "; + auto id = getSessionID(); //ID retrieved under mutex + if(id != CONTENT_SECURITY_MGR_INVALID_SESSION_ID) + { + ss< +#include +#include +#include +#include +#include +#include + +std::condition_variable mConnectionCV; +std::mutex mConnectionMutex; +using namespace Firebolt; +uint64_t ContentProtectionFirebolt::mSubscriptionId = 0; + +//Lookup table to convert CPS error to secmanager error +static const std::map> ContentProtectionSecManagerErrorLookUp = +{ + {CONTENT_PROTECTION_SERVICE_INVALID_ASPECT_DIMENSIONS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ASPECT_DIMENSION}}, + {CONTENT_PROTECTION_SERVICE_INVALID_KEY_SYSTEM, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_KEY_SYSTEM_PARAM}}, + {CONTENT_PROTECTION_SERVICE_INVALID_LICENSE_REQUEST, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_DRM_LICENSE_PARAM}}, + {CONTENT_PROTECTION_SERVICE_INVALID_CONTENT_METADATA, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_METADATA}}, + {CONTENT_PROTECTION_SERVICE_INVALID_MEDIA_USAGE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_MEDIA_USAGE}}, + {CONTENT_PROTECTION_SERVICE_INVALID_ACCESS_TOKEN, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_TOKEN}}, + {CONTENT_PROTECTION_SERVICE_INVALID_ACCESS_ATTRIBUTES, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_ATTRIBUTE}}, + {CONTENT_PROTECTION_SERVICE_INVALID_SESSION_ID, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_SESSION_ID}}, + {CONTENT_PROTECTION_SERVICE_INVALID_CLIENT_ID, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CLIENT_ID}}, + {CONTENT_PROTECTION_SERVICE_INVALID_WATERMARKING_PARAM, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_WATERMARK_PARAMETER}}, + {CONTENT_PROTECTION_SERVICE_INVALID_CONTENT_ATTRIBUTES, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_PARAMETER}}, + {CONTENT_PROTECTION_SERVICE_DRM_GENERAL_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {CONTENT_PROTECTION_SERVICE_DRM_LICENSE_NW_TIMEOUT, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_TIMEOUT}}, + {CONTENT_PROTECTION_SERVICE_DRM_LICENSE_NW_CONNECTION_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {CONTENT_PROTECTION_SERVICE_DRM_ACCESS_TOKEN_EXPIRED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_ACCESS_TOKEN_EXPIRED}}, + {CONTENT_PROTECTION_SERVICE_DRM_MAC_KEY_NOT_PROVISIONED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_MAC_TOKEN_NO_PROV}}, + {CONTENT_PROTECTION_SERVICE_DRM_MEMORY_ALLOCATION_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_MEMORY_ALLOCATION_ERROR}}, + {CONTENT_PROTECTION_SERVICE_DRM_SECURITY_API_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_SECAPI_USAGE_FAILURE}}, + {CONTENT_PROTECTION_SERVICE_DRM_ENTITLEMENT_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_ENTITLEMENT_ERROR}}, + {CONTENT_PROTECTION_SERVICE_WATERMARK_GENERAL_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_WATERMARK_FAIL, CONTENT_SECURITY_MANAGER_REASON_WM_GENERAL_FAILURE}}, + {CONTENT_PROTECTION_SERVICE_WATERMARK_TIMEOUT, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_WATERMARK_FAIL, CONTENT_SECURITY_MANAGER_REASON_WM_NETWORK_TIMEOUT}}, + {CONTENT_PROTECTION_SERVICE_WATERMARK_MEMORY_ALLOCATION_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_WATERMARK_FAIL, CONTENT_SECURITY_MANAGER_REASON_WM_MEMORY_ALLOCATION_ERROR}}, + {CONTENT_PROTECTION_SERVICE_WATERMARK_PERCEPTIBILITY_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_WATERMARK_FAIL, CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INVALID_INPUT}} +}; + +/** + * @brief Convert the CPS DRM error code into secmanager error code to have a unified verbose error reported + */ +bool getContentProtectionAsVerboseErrorCode(int32_t httpCode, int32_t &secManagerClass, int32_t &secManagerReasonCode ) +{ + secManagerClass = CONTENT_SECURITY_MANAGER_DRM_FAILURE; + secManagerReasonCode = CONTENT_SECURITY_MANAGER_DRM_GEN_FAILURE; + //look for the correct code from the lookup + auto it = ContentProtectionSecManagerErrorLookUp.find(httpCode); + if (it != ContentProtectionSecManagerErrorLookUp.end()) { + secManagerClass = it->second.first; + secManagerReasonCode = it->second.second; + return true; + } + return false; +} + +ContentProtectionFirebolt::ContentProtectionFirebolt() : mInitialized(false), mSpeedStateMutex(), mContentProtectionMutex(), mFireboltInitMutex() +{ + Initialize(); +} +ContentProtectionFirebolt::~ContentProtectionFirebolt() +{ + DeInitialize(); +} + +// TODO- Yet to test Watermark Events as ContentProtection Thunder Plugin have issues. +void ContentProtectionFirebolt::SubscribeEvents() +{ + auto result = Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().subscribeOnWatermarkStatusChanged( + [this](const auto& status) + { + HandleWatermarkEvent(status.sessionId, status.status, status.appId); + + }); + if(result) + { + mSubscriptionId = result.value(); + MW_LOG_INFO("Subscribed to Firebolt Content Protection events. mSubscriptionId = %lld", mSubscriptionId); + } + else + { + MW_LOG_ERR("Failed to subscribe to watermark events: %d", static_cast(result.error())); + } +} + +void ContentProtectionFirebolt::UnSubscribeEvents() +{ + MW_LOG_INFO("Unsubscribing from Firebolt Content Protection events %lld", mSubscriptionId); + auto result = + Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().unsubscribe(mSubscriptionId); + if (result.error() != Firebolt::Error::None) + { + MW_LOG_ERR("Failed to Unsubscribe to watermark events: %d", static_cast(result.error())); + } +} + +void ContentProtectionFirebolt::HandleWatermarkEvent(const std::string& sessionId, const std::string& statusStr, const std::string& appId) +{ + MW_LOG_INFO("ContentSecurityManager HanldeWatermarkEvent invoked | sessionId=%s status=%s appId=%s", + sessionId.c_str(), statusStr.c_str(), appId.c_str()); + if(mInitialized) + { + MW_LOG_INFO("HandleWaterMarkEvent Triggered"); + PlayerJsonObject statusJson(statusStr); + int reasonCode = -1; + if (statusJson.get("failureReason", reasonCode )) + { + MW_LOG_INFO("HandleWaterMarkEvent Failure ReasonCode %d", reasonCode); + } + else + { + MW_LOG_INFO("Json Parsing Failed to extract Watermarking status"); + } + std::lock_guard lock(mFireboltInitMutex); + if (ContentSecurityManager::SendWatermarkSessionEvent_CB) + { + MW_LOG_INFO("ContentSecurityManager SendWatermarkSessionEvent_CB invoked | sessionId=%s reasonCode =%d appId=%s", + sessionId.c_str(), reasonCode, appId.c_str()); + ContentSecurityManager::SendWatermarkSessionEvent_CB(std::stoi(sessionId), reasonCode, appId); + } + } +} + +void ContentProtectionFirebolt::Initialize() +{ + MW_LOG_INFO("ContentProtectionFirebolt Initialize "); + m_pFireboltInterface = FireboltInterface::GetInstance(); + mInitialized = true; + /* hide watermarking at startup */ + int64_t sessionId = 0; + ShowWatermark(false, sessionId); + /* CP Thunder Plugin doesnt allow invalid sessionId like 0 as in Thunder, hence not calling CloseDrmSession */ + //CloseDrmSession(sessionId); + SubscribeEvents(); +} + +void ContentProtectionFirebolt::DeInitialize() +{ + /* SessionID is not used internally in CP Thunder Plugin for ShowWatermark. + However Native SDK requires it to be sent. Keeping it dummy*/ + ShowWatermark(false, 0); + UnSubscribeEvents(); + mInitialized = false; + m_pFireboltInterface = nullptr; + MW_LOG_INFO("Firebolt Core de-initialized"); +} + +bool ContentProtectionFirebolt::IsActive(bool /*force*/) +{ + return mInitialized; +} + +bool ContentProtectionFirebolt::AcquireLicenseOpenOrUpdate( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contMetaLen, + const char* licenseRequest, size_t licReqLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) +{ + // licenseUrl un-used now + (void) licenseUrl; + + bool ret = false; + bool result = false; + unsigned int retryCount = 0; + bool update = false; + int32_t errorCode = CONTENT_SECURITY_MANAGER_DRM_GEN_ERR_NONE; + + //Initializing it with default error codes (which would be sent if there any jsonRPC + //call failures to thunder) + *statusCode = CONTENT_SECURITY_MANAGER_DRM_FAILURE; + *reasonCode = CONTENT_SECURITY_MANAGER_DRM_GEN_FAILURE; + + PlayerJsonObject param; + PlayerJsonObject response; + PlayerJsonObject sessionConfig; + PlayerJsonObject aspectDimensions; + const char* apiName = "OpenDrmSession"; + + std::string accessTokenStr = accessToken + ? std::string(accessToken, accTokenLen) + : std::string(); + std::string contentMetaDataStr = contentMetadata + ? std::string(contentMetadata, contMetaLen) + : std::string(); + + std::string keySystem = keySystemId ? keySystemId : ""; + std::string licenseRequestStr = licenseRequest + ? std::string(licenseRequest, licReqLen) + : std::string(); + + // use .add() instead of operator[] + sessionConfig.add("distributedTraceType", "money"); + sessionConfig.add("distributedTraceId", moneyTraceMetadata[0][1]); + sessionConfig.add("sessionState", isVideoMuted ? "inactive" : "active"); + + // width/height are numbers, but PlayerJsonObject's add expects strings -> so convert to string + aspectDimensions.add("width", 1920); + aspectDimensions.add("height", 1080); + + std::string mediaUsageStr = mediaUsage ? mediaUsage : ""; + + param.add("sessionConfiguration", sessionConfig); + param.add("contentAspectDimensions", aspectDimensions); + param.add("mediaUsage", mediaUsageStr); + + int64_t sessionId; + if (session.isSessionValid()) + { + // If sessionId is present, we are trying to acquire a new license within the same session + apiName = "UpdateDrmSession"; + sessionId = session.getSessionID(); + update = true; + } + + { + std::lock_guard lock(mContentProtectionMutex); + if (!accessTokenStr.empty() && + !contentMetaDataStr.empty() && + !licenseRequestStr.empty()) + + { + MW_LOG_INFO("Access token, Content metadata and license request are copied successfully, passing details with ContentProtection"); + + //Set json params to be used by sec manager + param.add("accessToken", accessTokenStr); + param.add("contentMetadata", contentMetaDataStr); + + std::string initData = param.print_UnFormatted(); + MW_LOG_WARN("ContentProtection %s param: %s",apiName, initData.c_str()); + bool result = false; + //invoke "openDrmSession" or "updateDrmSession" with retries for specific error cases + do + { + std::string drmSession; + if (!IsActive()) + { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return false; + } + + // Call the openDrmSession method from the interface + if(!update) + { + result = OpenDrmSession(clientId, appId, keySystem, + licenseRequest, initData, sessionId, errorCode, drmSession); + } + else + { + result = + UpdateDrmSession(sessionId, errorCode, + licenseRequest, initData, drmSession); + } + if (drmSession.empty()) + { + MW_LOG_WARN("DrmSession Response is empty."); + } + if (result) + { + ContentSecurityManagerSession newSession; + PlayerJsonObject sessionObj(drmSession); + + if(!drmSession.empty()) + { + /* + * Ensure all sessions have a Session ID created to manage lifetime + * multiple object creation is OK as an existing instance should be returned + * where input data changes e.g. following a call to updatePlaybackSession + * the input data to the shared session is updated here + */ + + newSession = ContentSecurityManagerSession(sessionId, + ContentSecurityManager::getInputSummaryHash(moneyTraceMetadata, contentMetadata, + contMetaLen, licenseRequest, keySystemId, + mediaUsage, accessToken, isVideoMuted)); + + std::string license; + if (sessionObj.get("license", license)) + { + MW_LOG_TRACE("ContentProtection obtained license with length: %zu and data: %s", license.size(), license.c_str()); + + if (!license.empty()) + { + unsigned char* licenseDecoded = nullptr; + size_t licenseDecodedLen = 0; + + licenseDecoded = base64_Decode(license.c_str(), &licenseDecodedLen); + MW_LOG_TRACE("ContentProtection license decoded len: %zu and data: %p", licenseDecodedLen, licenseDecoded); + + if (licenseDecoded != nullptr && licenseDecodedLen != 0) + { + *licenseResponse = (char*)malloc(licenseDecodedLen); + if (*licenseResponse) + { + memcpy(*licenseResponse, licenseDecoded, licenseDecodedLen); + *licenseResponseLength = licenseDecodedLen; + + MW_LOG_INFO("ContentProtection license post base64 decode length: %zu", *licenseResponseLength); + } + else + { + MW_LOG_ERR("ContentProtection failed to allocate memory for license!"); + } + free(licenseDecoded); + ret = true; + } + else + { + MW_LOG_ERR("ContentProtection license base64 decode failed!"); + } + } + } + } + + if (newSession.isSessionValid() && !session.isSessionValid()) + { + session = newSession; + } + + } + + if(!drmSession.empty()) + { + + PlayerJsonObject response(drmSession); + PlayerJsonObject resultContext; + if (response.get("secManagerResultContext", resultContext)) + { + int value = -1; + + // Get statusCode + if (resultContext.get("class", value)) + { + *statusCode = value; + } + + // Get reasonCode + if (resultContext.get("reason", value)) + { + *reasonCode = value; + } + + // Get businessStatus + if (resultContext.get("businessStatus", value)) + { + *businessStatus = value; + } + + MW_LOG_WARN("ContentProtection Parsed Status Code: %d, Reason: %d, Business Status: %d", + statusCode ? *statusCode : -1, + reasonCode ? *reasonCode : -1, + businessStatus ? *businessStatus : -1); + } + } + else if( errorCode != CONTENT_SECURITY_MANAGER_DRM_GEN_ERR_NONE) + { + getContentProtectionAsVerboseErrorCode(errorCode,*statusCode,*reasonCode); + } + if(!ret) + { + //As per Secmanager retry is meaningful only for + //Digital Rights Management Failure Class (200) or + //Watermarking Failure Class (300) + //having the reasons - + //DRM license service network timeout / Request/network time out (3). + //DRM license network connection failure/Watermark vendor-access service connection failure (4) + //DRM license server busy/Watermark service busy (5) + if((*statusCode == CONTENT_SECURITY_MANAGER_DRM_FAILURE || *statusCode == CONTENT_SECURITY_MANAGER_WM_FAILURE) && + (*reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_TIMEOUT || + *reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_CON_FAILURE || + *reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_BUSY ) && retryCount < MAX_LICENSE_REQUEST_ATTEMPTS) + { + ++retryCount; + MW_LOG_WARN("ContentProtection license request failed, response for %s : statusCode: %d, reasonCode: %d, so retrying with delay %d, retry count : %u", apiName, *statusCode, *reasonCode, sleepTime, retryCount ); + ms_sleep(sleepTime); + } + else + { + MW_LOG_ERR("ContentProtection license request failed, response for %s : statusCode: %d, reasonCode: %d", apiName, *statusCode, *reasonCode); + break; + } + } + else + { + MW_LOG_INFO("ContentProtection license request success, response for %s : statusCode: %d, reasonCode: %d, session status: %s", apiName, *statusCode, *reasonCode, isVideoMuted ? "inactive" : "active"); + break; + } + } + while(retryCount < MAX_LICENSE_REQUEST_ATTEMPTS); + } + else + { + MW_LOG_ERR("ContentProtection Failed to copy access token to the shared memory, %s is aborted statusCode: %d, reasonCode: %d", apiName, *statusCode, *reasonCode); + } + } + return ret; +} + +void ContentProtectionFirebolt::CloseDrmSession(int64_t sessionId) +{ + // Check if Firebolt is active before proceeding + if (!IsActive()) + { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return; + } + std::lock_guard lock(mContentProtectionMutex); + // Call the closeDrmSession method from the interface + auto result = Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().closeDrmSession(std::to_string(sessionId)); + // Check for errors + if (result.error() == Firebolt::Error::None) + { + // No error, session was closed successfully + MW_LOG_INFO("Drm session closed successfully for sessionId: %" PRId64 "", sessionId); + } + else + { + // An error occurred, log the error + MW_LOG_ERR("CloseDrmSession: failed for sessionID: %" PRId64 " Firebolt Error: \"%d\"", sessionId, static_cast(result.error())); + } +} +bool ContentProtectionFirebolt::SetDrmSessionState(int64_t sessionId, bool active) +{ + bool ret = false; + Firebolt::Error error = Firebolt::Error::None; + // Check if Firebolt is active before proceeding + if (!IsActive()) + { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return ret; + } + Firebolt::ContentProtection::SessionState sessionState; + if (active) + { + sessionState = Firebolt::ContentProtection::SessionState::ACTIVE; + } + else + { + sessionState = Firebolt::ContentProtection::SessionState::INACTIVE; + } + std::lock_guard lock(mContentProtectionMutex); + // Call the setDrmSessionState method from the interface + auto result = Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().setDrmSessionState(std::to_string(sessionId), sessionState); + // Check for errors + if (result.error() == Firebolt::Error::None) + { + // No error, state was set successfully + MW_LOG_INFO("DRM session state set to %d for sessionId: %" PRId64 "", active, sessionId); + ret = true; + } + else + { + // An error occurred, log the error + MW_LOG_ERR("DRM session state failed to set to %d for sessionId: %" PRId64 ", Firebolt Error: \"%d\"", static_cast(sessionState), sessionId, static_cast(result.error())); + } + return ret; +} +/** + * @brief To set Playback Speed State to SecManager + */ +bool ContentProtectionFirebolt::SetPlaybackPosition(int64_t sessionId, float speed, int32_t position) +{ + bool ret = false; + // Check if Firebolt is active before proceeding + if (!IsActive()) + { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return ret; + } + std::lock_guard lock(mContentProtectionMutex); + // Call the setPlaybackPosition method from the interface + auto result = Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().setPlaybackPosition(std::to_string(sessionId), speed, position); + // Check for errors + if (result.error() == Firebolt::Error::None) + { + // No error, playback position was set successfully + MW_LOG_INFO("SetPlaybackPosition set successfully for sessionId: %" PRId64 " at position %d with speed %.2f", sessionId, position, speed); + ret = true; + } + else + { + // An error occurred, log the error + MW_LOG_ERR("SetPlaybackPosition failed to set for ID: %" PRId64 " Firebolt Error: \"%d\"", sessionId, static_cast(result.error())); + } + return ret; +} +/** + * @brief Show watermark image + */ +void ContentProtectionFirebolt::ShowWatermark(bool show, int64_t sessionId) +{ + // Check if Firebolt is active before proceeding + if (!IsActive()) { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return; + } + + std::lock_guard lock(mContentProtectionMutex); + // Call the showWatermark method from the interface + auto result = Firebolt::IFireboltAampAccessor::Instance().ContentProtectionInterface().showWatermark(std::to_string(sessionId), show, 0); + // Check for errors + if (result.error() == Firebolt::Error::None) { + // No error, watermark visibility was successfully set + MW_LOG_INFO("ShowWatermark visibility set successfully. Show: %d", show); + } else { + // An error occurred, log the error + MW_LOG_ERR("showWatermark failed. Firebolt Error: \"%d\"", static_cast(result.error())); + } +} +static Firebolt::ContentProtection::KeySystem convertStringToKeySystem(const std::string& keySystemStr) +{ + if (keySystemStr.find("widevine") != std::string::npos) + { + return Firebolt::ContentProtection::KeySystem::WIDEVINE; + } + else if (keySystemStr.find("playready") != std::string::npos) + { + return Firebolt::ContentProtection::KeySystem::PLAYREADY; + } + else if (keySystemStr.find("clearkey") != std::string::npos) + { + return Firebolt::ContentProtection::KeySystem::CLEARKEY; + } + else + { + MW_LOG_ERR("Unknown KeySystem string: %s returning to default", keySystemStr.c_str()); + return Firebolt::ContentProtection::KeySystem::WIDEVINE; // safest fallback default + } +} +bool ContentProtectionFirebolt::OpenDrmSession(std::string& clientId, std::string appId, std::string keySystem, std::string licenseRequest, std::string initData, int64_t &sessionId, int32_t &errorCode, std::string &response) +{ + bool ret = false; + // Check if the system is active before proceeding + if (!IsActive()) { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return false; // Return false if system is not active + } +// Firebolt::ContentProtection::DRMSession drmSession; + auto drmSession = Firebolt::IFireboltAampAccessor::Instance() + .ContentProtectionInterface() + .openDrmSession(clientId, appId, convertStringToKeySystem(keySystem), + licenseRequest, initData); + if (drmSession) + { + MW_LOG_WARN("DRM session opened successfully with sessionId: '%s' with Response %s", drmSession->sessionId.c_str(), drmSession->openSessionResponse.c_str()); + response = drmSession->openSessionResponse; + sessionId = std::stoll(drmSession->sessionId); + ret = true; + } + else + { + errorCode = static_cast(drmSession.error()); + MW_LOG_ERR("openDrmSession: Firebolt Error: \"%d\"", errorCode); + } + return ret; +} +bool ContentProtectionFirebolt::UpdateDrmSession(int64_t sessionId, int32_t &errorCode, std::string licenseRequest, std::string initData, std::string &response) +{ + bool ret = false; + // Check if the system is active before proceeding + if (!IsActive()) { + MW_LOG_ERR("Firebolt is not active (or) channel couldn't be opened"); + return false; // Return false if system is not active + } + + auto drmSession = Firebolt::IFireboltAampAccessor::Instance() + .ContentProtectionInterface() + .updateDrmSession(std::to_string(sessionId), + licenseRequest, initData); + if(drmSession) + { + MW_LOG_INFO("DRM session updated successfully for sessionId: %" PRId64 " with Response %s", sessionId, drmSession.value().c_str()); + response = drmSession.value(); + ret = true; + } + else + { + errorCode = static_cast(drmSession.error()); + MW_LOG_ERR("updateDrmSession: Firebolt Error: \"%d\"", errorCode); + } + return ret; +} diff --git a/middleware/externals/contentsecuritymanager/IFirebolt/ContentProtectionFirebolt.h b/middleware/externals/contentsecuritymanager/IFirebolt/ContentProtectionFirebolt.h new file mode 100644 index 000000000..7c3ee35c3 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/IFirebolt/ContentProtectionFirebolt.h @@ -0,0 +1,207 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file ContentProtectionFirebolt.h + * @brief Class to communicate with Content Protection Firebolt SDK + */ +#ifndef CONTENT_PROTECTION_FIREBOLT_H +#define CONTENT_PROTECTION_FIREBOLT_H + +#include "ContentSecurityManager.h" +#include "ContentSecurityManagerSession.h" + +#include +#include +#include +#include +#include +#include +#include + +class FireboltInterface; //forward declaration + +typedef enum { + // API Errors + CONTENT_PROTECTION_SERVICE_INVALID_SESSION_CONFIG = 21001, + CONTENT_PROTECTION_SERVICE_INVALID_ASPECT_DIMENSIONS = 21002, + CONTENT_PROTECTION_SERVICE_INVALID_KEY_SYSTEM = 21003, + CONTENT_PROTECTION_SERVICE_INVALID_LICENSE_REQUEST = 21004, + CONTENT_PROTECTION_SERVICE_INVALID_CONTENT_METADATA = 21005, + CONTENT_PROTECTION_SERVICE_INVALID_MEDIA_USAGE = 21006, + CONTENT_PROTECTION_SERVICE_INVALID_ACCESS_TOKEN = 21007, + CONTENT_PROTECTION_SERVICE_INVALID_ACCESS_ATTRIBUTES = 21008, + CONTENT_PROTECTION_SERVICE_INVALID_SESSION_ID = 21009, + CONTENT_PROTECTION_SERVICE_INVALID_APP_ID = 21010, + CONTENT_PROTECTION_SERVICE_INVALID_EVENT_ID = 21011, + CONTENT_PROTECTION_SERVICE_INVALID_CLIENT_ID = 21012, + CONTENT_PROTECTION_SERVICE_INVALID_PERCEPTION_ID = 21013, + CONTENT_PROTECTION_SERVICE_INVALID_WATERMARKING_PARAM = 21014, + CONTENT_PROTECTION_SERVICE_INVALID_CONTENT_ATTRIBUTES = 21015, + CONTENT_PROTECTION_SERVICE_INVALID_ACCOUNT_TOKEN = 21016, + CONTENT_PROTECTION_SERVICE_INVALID_APP_CERT_CALLBACK = 21017, + CONTENT_PROTECTION_SERVICE_INVALID_RSA_SIGNATURE_CALLBACK = 21018, + CONTENT_PROTECTION_SERVICE_INVALID_ACCOUNT_SOURCE_CALLBACK = 21019, + + // DRM Errors + CONTENT_PROTECTION_SERVICE_DRM_GENERAL_FAILURE = 22001, + CONTENT_PROTECTION_SERVICE_DRM_SESSION_NOT_FOUND = 22002, + CONTENT_PROTECTION_SERVICE_DRM_LICENSE_NW_TIMEOUT = 22003, + CONTENT_PROTECTION_SERVICE_DRM_LICENSE_NW_CONNECTION_FAILURE = 22004, + CONTENT_PROTECTION_SERVICE_DRM_LICENSE_SERVER_BUSY = 22005, + CONTENT_PROTECTION_SERVICE_DRM_ACCESS_TOKEN_ACQUISITION_FAILED = 22006, + CONTENT_PROTECTION_SERVICE_DRM_ACCESS_TOKEN_IP_MISMATCH = 22007, + CONTENT_PROTECTION_SERVICE_DRM_ACCESS_TOKEN_EXPIRED = 22008, + CONTENT_PROTECTION_SERVICE_DRM_DEVICE_TOKEN_EXPIRED = 22009, + CONTENT_PROTECTION_SERVICE_DRM_MAC_TOKEN_MISSING_OR_EXPIRED = 22010, + CONTENT_PROTECTION_SERVICE_DRM_MAC_KEY_NOT_PROVISIONED = 22011, + CONTENT_PROTECTION_SERVICE_DRM_MEMORY_ALLOCATION_ERROR = 22012, + CONTENT_PROTECTION_SERVICE_DRM_SECURITY_API_FAILURE = 22013, + CONTENT_PROTECTION_SERVICE_DRM_CLIENT_TRANSACTION_ERROR = 22014, + CONTENT_PROTECTION_SERVICE_DRM_LICENSE_AUTHORIZATION_FAILED = 22015, + CONTENT_PROTECTION_SERVICE_DRM_ENTITLEMENT_FAILURE = 22016, + CONTENT_PROTECTION_SERVICE_DRM_AUTHENTICATION_FAILURE = 22017, + CONTENT_PROTECTION_SERVICE_DRM_MISSING_PARTNER_CONTEXT = 22018, + + // Watermarking Errors + CONTENT_PROTECTION_SERVICE_WATERMARK_GENERAL_FAILURE = 23001, + CONTENT_PROTECTION_SERVICE_WATERMARK_SESSION_DENIED = 23002, + CONTENT_PROTECTION_SERVICE_WATERMARK_TIMEOUT = 23003, + CONTENT_PROTECTION_SERVICE_WATERMARK_CONNECTION_FAILURE = 23004, + CONTENT_PROTECTION_SERVICE_WATERMARK_SERVICE_BUSY = 23005, + CONTENT_PROTECTION_SERVICE_WATERMARK_PROTOCOL_ERROR = 23006, + CONTENT_PROTECTION_SERVICE_WATERMARK_IMAGE_TAMPERING = 23007, + CONTENT_PROTECTION_SERVICE_WATERMARK_REQUEST_TAMPERING = 23008, + CONTENT_PROTECTION_SERVICE_WATERMARK_RESPONSE_TAMPERING = 23009, + CONTENT_PROTECTION_SERVICE_WATERMARK_INVALID_ACCOUNT = 23010, + CONTENT_PROTECTION_SERVICE_WATERMARK_INVALID_ACCESS_TOKEN = 23011, + CONTENT_PROTECTION_SERVICE_WATERMARK_MEMORY_ALLOCATION_ERROR = 23012, + CONTENT_PROTECTION_SERVICE_WATERMARK_SECURITY_API_FAILURE = 23013, + CONTENT_PROTECTION_SERVICE_WATERMARK_PERCEPTIBILITY_ERROR = 23014 +}ContentProtectionSecurityPluginErrorCode; + +/** + * @class ContentProtectionFirebolt + * @brief Provides integration with Firebolt DRM SDK for content protection + */ +class ContentProtectionFirebolt : public ContentSecurityManager +{ +public: + /** + * @brief Default constructor + */ + ContentProtectionFirebolt(); + /** + * @brief Destructor + */ + ~ContentProtectionFirebolt() ; + ContentProtectionFirebolt(const ContentProtectionFirebolt&) = delete; + ContentProtectionFirebolt& operator=(const ContentProtectionFirebolt&) = delete; + /** + * @brief Initializes the Firebolt client + */ + void Initialize(); + /** + * @brief De-initializes and tears down connection + */ + void DeInitialize(); + /** + * @brief Checks if Firebolt is active + * @param force + * @return true if initialized + */ + bool IsActive(bool force=false); + /** + * @brief Sets DRM session state (e.g., active/inactive) + * @param sessionId DRM session ID + * @param active Whether the session should be marked active + * @return true on success, false otherwise + */ + bool SetDrmSessionState(int64_t sessionId, bool active) override; + /** + * @brief Closes an existing DRM session + * @param sessionId DRM session ID + * @return true if closed successfully + */ + void CloseDrmSession(int64_t sessionId) override; + /* Run AcquireLicenseOpenOrUpdate is the old AcquireLicense code + * It is used by AcquireLicense() to for opening sessions & for calling update when this is required*/ + bool AcquireLicenseOpenOrUpdate( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contentMetadataLen, + const char* licenseRequest, size_t licenseRequestLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accessTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, + int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) override; + /** + * @brief Opens a new DRM session + * @param[in,out] clientId Client identifier (may be modified) + * @param appId Application ID + * @param keySystem DRM system string (e.g., widevine) + * @param licenseRequest License challenge + * @param initData Initialization data + * @param[out] sessionId Output DRM session ID + * @param[out] response License server response + * @return true on success + */ + bool OpenDrmSession(std::string& clientId, std::string appId, std::string keySystem, std::string licenseRequest, std::string initData, int64_t &sessionId, int32_t &errorCode, std::string &response); + /** + * @brief Sends update license challenge to existing session + * @param sessionId DRM session ID + * @param licenseRequest Challenge string + * @param initData Initialization data + * @param[out] response Response from Firebolt + * @return true on success + */ + bool UpdateDrmSession(int64_t sessionId, int32_t &errorCode, std::string licenseRequest, std::string initData, std::string &response); + /** + * @brief Sets playback state for watermark alignment + * @param sessionId Session ID + * @param speed Playback rate (1.0 = normal) + * @param position Current position in seconds + * @return true if command succeeded + */ + bool SetPlaybackPosition(int64_t sessionId, float speed, int32_t position) override; + /** + * @brief Enables or disables visual watermark + * @param show Show/hide flag + * @param sessionId Session context (optional) + */ + void ShowWatermark(bool show, int64_t sessionId); + + void HandleWatermarkEvent(const std::string& sessionId, const std::string& statusStr, const std::string& appId); +private: + /** + * @brief Subscribes to Firebolt events (currently stub) + * @return true if stub accepted + */ + void SubscribeEvents(); + /** + * @brief Unsubscribe from Firebolt events (currently stub) + * @return true if stub accepted + */ + void UnSubscribeEvents(); + std::mutex mFireboltInitMutex; + std::mutex mContentProtectionMutex; + std::mutex mSpeedStateMutex; + bool mInitialized; + static uint64_t mSubscriptionId; + std::shared_ptr m_pFireboltInterface; +}; + +#endif /* CONTENT_PROTECTION_FIREBOLT_H */ diff --git a/middleware/externals/contentsecuritymanager/PlayerMemoryUtils.cpp b/middleware/externals/contentsecuritymanager/PlayerMemoryUtils.cpp new file mode 100644 index 000000000..094c3af48 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/PlayerMemoryUtils.cpp @@ -0,0 +1,106 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerMemoryUtils.cpp + * @brief Helper functions for memory management + */ + +#include "PlayerMemoryUtils.h" +#include "PlayerLogManager.h" +#include +#include +#include + +/** + * @brief Creates shared memory and provides the key + */ +void * player_CreateSharedMem( size_t shmLen, key_t & shmKey) +{ + void *shmPointer = NULL; + if( shmLen > 0) + { + for( int retryCount=0; retryCount +#include +#define SHM_ACCESS_PERMISSION 0666 +#define SHMGET_RETRY_MAX 10 + +/** + * @fn player_CreateSharedMem + * @param shmLen Length of the buffer to be created + * @param shmKey shared memory key + */ +void * player_CreateSharedMem( size_t shmLen, key_t & shmKey); +/** + * @fn player_CleanUpSharedMem + * @param shmPointer Pointer to the created memory + * @param shmKey shared memory key + * @param shmLen Length of the buffer + */ +void player_CleanUpSharedMem(void* shmPointer, key_t shmKey, size_t shmLen); +#endif /* __PLAYER_MEMORY_UTILS_H__ */ diff --git a/middleware/externals/contentsecuritymanager/PlayerSecInterface.cpp b/middleware/externals/contentsecuritymanager/PlayerSecInterface.cpp new file mode 100644 index 000000000..de955a4e5 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/PlayerSecInterface.cpp @@ -0,0 +1,214 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerSecInterface.cpp + * @brief Interface for PlayerSec client + */ + + +#include "PlayerSecInterface.h" +#ifdef USE_SECCLIENT +#include "sec_client.h" +#endif + +#ifdef USE_SECCLIENT +//Lookup table to convert secclient error to secmanager error +std::map> secClientSeManagerErrorLookUp = +{ + {SEC_CLIENT_RESULT_SUCCESS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SUCCESS, CONTENT_SECURITY_MANAGER_SUCCESS}}, + {SEC_CLIENT_RESULT_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_INVALID_PARAMETERS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_GENERIC, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_TOO_MANY_REDIRECTS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_CONNECTIVITY, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_HOST_RESOLUTION, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_TIMEOUT, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_TLS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL}}, + {SEC_CLIENT_RESULT_MAC_AUTH_NOT_PROVISIONED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_MAC_TOKEN_NO_PROV}}, + {SEC_CLIENT_RESULT_MONEYTRACE_MISSING_OR_MALFORMED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_REQUEST_CREATION_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_MALFORMED_RESPONSE_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_MALFORMED_CONFIGURATION_PARAMETER_ERROR,{CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_MEMORY_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_MEMORY_ALLOCATION_ERROR}}, + {SEC_CLIENT_RESULT_FAILED_TO_LOAD_SEC_LIBRARY_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_INVALID_LOAD_CONFIGURATION_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_INVALID_LOAD_CRYPTO_TYPE_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_INVALID_LOAD_TIMEOUT_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_JSON_ENCODING_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_UNSUPPORTED_FEATURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_WATERMARKING_NOT_REQUIRED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_WATERMARKING_SESSION_DENIED, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_GENERAL_CRYPTOGRAPHIC_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_UNKNOWN_CRYPTO_ENGINE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_UNKNOWN_KEY_AGREEMENT_ALGO, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_INTERNAL_ERROR_EXPONENT, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_INTERNAL_ERROR_RANDOM, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_UNSUPPORTED_HASH_ALGORITHM, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_ERROR_CREATING_MAC_AUTH_HEADER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_ERROR_GENERATING_KEY_PAIR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_ERROR_GENERATING_DERIVED_KEYS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_ERROR_GENERATING_MAC_VALUE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_CLIENT_VERIFICATION_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_MISSING_SESSION_CREDENTIALS, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_ENCRYPTION_KEY_MISMATCH, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_PKCS7_SIGNATURE_ERROR, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_CLIENT_AUTH_TOKEN_FAILURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_MISSING_KEY_PROVISIONING_DATA, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_MISSING_DEVICE_AUTH_DATA, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_CONFIGURATION_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_INVALID_DEVICE_TOKEN_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_KEY_PROVISION_RESULT_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_DEVICE_AUTHENTICATION_RESULT_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_DEVICE_ATTRIBUTES_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_ACCESS_TOKEN_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_TOKEN}}, + {SEC_CLIENT_RESULT_INVALID_ACCESS_ATTRIBUTES_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_ATTRIBUTE}}, + {SEC_CLIENT_RESULT_INVALID_KEY_SYSTEM_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_KEY_SYSTEM_PARAM}}, + {SEC_CLIENT_RESULT_INVALID_LICENSE_REQUEST_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_DRM_LICENSE_PARAM}}, + {SEC_CLIENT_RESULT_INVALID_CONTENT_METADATA_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_METADATA}}, + {SEC_CLIENT_RESULT_INVALID_MEDIA_USAGE_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_MEDIA_USAGE}}, + {SEC_CLIENT_RESULT_INVALID_LICENSE_IDENTIFIER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_REQUEST_BODY, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_ENTITLEMENT_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_ACCOUNT_TOKEN_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_REQUEST_METADATA_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL, 0}}, + {SEC_CLIENT_RESULT_INVALID_CONTENT_IDENTIFIER_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_DOWNLOADS_LIST_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_ACQUIRE_SAT_FUNCTION_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR}}, // Undefined + {SEC_CLIENT_RESULT_INVALID_WATERMARKING_SYSTEM_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_WATERMARK_PARAMETER}}, + {SEC_CLIENT_RESULT_INVALID_CONTENT_ATTRIBUTES_PARAMETER, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL, CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_PARAMETER}}, + {SEC_CLIENT_RESULT_UNSUPPORTED_OR_INVALID_WATERMARK_FEATURE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}}, + {SEC_CLIENT_RESULT_INVALID_WATERMARK_SESSION_RESPONSE, {CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL, CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE}} +}; +#endif + +/** + * @brief Check if sec feature is enabled + */ +bool isSecFeatureEnabled() +{ +#if defined(USE_SECMANAGER) || defined(USE_SECCLIENT) + return true; +#else + return false; +#endif +} + +/** + * @brief Check if Sec Manager is enabled + */ +bool isSecManagerEnabled() +{ +#if defined(USE_SECMANAGER) + return true; +#else + return false; +#endif +} + +/** + * @brief Convert the secclient DRM error code into secmanager error code to have a unified verbose error reported + */ +bool getAsVerboseErrorCode(int32_t httpCode, int32_t &secManagerClass, int32_t &secManagerReasonCode ) +{ +#ifdef USE_SECCLIENT + //look for the correct code from the lookup + auto it = secClientSeManagerErrorLookUp.find(-httpCode); //Secclient error codes are -ve + if (it != secClientSeManagerErrorLookUp.end()) { + secManagerClass = it->second.first; + secManagerReasonCode = it->second.second; + return true; + } +#endif + return false; +} + +/** + * @brief Acquire license via sec client + */ +int32_t PlayerSecInterface::PlayerSec_AcquireLicense(const char *serviceHostUrl, uint8_t numberOfRequestMetadataKeys, + const char *requestMetadata[][2], uint8_t numberOfAccessAttributes, + const char *accessAttributes[][2], const char *contentMetadata, + size_t contentMetadataLength, const char *licenseRequest, + size_t licenseRequestLength, const char *keySystemId, + const char *mediaUsage, const char *accessToken, + char **licenseResponse, size_t *licenseResponseLength, + uint32_t *refreshDurationSeconds, + PlayerSecExtendedStatus *statusInfo) +{ +#ifdef USE_SECCLIENT + int32_t sec_client_result = SEC_CLIENT_RESULT_FAILURE; + SecClient_ExtendedStatus statusExtInfo = {}; + sec_client_result = SecClient_AcquireLicense(serviceHostUrl, numberOfRequestMetadataKeys, + requestMetadata, numberOfAccessAttributes, + accessAttributes, + contentMetadata, + contentMetadataLength, + licenseRequest, licenseRequestLength, keySystemId, mediaUsage, + accessToken, + licenseResponse, licenseResponseLength, refreshDurationSeconds, &statusExtInfo); + if (statusInfo != nullptr) { + statusInfo->accessAttributeStatus = statusExtInfo.accessAttributeStatus; + statusInfo->statusCode = statusExtInfo.statusCode; + } + return sec_client_result; +#else + return 0; +#endif +} + +/** + * @brief Free resource + */ +int32_t PlayerSecInterface::PlayerSec_FreeResource(const char *resource) +{ +#ifdef USE_SECCLIENT + int32_t sec_client_result = SEC_CLIENT_RESULT_FAILURE; + if (resource) + { + sec_client_result = SecClient_FreeResource(resource); + } + return sec_client_result; +#else + return 0; +#endif +} + +/** + * @brief Check if sec request failed + */ +bool PlayerSecInterface::isSecRequestFailed(int32_t requestResult) +{ + bool isRequestFailed = 0; +#ifdef USE_SECCLIENT + isRequestFailed = (requestResult != SEC_CLIENT_RESULT_SUCCESS); +#endif + return isRequestFailed; +} + +/** + * @brief Check if sec request result is in range + */ +bool PlayerSecInterface::isSecResultInRange(int32_t requestResult) +{ + bool isRequestInRange = 0; +#ifdef USE_SECCLIENT + isRequestInRange = (requestResult >= SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_TLS && requestResult <= SEC_CLIENT_RESULT_HTTP_RESULT_FAILURE_GENERIC ); +#endif + return isRequestInRange; +} diff --git a/middleware/externals/contentsecuritymanager/PlayerSecInterface.h b/middleware/externals/contentsecuritymanager/PlayerSecInterface.h new file mode 100644 index 000000000..3ea5154ed --- /dev/null +++ b/middleware/externals/contentsecuritymanager/PlayerSecInterface.h @@ -0,0 +1,223 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef PLAYER_SEC_INTERFACE_H +#define PLAYER_SEC_INTERFACE_H + +/** + * @file PlayerSecInterface.h + * @brief Log manager for Player Interface + */ + +#include +#include +#include +#include + +typedef enum { + CONTENT_SECURITY_MANAGER_CLASS_RESULT_SUCCESS = 0, + CONTENT_SECURITY_MANAGER_CLASS_RESULT_API_FAIL = 100, + CONTENT_SECURITY_MANAGER_CLASS_RESULT_DRM_FAIL = 200, + CONTENT_SECURITY_MANAGER_CLASS_RESULT_WATERMARK_FAIL = 300, + CONTENT_SECURITY_MANAGER_CLASS_RESULT_SECCLIENT_FAIL = 400, + CONTENT_SECURITY_MANAGER_CLASS_RESULT_UNDEFINED = 9999 +} ContentSecurityManagerResultClassStatusCode; + +typedef enum { + CONTENT_SECURITY_MANAGER_SUCCESS = 0, + CONTENT_SECURITY_MANAGER_SUCCESS_WATERMARK_SESSION_ENGAGED = 100, + CONTENT_SECURITY_MANAGER_SUCCESS_WATERMARK_NOT_REQUIRED = 101 +} ContentSecurityManagerResultSuccessCode; + +typedef enum { + CONTENT_SECURITY_MANAGER_REASON_DRM_ERR_NONE = 0, + CONTENT_SECURITY_MANAGER_REASON_DRM_GENERAL_FAILURE = 1, + CONTENT_SECURITY_MANAGER_REASON_DRM_NO_PLAYBACK_SESSION = 2, + CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_TIMEOUT = 3, + CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_NETWORK_FAIL = 4, + CONTENT_SECURITY_MANAGER_REASON_DRM_LICENSE_BUSY = 5, + CONTENT_SECURITY_MANAGER_REASON_DRM_ACCESS_TOKEN_ERROR = 6, + CONTENT_SECURITY_MANAGER_REASON_DRM_ACCESS_TOKEN_IP_DIFF = 7, + CONTENT_SECURITY_MANAGER_REASON_DRM_ACCESS_TOKEN_EXPIRED = 8, + CONTENT_SECURITY_MANAGER_REASON_DRM_DEVICE_TOKEN_EXPIRED = 9, + CONTENT_SECURITY_MANAGER_REASON_DRM_MAC_TOKEN_MISSING = 10, + CONTENT_SECURITY_MANAGER_REASON_DRM_MAC_TOKEN_NO_PROV = 11, + CONTENT_SECURITY_MANAGER_REASON_DRM_MEMORY_ALLOCATION_ERROR = 12, + CONTENT_SECURITY_MANAGER_REASON_DRM_SECAPI_USAGE_FAILURE = 13, + CONTENT_SECURITY_MANAGER_REASON_DRM_PERMISSION_DENIED = 100, + CONTENT_SECURITY_MANAGER_REASON_DRM_RULE_ERROR = 101, + CONTENT_SECURITY_MANAGER_REASON_DRM_ENTITLEMENT_ERROR = 102, + CONTENT_SECURITY_MANAGER_REASON_DRM_AUTHENTICATION_FAIL = 103 +} ContentSecurityManagerResultDRMCode; + +typedef enum { + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_SESSION_CONFIG = 1, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ASPECT_DIMENSION = 2, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_KEY_SYSTEM_PARAM = 3, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_DRM_LICENSE_PARAM = 4, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_METADATA = 5, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_MEDIA_USAGE = 6, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_TOKEN = 7, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_ACCESS_ATTRIBUTE = 8, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_SESSION_ID = 9, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_APPLICATION_ID = 10, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_EVENT_ID = 11, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CLIENT_ID = 12, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_PERCEPTION_ID = 13, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_WATERMARK_PARAMETER = 14, + CONTENT_SECURITY_MANAGER_REASON_API_INVALID_CONTENT_PARAMETER = 15, + CONTENT_SECURITY_MANAGER_REASON_API_UNDEFINED_ERROR = 9999 +} ContentSecurityManagerResultApiCode; + +typedef enum { + // Watermarking Errors + CONTENT_SECURITY_MANAGER_REASON_WM_GENERAL_FAILURE = 1, + CONTENT_SECURITY_MANAGER_REASON_WM_SESSION_DENIED = 2, + CONTENT_SECURITY_MANAGER_REASON_WM_NETWORK_TIMEOUT = 3, + CONTENT_SECURITY_MANAGER_REASON_WM_CONNECTION_FAILURE = 4, + CONTENT_SECURITY_MANAGER_REASON_WM_SERVICE_BUSY = 5, + CONTENT_SECURITY_MANAGER_REASON_WM_PROTOCOL_ERROR = 6, + CONTENT_SECURITY_MANAGER_REASON_WM_IMAGE_TAMPERING = 7, + CONTENT_SECURITY_MANAGER_REASON_WM_REQUEST_TAMPERING = 8, + CONTENT_SECURITY_MANAGER_REASON_WM_RESPONSE_TAMPERING = 9, + CONTENT_SECURITY_MANAGER_REASON_WM_INVALID_ACCOUNT = 10, + CONTENT_SECURITY_MANAGER_REASON_WM_INVALID_ACCESS_TOKEN = 11, + CONTENT_SECURITY_MANAGER_REASON_WM_MEMORY_ALLOCATION_ERROR = 12, + CONTENT_SECURITY_MANAGER_REASON_WM_SECAPI_USAGE_FAILURE = 13, + + // Perceptibility Errors + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INTERNAL_ERROR = 1000, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_NULL_POINTER = 1001, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INVALID_INPUT = 1002, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INVALID_ARGUMENT = 1003, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_MEMORY_ALLOCATION_ERROR = 1004, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_UNINITIALIZED_OBJECT = 1005, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_RE_INIT_ERROR = 1006, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_OVERLAY_IMAGE_ERROR = 1007, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INVALID_IMAGE_FORMAT = 1008, + CONTENT_SECURITY_MANAGER_REASON_WM_PERCEPTIBILITY_INVALID_IMAGE_OR_OBJECT_SIZE = 1009 +}ContentSecurityManagerResultWaterMarkCode; + + +/** + * @struct PlayerSecExtendedStatus + * @brief Structure to hold status info + */ +struct PlayerSecExtendedStatus +{ + int accessAttributeStatus; + int statusCode; +}; + +/** + * @fn isSecFeatureEnabled + * @brief check if sec feature is enabled + * @return bool + */ +bool isSecFeatureEnabled(); + +/** + * @fn isSecManagerEnabled + * @brief check if sec manager is enabled + * + * @return bool + */ +bool isSecManagerEnabled(); + +/** + * @fn DrmMetaDataEvent::getAsVerboseErrorCode + * @brief get as verbose error code + * + * @param httpCode - http code + * @param secManagerClass - sec manager class + * @param secManagerReasonCode - sec manager reason code + * + * @return bool + */ +bool getAsVerboseErrorCode(int32_t httpCode, int32_t &secManagerClass, int32_t &secManagerReasonCode ); + +/** + * @class PlayerSecInterface + * @brief PlayerSecInterface Class + */ +class PlayerSecInterface +{ +public : + /** + * @fn PlayerSecInterface + * + * @brief acquire license via sec client + * @param[in] serviceHostUrl - service host url + * @param[in] numberOfRequestMetadataKeys - number of request metadata keys + * @param[in] requestMetadata - request metadata + * @param[in] numberOfAccessAttributes - number of access attributes + * @param[in] accessAttributes - access attributes + * @param[in] contentMetadata - content metadata + * @param[in] contentMetadataLength - content metadata length + * @param[in] licenseRequest - license request + * @param[in] licenseRequestLength - license request length + * @param[in] keySystemId - key system id + * @param[in] mediaUsage - media usage + * @param[in] accessToken - access token + * @param[out] licenseResponse - license response + * @param[out] licenseResponseLength - license response length + * @param[out] refreshDurationSeconds - refresh duration seconds + * @param[out] statusInfo - status info + * + * @return int32_t + */ + + int32_t PlayerSec_AcquireLicense( const char *serviceHostUrl, uint8_t numberOfRequestMetadataKeys, + const char *requestMetadata[][2], uint8_t numberOfAccessAttributes, + const char *accessAttributes[][2], const char *contentMetadata, + size_t contentMetadataLength, const char *licenseRequest, + size_t licenseRequestLength, const char *keySystemId, + const char *mediaUsage, const char *accessToken, + char **licenseResponse, size_t *licenseResponseLength, + uint32_t *refreshDurationSeconds, + PlayerSecExtendedStatus *statusInfo ); + /** + * @fn PlayerSec_FreeResource + * + * @param[in] resource - resource + * @return int32_t + */ + int32_t PlayerSec_FreeResource( const char *resource ); + + /** + * @fn isSecRequestFailed + * @brief check if sec request failed + * + * @param requestResult - request result + * + * @return bool + */ + bool isSecRequestFailed( int32_t requestResult ); + + /** + * @fn isSecResultInRange + * + * @brief check if sec result is in range + * @param requestResult - request result + * + * @return bool + */ + bool isSecResultInRange( int32_t requestResult ); +}; + +#endif /* PLAYER_SEC_INTERFACE_H */ diff --git a/middleware/externals/contentsecuritymanager/SecManagerThunder.cpp b/middleware/externals/contentsecuritymanager/SecManagerThunder.cpp new file mode 100644 index 000000000..9d025f486 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/SecManagerThunder.cpp @@ -0,0 +1,962 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License.m + */ + +/** + * @file ContentSecurityManager.cpp + * @brief Class impl for ContentSecurityManager + */ + +#include "ContentSecurityManager.h" +#include "SecManagerThunder.h" +#include "PlayerLogManager.h" +#include +#include "_base64.h" +#include // For PRId64 +#include +#include +#include +#include "ThunderAccessPlayer.h" + +/** + * @brief SecManagerThunder Constructor + */ +SecManagerThunder::SecManagerThunder() : mSecManagerObj(SECMANAGER_CALL_SIGN), mSecMutex(), mSchedulerStarted(false), + mRegisteredEvents(), mWatermarkPluginObj(WATERMARK_PLUGIN_CALLSIGN), mWatMutex(), mSpeedStateMutex() +{ + std::lock_guard lock(mSecMutex); + mSecManagerObj.ActivatePlugin(); + { + std::lock_guard lock(mWatMutex); + mWatermarkPluginObj.ActivatePlugin(); + } + + /* hide watermarking at startup */ + ShowWatermark(false); + + /*Start Scheduler for handling RDKShell API invocation*/ + if(false == mSchedulerStarted) + { + StartScheduler(); // pass dummy required playerId parameter; note that we don't yet have a valid player instance to derive it from + mSchedulerStarted = true; + } + /* + * Release any unexpectedly open sessions. + * These could exist if a previous player session crashed + * 'firstInstance' check is a defensive measure allowing for the usage of + * GetInstance() & DestroyInstance() to change in the future + * InstanceMutex use in GetInstance() makes this thread safe + * (currently mSecMutex is also locked but ideally the scope of this would be reduced)*/ + static bool firstInstance = true; + if(firstInstance) + { + firstInstance=false; + JsonObject result; + JsonObject param; + param["clientId"] = "player"; + param["sessionId"] = 0; //Instructs closePlaybackSession to close all open 'player' sessions + MW_LOG_WARN("SecManager call closePlaybackSession to ensure no old sessions exist."); + bool rpcResult = mSecManagerObj.InvokeJSONRPC("closePlaybackSession", param, result); + + if (rpcResult && result["success"].Boolean()) + { + MW_LOG_WARN("old SecManager sessions removed"); + } + else + { + MW_LOG_WARN("no old SecManager sessions to remove"); + } + } + + RegisterAllEvents(); +} + +/** + * @brief SecManagerThunder Destructor + */ +SecManagerThunder::~SecManagerThunder() +{ + std::lock_guard lock(mSecMutex); + /* hide watermarking before secmanager shutdown */ + ShowWatermark(false); + + /*Stop Scheduler used for handling RDKShell API invocation*/ + if(true == mSchedulerStarted) + { + StopScheduler(); + mSchedulerStarted = false; + } + UnRegisterAllEvents(); +} + +/** + * @brief To acquire access token + */ +bool SecManagerThunder::getSessionToken(std::string &token) +{ + ThunderAccessPlayer authService(AUTH_SERVICE_CALL_SIGN); + JsonObject param; + JsonObject response; + + if (authService.InvokeJSONRPC("getSessionToken", param, response, 10000)) + { + token = response["token"].String(); + return true; + } + return false; +} + +/** + * @brief To acquire license from SecManager + */ +bool SecManagerThunder::AcquireLicenseOpenOrUpdate( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contMetaLen, + const char* licenseRequest, size_t licReqLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) +{ + // licenseUrl un-used now + (void) licenseUrl; + + bool ret = false; + bool rpcResult = false; + unsigned int retryCount = 0; + bool update = false; + //Initializing it with default error codes (which would be sent if there any jsonRPC + //call failures to thunder) + *statusCode = CONTENT_SECURITY_MANAGER_DRM_FAILURE; + *reasonCode = CONTENT_SECURITY_MANAGER_DRM_GEN_FAILURE; + + std::string accessTokenStr = accessToken + ? std::string(accessToken, accTokenLen) + : std::string(); + std::string contentMetaDataStr = contentMetadata + ? std::string(contentMetadata, contMetaLen) + : std::string(); + std::string licenseRequestStr = licenseRequest + ? std::string(licenseRequest, licReqLen) + : std::string(); + + const char* apiName = "openPlaybackSession"; + JsonObject param; + JsonObject response; + JsonObject sessionConfig; + JsonObject aspectDimensions; + + sessionConfig["distributedTraceType"] = "money"; + sessionConfig["distributedTraceId"] = moneyTraceMetadata[0][1]; + //Start the playback session as inactive if the video mute is on + sessionConfig["sessionState"] = isVideoMuted ? "inactive" : "active"; + // TODO: Remove hardcoded values + aspectDimensions["width"] = 1920; + aspectDimensions["height"] = 1080; + + param["clientId"] = "player"; + param["sessionConfiguration"] = sessionConfig; + param["contentAspectDimensions"] = aspectDimensions; + + param["keySystem"] = keySystemId; + param["mediaUsage"] = mediaUsage; + + // If sessionId is present, we are trying to acquire a new license within the same session + if (session.isSessionValid()) + { + apiName = "updatePlaybackSession"; + param["sessionId"] = session.getSessionID(); + } + +#ifdef DEBUG_SECMANAGER + std::string params; + param.ToString(params); + MW_LOG_WARN("SecManager %s param: %s",apiName, params.c_str()); +#endif + + { + std::lock_guard lock(mSecMutex); + if(!accessTokenStr.empty() && + !contentMetaDataStr.empty() && + !licenseRequestStr.empty()) + { + MW_LOG_INFO("Access token, Content metadata and license request are copied successfully, passing details with SecManager"); + + //Set json params to be used by sec manager + param["accessToken"] = accessTokenStr; + param["contentMetadata"] = contentMetaDataStr; + param["licenseRequest"] = licenseRequestStr; + +#ifdef DEBUG_SECMANAGER + { + std::string params; + param.ToString(params); + MW_LOG_WARN("SecManager %s param: %s",apiName, params.c_str()); + } +#endif + + //invoke "openPlaybackSession" or "updatePlaybackSession" with retries for specific error cases + do + { + rpcResult = mSecManagerObj.InvokeJSONRPC(apiName, param, response, 10000); + if (rpcResult) + { + ContentSecurityManagerSession newSession; + +#ifdef DEBUG_SECMANAGER + std::string output; + response.ToString(output); + MW_LOG_WARN("SecManager %s o/p: %s",apiName, output.c_str()); +#endif + if (response["success"].Boolean()) + { + /* + * Ensure all sessions have a Session ID created to manage lifetime + * multiple object creation is OK as an existing instance should be returned + * where input data changes e.g. following a call to updatePlaybackSession + * the input data to the shared session is updated here*/ + newSession = ContentSecurityManagerSession(response["sessionId"].Number(), + ContentSecurityManager::getInputSummaryHash(moneyTraceMetadata, contentMetadata, + contMetaLen, licenseRequest, keySystemId, + mediaUsage, accessToken, isVideoMuted)); + + std::string license = response["license"].String(); + MW_LOG_TRACE("SecManager obtained license with length: %d and data: %s",license.size(), license.c_str()); + if (!license.empty()) + { + // Here license is base64 encoded + unsigned char * licenseDecoded = nullptr; + size_t licenseDecodedLen = 0; + licenseDecoded = base64_Decode(license.c_str(), &licenseDecodedLen); + MW_LOG_TRACE("SecManager license decoded len: %d and data: %p", licenseDecodedLen, licenseDecoded); + + if (licenseDecoded != nullptr && licenseDecodedLen != 0) + { + MW_LOG_INFO("SecManager license post base64 decode length: %d", *licenseResponseLength); + *licenseResponse = (char*) malloc(licenseDecodedLen); + if (*licenseResponse) + { + memcpy(*licenseResponse, licenseDecoded, licenseDecodedLen); + *licenseResponseLength = licenseDecodedLen; + } + else + { + MW_LOG_ERR("SecManager failed to allocate memory for license!"); + } + free(licenseDecoded); + ret = true; + } + else + { + MW_LOG_ERR("SecManager license base64 decode failed!"); + } + } + } + + // Save session ID + if (newSession.isSessionValid() && !session.isSessionValid()) + { + session = newSession; + } + + } + // TODO: Sort these values out for backward compatibility + if(response.HasLabel("secManagerResultContext")) + { + JsonObject resultContext = response["secManagerResultContext"].Object(); + + if(resultContext.HasLabel("class")) + *statusCode = resultContext["class"].Number(); + if(resultContext.HasLabel("reason")) + *reasonCode = resultContext["reason"].Number(); + if(resultContext.HasLabel("businessStatus")) + *businessStatus = resultContext["businessStatus"].Number(); + } + + if(!ret) + { + //As per Secmanager retry is meaningful only for + //Digital Rights Management Failure Class (200) or + //Watermarking Failure Class (300) + //having the reasons - + //DRM license service network timeout / Request/network time out (3). + //DRM license network connection failure/Watermark vendor-access service connection failure (4) + //DRM license server busy/Watermark service busy (5) + if((*statusCode == CONTENT_SECURITY_MANAGER_DRM_FAILURE || *statusCode == CONTENT_SECURITY_MANAGER_WM_FAILURE) && + (*reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_TIMEOUT || + *reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_CON_FAILURE || + *reasonCode == CONTENT_SECURITY_MANAGER_SERVICE_BUSY ) && retryCount < MAX_LICENSE_REQUEST_ATTEMPTS) + { + ++retryCount; + MW_LOG_WARN("SecManager license request failed, response for %s : statusCode: %d, reasonCode: %d, so retrying with delay %d, retry count : %u", apiName, *statusCode, *reasonCode, sleepTime, retryCount ); + ms_sleep(sleepTime); + } + else + { + MW_LOG_ERR("SecManager license request failed, response for %s : statusCode: %d, reasonCode: %d", apiName, *statusCode, *reasonCode); + break; + } + } + else + { + MW_LOG_INFO("SecManager license request success, response for %s : statusCode: %d, reasonCode: %d, session status: %s", apiName, *statusCode, *reasonCode, isVideoMuted ? "inactive" : "active"); + break; + } + } + while(retryCount < MAX_LICENSE_REQUEST_ATTEMPTS); + + } + else + { + MW_LOG_ERR("SecManager Failed to copy access token to the shared memory, %s is aborted statusCode: %d, reasonCode: %d", apiName, *statusCode, *reasonCode); + } + } + return ret; +} + +/** + * @brief To update session state to SecManager + */ +bool SecManagerThunder::SetDrmSessionState(int64_t sessionId, bool active) +{ + bool success = false; + bool rpcResult = false; + JsonObject result; + JsonObject param; + param["clientId"] = "player"; + param["sessionId"] = sessionId; + MW_LOG_INFO("%s:%d SecManager call setPlaybackSessionState for ID: %" PRId64 " and state: %d", __FUNCTION__, __LINE__, sessionId, active); + if (active) + { + param["sessionState"] = "active"; + } + else + { + param["sessionState"] = "inactive"; + } + + { + std::lock_guard lock(mSecMutex); + rpcResult = mSecManagerObj.InvokeJSONRPC("setPlaybackSessionState", param, result); + } + + if (rpcResult) + { + if (result["success"].Boolean()) + { + success = true; + } + else + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("%s:%d SecManager setPlaybackSessionState failed for ID: %" PRId64 ", active:%d and result: %s", __FUNCTION__, __LINE__, sessionId, active, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("%s:%d SecManager setPlaybackSessionState failed for ID: %" PRId64 " and active: %d", __FUNCTION__, __LINE__, sessionId, active); + } + return success; +} + +/** + * @brief To notify SecManager to release a session + */ +void SecManagerThunder::CloseDrmSession(int64_t sessionId) +{ + bool rpcResult = false; + JsonObject result; + JsonObject param; + param["clientId"] = "player"; + param["sessionId"] = sessionId; + MW_LOG_INFO("%s:%d SecManager call closePlaybackSession for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + + { + std::lock_guard lock(mSecMutex); + rpcResult = mSecManagerObj.InvokeJSONRPC("closePlaybackSession", param, result); + } + + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("%s:%d SecManager closePlaybackSession failed for ID: %" PRId64 " and result: %s", __FUNCTION__, __LINE__, sessionId, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("%s:%d SecManager closePlaybackSession failed for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + } +} + +/** + * @brief To update session state to SecManager + */ +bool SecManagerThunder::setWindowSize(int64_t sessionId, int64_t video_width, int64_t video_height) +{ + bool rpcResult = false; + JsonObject result; + JsonObject param; + + param["sessionId"] = sessionId; + param["videoWidth"] = video_width; + param["videoHeight"] = video_height; + + MW_LOG_INFO("%s:%d SecManager call setVideoWindowSize for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + { + std::lock_guard lock(mSecMutex); + rpcResult = mSecManagerObj.InvokeJSONRPC("setVideoWindowSize", param, result); + } + + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("%s:%d SecManager setVideoWindowSize failed for ID: %" PRId64 " and result: %s", __FUNCTION__, __LINE__, sessionId, responseStr.c_str()); + rpcResult = false; + } + + } + else + { + MW_LOG_ERR("%s:%d SecManager setVideoWindowSize failed for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + } + return rpcResult; +} + +/** + * @brief To set Playback Speed State to SecManager + */ +bool SecManagerThunder::SetPlaybackPosition(int64_t sessionId, float playback_speed, int32_t playback_position) +{ + bool rpcResult = false; + JsonObject result; + JsonObject param; + //mSpeedStateMutex is used to avoid any speedstate event to go when a delayed event is in progress results change in order of event call (i.e, if user tries a trickplay within half a second of tune) + std::lock_guard lock(mSpeedStateMutex); + + param["sessionId"] = sessionId; + param["playbackSpeed"] = playback_speed; + param["playbackPosition"] = playback_position; + + MW_LOG_INFO("%s:%d SecManager call setPlaybackSpeedState for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + + { + std::lock_guard lock(mSecMutex); + rpcResult = mSecManagerObj.InvokeJSONRPC("setPlaybackSpeedState", param, result); + } + + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("%s:%d SecManager setPlaybackSpeedState failed for ID: %" PRId64 " and result: %s", __FUNCTION__, __LINE__, sessionId, responseStr.c_str()); + rpcResult = false; + } + } + else + { + MW_LOG_ERR("%s:%d SecManager setPlaybackSpeedState failed for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + } + return rpcResult; +} + + +/** + * @brief To Load ClutWatermark + */ +bool SecManagerThunder::loadClutWatermark(int64_t sessionId, int64_t graphicId, int64_t watermarkClutBufferKey, int64_t watermarkImageBufferKey, int64_t clutPaletteSize, + const char* clutPaletteFormat, int64_t watermarkWidth, int64_t watermarkHeight, float aspectRatio) +{ + bool rpcResult = false; + JsonObject result; + JsonObject param; + + param["sessionId"] = sessionId; + param["graphicId"] = graphicId; + param["watermarkClutBufferKey"] = watermarkClutBufferKey; + param["watermarkImageBufferKey"] = watermarkImageBufferKey; + param["clutPaletteSize"] = clutPaletteSize; + param["clutPaletteFormat"] = clutPaletteFormat; + param["watermarkWidth"] = watermarkWidth; + param["watermarkHeight"] = watermarkHeight; + param["aspectRatio"] = std::to_string(aspectRatio).c_str(); + + MW_LOG_INFO("%s:%d SecManager call loadClutWatermark for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + + { + std::lock_guard lock(mSecMutex); + rpcResult = mSecManagerObj.InvokeJSONRPC("loadClutWatermark", param, result); + } + + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("%s:%d SecManager loadClutWatermark failed for ID: %" PRId64 " and result: %s", __FUNCTION__, __LINE__, sessionId, responseStr.c_str()); + rpcResult = false; + } + } + else + { + MW_LOG_ERR("%s:%d SecManager loadClutWatermark failed for ID: %" PRId64 "", __FUNCTION__, __LINE__, sessionId); + } + return rpcResult; +} + +/** + * @brief Registers all Events to input plugin + */ +void SecManagerThunder::RegisterAllEvents () +{ + bool bSubscribed = false; + std::function watermarkSessionMethod = std::bind(&SecManagerThunder::watermarkSessionHandler, this, std::placeholders::_1); + bSubscribed = mSecManagerObj.SubscribeEvent(_T("onWatermarkSession"), watermarkSessionMethod); + if(bSubscribed) + mRegisteredEvents.push_back("onWatermarkSession"); + + std::function addWatermarkMethod = std::bind(&SecManagerThunder::addWatermarkHandler, this, std::placeholders::_1); + bSubscribed = mSecManagerObj.SubscribeEvent(_T("onAddWatermark"), addWatermarkMethod); + if(bSubscribed) + mRegisteredEvents.push_back("onAddWatermark"); + + std::function updateWatermarkMethod = std::bind(&SecManagerThunder::updateWatermarkHandler, this, std::placeholders::_1); + bSubscribed = mSecManagerObj.SubscribeEvent(_T("onUpdateWatermark"), updateWatermarkMethod); + if(bSubscribed) + mRegisteredEvents.push_back("onUpdateWatermark"); + + std::function removeWatermarkMethod = std::bind(&SecManagerThunder::removeWatermarkHandler, this, std::placeholders::_1); + bSubscribed = mSecManagerObj.SubscribeEvent(_T("onRemoveWatermark"), removeWatermarkMethod); + if(bSubscribed) + mRegisteredEvents.push_back("onRemoveWatermark"); + + std::function showWatermarkMethod = std::bind(&SecManagerThunder::showWatermarkHandler, this, std::placeholders::_1); + bSubscribed = mSecManagerObj.SubscribeEvent(_T("onDisplayWatermark"), showWatermarkMethod); + if(bSubscribed) + mRegisteredEvents.push_back("onDisplayWatermark"); +} + +/** + * @brief UnRegisters all Events from plugin + */ +void SecManagerThunder::UnRegisterAllEvents () +{ + for (auto const& evtName : mRegisteredEvents) + { + mSecManagerObj.UnSubscribeEvent(_T(evtName)); + } + mRegisteredEvents.clear(); +} + +/** + * @brief To get scheduler status + */ +bool SecManagerThunder::getSchedulerStatus () +{ + return mSchedulerStarted; +} + +/** + * @brief Detects watermarking session conditions + */ +void SecManagerThunder::watermarkSessionHandler(const JsonObject& parameters) +{ + std::string param; + parameters.ToString(param); + MW_LOG_WARN("ContentSecurityManager::%s:%d i/p params: %s", __FUNCTION__, __LINE__, param.c_str()); + std::function sendWatermarkEvent_CB = ContentSecurityManager::getWatermarkSessionEvent_CB(); + if (nullptr != sendWatermarkEvent_CB) + { + sendWatermarkEvent_CB( parameters["sessionId"].Number(),parameters["conditionContext"].Number(),parameters["watermarkingSystem"].String()); + } +} + +/** + * @brief Gets watermark image details and manages watermark rendering + */ +void SecManagerThunder::addWatermarkHandler(const JsonObject& parameters) +{ + std::string param; + parameters.ToString(param); + + MW_LOG_WARN("ContentSecurityManager::%s:%d i/p params: %s", __FUNCTION__, __LINE__, param.c_str()); + if(getSchedulerStatus()) + { + int graphicId = parameters["graphicId"].Number(); + int zIndex = parameters["zIndex"].Number(); + MW_LOG_WARN("ContentSecurityManager::%s:%d graphicId : %d index : %d ", __FUNCTION__, __LINE__, graphicId, zIndex); + ScheduleTask(PlayerAsyncTaskObj([graphicId, zIndex](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->CreateWatermark(graphicId, zIndex); + }, (void *) ContentSecurityManager::GetInstance())); + + int smKey = parameters["graphicImageBufferKey"].Number(); + int smSize = parameters["graphicImageSize"].Number();/*ToDo : graphicImageSize (long) long conversion*/ + MW_LOG_WARN("ContentSecurityManager::%s:%d graphicId : %d smKey: %d smSize: %d", __FUNCTION__, __LINE__, graphicId, smKey, smSize); + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([graphicId, smKey, smSize](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->UpdateWatermark(graphicId, smKey, smSize); + }, (void *)ContentSecurityManager::GetInstance())); + + if (parameters["adjustVisibilityRequired"].Boolean()) + { + int sessionId = parameters["sessionId"].Number(); + MW_LOG_WARN("ContentSecurityManager::%s:%d adjustVisibilityRequired is true, invoking GetWaterMarkPalette. graphicId : %d", __FUNCTION__, __LINE__, graphicId); + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([sessionId, graphicId](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->GetWaterMarkPalette(sessionId, graphicId); + }, (void *)ContentSecurityManager::GetInstance())); + } + else + { + MW_LOG_WARN("ContentSecurityManager::%s:%d adjustVisibilityRequired is false, graphicId : %d", __FUNCTION__, __LINE__, graphicId); + } + +#if 0 + /*Method to be called only if RDKShell is used for rendering*/ + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([show](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->AlwaysShowWatermarkOnTop(show); + }, (void *)ContentSecurityManager::GetInstance())); +#endif + } + return; +} + +/** + * @brief Gets updated watermark image details and manages watermark rendering + */ +void SecManagerThunder::updateWatermarkHandler(const JsonObject& parameters) +{ + if(getSchedulerStatus()) + { + int graphicId = parameters["graphicId"].Number(); + int clutKey = parameters["watermarkClutBufferKey"].Number(); + int imageKey = parameters["watermarkImageBufferKey"].Number(); + MW_LOG_TRACE("graphicId : %d ",graphicId); + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([graphicId, clutKey, imageKey](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->ModifyWatermarkPalette(graphicId, clutKey, imageKey); + }, (void *) ContentSecurityManager::GetInstance())); + } + return; +} + +/** + * @brief Removes watermark image + */ +void SecManagerThunder::removeWatermarkHandler(const JsonObject& parameters) +{ +#ifdef DEBUG_SECMANAGER + std::string param; + parameters.ToString(param); + MW_LOG_WARN("ContentSecurityManager::%s:%d i/p params: %s", __FUNCTION__, __LINE__, param.c_str()); +#endif + if(getSchedulerStatus()) + { + int graphicId = parameters["graphicId"].Number(); + MW_LOG_WARN("ContentSecurityManager::%s:%d graphicId : %d ", __FUNCTION__, __LINE__, graphicId); + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([graphicId](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->DeleteWatermark(graphicId); + }, (void *) ContentSecurityManager::GetInstance())); +#if 0 + /*Method to be called only if RDKShell is used for rendering*/ + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([show](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->AlwaysShowWatermarkOnTop(show); + }, (void *) ContentSecurityManager::GetInstance())); +#endif + } + return; +} + +/** + * @brief Handles watermark calls to be only once + */ +void SecManagerThunder::showWatermarkHandler(const JsonObject& parameters) +{ + bool show = true; + if(parameters["hideWatermark"].Boolean()) + { + show = false; + } + MW_LOG_INFO("Received onDisplayWatermark, show: %d ", show); + if(getSchedulerStatus()) + { + ContentSecurityManager::GetInstance()->ScheduleTask(PlayerAsyncTaskObj([show](void *data) + { + SecManagerThunder *instance = static_cast(data); + instance->ShowWatermark(show); + }, (void *) ContentSecurityManager::GetInstance())); + } + return; +} + +/** + * @brief Show watermark image + */ +void SecManagerThunder::ShowWatermark(bool show) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + + MW_LOG_ERR("ContentSecurityManager %s:%d ", __FUNCTION__, __LINE__); + param["show"] = show; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("showWatermark", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } + return; +} + +/** + * @brief Create Watermark + */ +void SecManagerThunder::CreateWatermark(int graphicId, int zIndex ) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + + MW_LOG_ERR("ContentSecurityManager %s:%d ", __FUNCTION__, __LINE__); + param["id"] = graphicId; + param["zorder"] = zIndex; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("createWatermark", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } + return; +} + +/** + * @brief Delete Watermark + */ +void SecManagerThunder::DeleteWatermark(int graphicId) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + + MW_LOG_ERR("ContentSecurityManager %s:%d ", __FUNCTION__, __LINE__); + param["id"] = graphicId; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("deleteWatermark", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } + return; +} + +/** + * @brief Update Watermark + */ +void SecManagerThunder::UpdateWatermark(int graphicId, int smKey, int smSize ) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + + MW_LOG_ERR("ContentSecurityManager %s:%d ", __FUNCTION__, __LINE__); + param["id"] = graphicId; + param["key"] = smKey; + param["size"] = smSize; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("updateWatermark", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } + return; +} + +/** + * @brief Show watermark image + * This method need to be used only when RDKShell is used for rendering. Not supported by Watermark Plugin + */ +void SecManagerThunder::AlwaysShowWatermarkOnTop(bool show) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + + MW_LOG_ERR("ContentSecurityManager %s:%d ", __FUNCTION__, __LINE__); + param["show"] = show; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("alwaysShowWatermarkOnTop", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } +} + +/** + * @brief GetWaterMarkPalette + */ +void SecManagerThunder::GetWaterMarkPalette(int sessionId, int graphicId) +{ + JsonObject param; + JsonObject result; + bool rpcResult = false; + param["id"] = graphicId; + MW_LOG_WARN("ContentSecurityManager %s:%d Graphic id: %d ", __FUNCTION__, __LINE__, graphicId); + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("getPalettedWatermark", param, result); + } + + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager::%s failed and result: %s", __FUNCTION__, responseStr.c_str()); + } + else //if success, request sec manager to load the clut into the clut shm + { + + MW_LOG_WARN("ContentSecurityManager::%s getWatermarkPalette invoke success for graphicId %d, calling loadClutWatermark", __FUNCTION__, graphicId); + int clutKey = result["clutKey"].Number(); + int clutSize = result["clutSize"].Number(); + int imageKey = result["imageKey"].Number(); + int imageWidth = result["imageWidth"].Number(); + int imageHeight = result["imageHeight"].Number(); + float aspectRatio = (imageHeight != 0) ? (float)imageWidth/(float)imageHeight : 0.0; + loadClutWatermark(sessionId, graphicId, clutKey, imageKey, + clutSize, "RGBA8888", imageWidth, imageHeight, + aspectRatio); + } + + } + else + { + MW_LOG_ERR("ContentSecurityManager::%s thunder invocation failed!", __FUNCTION__); + } +} + +/** + * @brief ModifyWatermarkPalette + */ +void SecManagerThunder::ModifyWatermarkPalette(int graphicId, int clutKey, int imageKey) +{ + JsonObject param; + JsonObject result; + + bool rpcResult = false; + param["id"] = graphicId; + param["clutKey"] = clutKey; + param["imageKey"] = imageKey; + { + std::lock_guard lock(mWatMutex); + rpcResult = mWatermarkPluginObj.InvokeJSONRPC("modifyPalettedWatermark", param, result); + } + if (rpcResult) + { + if (!result["success"].Boolean()) + { + std::string responseStr; + result.ToString(responseStr); + MW_LOG_ERR("ContentSecurityManager modifyPalettedWatermark failed with result: %s, graphic id: %d", responseStr.c_str(), graphicId); + } + else + { + MW_LOG_TRACE("ContentSecurityManager modifyPalettedWatermark invoke success, graphic id: %d", graphicId); + } + } + else + { + MW_LOG_ERR("ContentSecurityManager Thunder invocation for modifyPalettedWatermark failed!, graphic id: %d", graphicId); + } +} + diff --git a/middleware/externals/contentsecuritymanager/SecManagerThunder.h b/middleware/externals/contentsecuritymanager/SecManagerThunder.h new file mode 100644 index 000000000..475a09885 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/SecManagerThunder.h @@ -0,0 +1,208 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file SecManagerThunder.h + * @brief Class to communicate with SecManager Thunder plugin + */ + +#ifndef __SECMANAGER_THUNDER_H__ +#define __SECMANAGER_THUNDER_H__ + +#include +#include +#include "ContentSecurityManagerSession.h" +#include "PlayerScheduler.h" +#include "PlayerMemoryUtils.h" +#include "ThunderAccessPlayer.h" +#include +#include +#include +#include +#include + +#define SECMANAGER_CALL_SIGN "org.rdk.SecManager.1" +#define WATERMARK_PLUGIN_CALLSIGN "org.rdk.Watermark.1" +//#define RDKSHELL_CALLSIGN "org.rdk.RDKShell.1" //need to be used instead of WATERMARK_PLUGIN_CALLSIGN if RDK Shell is used for rendering watermark +#define AUTH_SERVICE_CALL_SIGN "org.rdk.AuthService.1" + +/** + * @class SecManagerThunder + * @brief Class to get License from Sec Manager Thunder + */ +class SecManagerThunder : public ContentSecurityManager +{ +public: + /** + * @brief Sets DRM session state (e.g., active/inactive) + * @param sessionId DRM session ID + * @param active Whether the session should be marked active + * @return true on success, false otherwise + */ + bool SetDrmSessionState(int64_t sessionId, bool active) override; + /** + * @brief Closes an existing DRM session + * @param sessionId DRM session ID + * @return true if closed successfully + */ + void CloseDrmSession(int64_t sessionId) override; + /* Run AcquireLicenseOpenOrUpdate is the old AcquireLicense code + * It is used by AcquireLicense() to for opening sessions & for calling update when this is required*/ + bool AcquireLicenseOpenOrUpdate( std::string clientId, std::string appId, const char* licenseUrl, const char* moneyTraceMetadata[][2], + const char* accessAttributes[][2], const char* contentMetadata, size_t contentMetadataLen, + const char* licenseRequest, size_t licenseRequestLen, const char* keySystemId, + const char* mediaUsage, const char* accessToken, size_t accessTokenLen, + ContentSecurityManagerSession &session, + char** licenseResponse, size_t* licenseResponseLength, + int32_t* statusCode, int32_t* reasonCode, int32_t* businessStatus, bool isVideoMuted, int sleepTime) override; + /** + * @brief Sends update license challenge to existing session + * @param sessionId DRM session ID + * @param licenseRequest Challenge string + * @param initData Initialization data + * @param[out] response Response from Firebolt + * @return true on success + */ + /** + * @brief Sets playback state for watermark alignment + * @param sessionId Session ID + * @param speed Playback rate (1.0 = normal) + * @param position Current position in seconds + * @return true if command succeeded + */ + bool SetPlaybackPosition(int64_t sessionId, float speed, int32_t position) override; + /** + * @brief Enables or disables visual watermark + * @param show Show/hide flag + * @param sessionId Session context (optional) + */ + void ShowWatermark(bool show); + /** + * @brief Acquire access token + * @param token Return token + * @return true if command succeeded + */ + bool getSessionToken(std::string &token); + /** + * @fn setWindowSize + * + * @param[in] sessionId - session id + * @param[in] video_width - video width + * @param[in] video_height - video height + */ + bool setWindowSize(int64_t sessionId, int64_t video_width, int64_t video_height); + + /** + * @fn getSchedulerStatus + * + * @return bool - true if scheduler is running, false otherwise + */ + bool getSchedulerStatus(); + /** + * @fn SecManagerThunder + */ + SecManagerThunder(); + /** + * @fn ~SecManagerThunder + */ + ~SecManagerThunder(); + /** + * @brief Copy constructor disabled + */ + SecManagerThunder(const SecManagerThunder&) = delete; + /** + * @brief assignment operator disabled + */ + SecManagerThunder* operator=(const SecManagerThunder&) = delete; +protected: + /*Event Handlers*/ + /** + * @fn watermarkSessionHandler + * @param parameters - i/p JsonObject params + */ + void watermarkSessionHandler(const JsonObject& parameters); + /** + * @fn addWatermarkHandler + * @param parameters - i/p JsonObject params + */ + void addWatermarkHandler(const JsonObject& parameters); + /** + * @fn updateWatermarkHandler + * @param parameters - i/p JsonObject params + */ + void updateWatermarkHandler(const JsonObject& parameters); + /** + * @fn removeWatermarkHandler + * @param parameters - i/p JsonObject params + */ + void removeWatermarkHandler(const JsonObject& parameters); + /** + * @fn showWatermarkHandler + * @param parameters - i/p JsonObject params + */ + void showWatermarkHandler(const JsonObject& parameters); + /** + * @fn loadClutWatermark + * @param[in] sessionId - session id + * + */ + bool loadClutWatermark(int64_t sessionId, int64_t graphicId, int64_t watermarkClutBufferKey, int64_t watermarkImageBufferKey, int64_t clutPaletteSize, const char* clutPaletteFormat, int64_t watermarkWidth, int64_t watermarkHeight, float aspectRatio); + /** + * @fn CreateWatermark + */ + void CreateWatermark(int graphicId, int zIndex); + /** + * @fn UpdateWatermark + */ + void UpdateWatermark(int graphicId, int smKey, int smSize); + /** + * @fn GetWaterMarkPalette + */ + void GetWaterMarkPalette(int sessionId, int graphicId); + /** + * @fn ModifyWatermarkPalette + */ + void ModifyWatermarkPalette(int graphicId, int clutKey, int imageKey); + /** + * @fn DeleteWatermark + */ + void DeleteWatermark(int graphicId); + /** + * @fn AlwaysShowWatermarkOnTop + */ + void AlwaysShowWatermarkOnTop(bool show); + /** + * @fn RegisterAllEvents + */ + void RegisterAllEvents (); + /** + * @fn UnRegisterAllEvents + */ + void UnRegisterAllEvents (); + + ThunderAccessPlayer mSecManagerObj; /**< ThunderAccessPlayer object for communicating with SecManager*/ + ThunderAccessPlayer mWatermarkPluginObj; /**< ThunderAccessPlayer object for communicating with Watermark Plugin Obj*/ + std::mutex mSecMutex; /**< Lock for accessing mSecManagerObj*/ + std::mutex mWatMutex; /**< Lock for accessing mWatermarkPluginObj*/ + std::mutex mSpeedStateMutex; /**< mutex for setPlaybackSpeedState()*/ + std::list mRegisteredEvents; + bool mSchedulerStarted; +}; + +#endif /* __SECMANAGER_THUNDER_H__ */ diff --git a/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.cpp b/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.cpp new file mode 100755 index 000000000..6ab170f82 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.cpp @@ -0,0 +1,234 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ThunderAccess.cpp + * @brief wrapper class for accessing thunder plugins + */ +#include "PlayerLogManager.h" +#include "ThunderAccessPlayer.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#ifdef USE_CPP_THUNDER_PLUGIN_ACCESS +#ifndef DISABLE_SECURITY_TOKEN +#include +#endif +#pragma GCC diagnostic pop + +using namespace std; +using namespace WPEFramework; +#endif +#define SERVER_DETAILS "127.0.0.1:9998" + +#define MAX_LENGTH 1024 + +//Delete non-array object +#define SAFE_DELETE(ptr) { delete(ptr); ptr = NULL; } + +/** + * @brief Structure to save the Thunder security token details + **/ +typedef struct ThunderSecurityPlayer +{ + std::string securityToken; + int tokenStatus; + bool tokenQueried; + ThunderSecurityPlayer(): securityToken(), tokenStatus(0), tokenQueried(false) { }; +}ThunderSecurityPlayerData; + +ThunderSecurityPlayerData gSecurityPlayerData; + + +/** + * @brief ThunderAccessPlayer constructor + */ +ThunderAccessPlayer::ThunderAccessPlayer(std::string callsign) + : remoteObject(NULL) + ,controllerObject(NULL) + ,pluginCallsign(callsign) +{ + MW_LOG_INFO( "[ThunderAccessPlayer]Inside"); + uint32_t status = Core::ERROR_NONE; + + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(SERVER_DETAILS))); + string sToken = ""; +#ifdef DISABLE_SECURITY_TOKEN + gSecurityPlayerData.securityToken = "token=" + sToken; + gSecurityPlayerData.tokenQueried = true; +#else + if(!gSecurityPlayerData.tokenQueried) + { + unsigned char buffer[MAX_LENGTH] = {0}; + gSecurityPlayerData.tokenStatus = GetSecurityToken(MAX_LENGTH,buffer); + if(gSecurityPlayerData.tokenStatus > 0){ + MW_LOG_INFO( "[ThunderAccessPlayer] : GetSecurityToken success"); + sToken = (char*)buffer; + gSecurityPlayerData.securityToken = "token=" + sToken; + } + gSecurityPlayerData.tokenQueried = true; + + //MW_LOG_WARN( "[ThunderAccessPlayer] securityToken : %s tokenStatus : %d tokenQueried : %s", gSecurityPlayerData.securityToken.c_str(), gSecurityPlayerData.tokenStatus, ((gSecurityPlayerData.tokenQueried)?"true":"false")); + } +#endif + if (NULL == controllerObject) { + /*Passing empty string instead of Controller callsign.This is assumed as controller plugin.*/ + if(gSecurityPlayerData.tokenStatus > 0){ + controllerObject = new JSONRPC::LinkType(_T(""), _T(""), false, gSecurityPlayerData.securityToken); + } + else{ + controllerObject = new JSONRPC::LinkType(_T("")); + } + + if (NULL == controllerObject) { + MW_LOG_WARN( "[ThunderAccessPlayer] Controller object creation failed"); + } else { + MW_LOG_INFO( "[ThunderAccessPlayer] Controller object creation success"); + } + } + + if(gSecurityPlayerData.tokenStatus > 0){ + remoteObject = new JSONRPC::LinkType(_T(pluginCallsign), _T(""), false, gSecurityPlayerData.securityToken); + } + else{ + remoteObject = new JSONRPC::LinkType(_T(pluginCallsign), _T("")); + } + if (NULL == remoteObject) { + MW_LOG_WARN( "[ThunderAccessPlayer] %s Client initialization failed", pluginCallsign.c_str()); + } else { + MW_LOG_INFO( "[ThunderAccessPlayer] %s Client initialization success", pluginCallsign.c_str()); + } +} + +/** + * @brief ThunderAccessPlayer destructor + */ +ThunderAccessPlayer::~ThunderAccessPlayer() +{ + SAFE_DELETE(controllerObject); + SAFE_DELETE(remoteObject); +} + +/** + * @brief ActivatePlugin + */ +bool ThunderAccessPlayer::ActivatePlugin() +{ + bool ret = true; + JsonObject result; + JsonObject controlParam; + std::string response; + uint32_t status = Core::ERROR_NONE; + + if (NULL != controllerObject) { + controlParam["callsign"] = pluginCallsign; + status = controllerObject->Invoke(THUNDER_RPC_TIMEOUT, _T("activate"), controlParam, result); + if (Core::ERROR_NONE == status){ + result.ToString(response); + MW_LOG_INFO( "[ThunderAccessPlayer] %s plugin Activated. Response : %s ", pluginCallsign.c_str(), response.c_str()); + } + else + { + MW_LOG_WARN( "[ThunderAccessPlayer] %s plugin Activation failed with error status : %u ", pluginCallsign.c_str(), status); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccessPlayer] Controller Object NULL "); + ret = false; + } + return ret; +} + +/*To Do: Only JSON Object can be used as parameter now*/ +/** + * @brief subscribeEvent + */ +bool ThunderAccessPlayer::SubscribeEvent (string eventName, std::function functionHandler) +{ + bool ret = true; + uint32_t status = Core::ERROR_NONE; + if (NULL != remoteObject) { + status = remoteObject->Subscribe(THUNDER_RPC_TIMEOUT, _T(eventName), functionHandler); + if (Core::ERROR_NONE == status) { + MW_LOG_INFO( "[ThunderAccessPlayer] Subscribed to : %s", eventName.c_str()); + } else { + MW_LOG_WARN( "[ThunderAccessPlayer] Subscription failed for : %s with error status %u", eventName.c_str(), status); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccessPlayer] remoteObject not created for the plugin!"); + ret = false; + } + return ret; +} + +/*To Do: Only JSON Object can be used as parameter now*/ + +/** + * @brief unSubscribeEvent + */ +bool ThunderAccessPlayer::UnSubscribeEvent (std::string eventName) +{ + bool ret = true; + if (NULL != remoteObject) { + remoteObject->Unsubscribe(THUNDER_RPC_TIMEOUT, _T(eventName)); + MW_LOG_INFO( "[ThunderAccessPlayer] UnSubscribed : %s event", eventName.c_str()); + } else { + MW_LOG_WARN( "[ThunderAccessPlayer] remoteObject not created for the plugin!"); + ret = false; + } + return ret; +} + +/** + * @brief invokeJSONRPC + * @note Invoke JSONRPC call for the plugin + */ +bool ThunderAccessPlayer::InvokeJSONRPC(std::string method, const JsonObject ¶m, JsonObject &result, const uint32_t waitTime) +{ + bool ret = true; + std::string response; + uint32_t status = Core::ERROR_NONE; + + if(NULL == remoteObject) + { + MW_LOG_WARN( "[ThunderAccessPlayer] client not initialized! "); + return false; + } + + JsonObject result_internal; + status = remoteObject->Invoke(waitTime, _T(method), param, result_internal); + if (Core::ERROR_NONE == status) + { + if (result_internal["success"].Boolean()) { + result_internal.ToString(response); + MW_LOG_TRACE( "[ThunderAccessPlayer] %s success! Response : %s", method.c_str() , response.c_str()); + } else { + result_internal.ToString(response); + MW_LOG_WARN( "[ThunderAccessPlayer] %s call failed! Response : %s", method.c_str() , response.c_str()); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccessPlayer] %s : invoke failed with error status %u", method.c_str(), status); + ret = false; + } + + result = result_internal; + return ret; +} diff --git a/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.h b/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.h new file mode 100755 index 000000000..7ffa0b7c9 --- /dev/null +++ b/middleware/externals/contentsecuritymanager/ThunderAccessPlayer.h @@ -0,0 +1,127 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file ThunderAccessPlayer.h + * @brief shim for dispatching UVE HDMI input playback + */ + +#ifndef THUNDERACCESSPLAYER_H_ +#define THUNDERACCESSPLAYER_H_ + +#include +#include +#include +#include +#include +#include + +#include "Module.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#include +#include +#pragma GCC diagnostic pop + +using namespace std; +using namespace WPEFramework; + +#define THUNDER_RPC_TIMEOUT 5000 + +#define MW_ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) + +//Delete non-array object +#define SAFE_DELETE(ptr) { delete(ptr); ptr = NULL; } +//Delete Array object +#define SAFE_DELETE_ARRAY(ptr) { delete [] ptr; ptr = NULL; } + +class PlayerLogManager; + +/** + * @class ThunderAccessPlayer + * @brief Support Thunder Plugin Access from AAMP + */ +class ThunderAccessPlayer +{ +public: + /** + * @fn ThunderAccessPlayer + * @note Security token acquisition, controller object creation + */ + ThunderAccessPlayer(std::string callsign); + + /** + * @fn ~ThunderAccessPlayer + * @note clean up + */ + ~ThunderAccessPlayer(); + + /** + * @brief ThunderAccessPlayer copy constructor disabled + */ + ThunderAccessPlayer(const ThunderAccessPlayer&) = delete; + + /** + * @brief ThunderAccessPlayer assignment disabled + */ + ThunderAccessPlayer& operator=(const ThunderAccessPlayer&) = delete; + + /** + * @brief ActivatePlugin + * @note Plugin activation and Remote object creation + * @param Plugin Callsign + * @retval true on success + * @retval false on failure + */ + bool ActivatePlugin(); + /** + * @fn InvokeJSONRPC + * @note Invoke JSONRPC call for the plugin + * @param method,param,result, method,waitTime reference to input param, result and waitTime (default = THUNDER_RPC_TIMEOUT) + * @retval true on success + * @retval false on failure + */ + bool InvokeJSONRPC(std::string method, const JsonObject ¶m, JsonObject &result, const uint32_t waitTime = THUNDER_RPC_TIMEOUT); + + /** + * @fn SubscribeEvent + * @note Subscribe event data for the specific plugin + * @param eventName,functionHandler Event name, Event handler + * @retval true on success + * @retval false on failure + */ + bool SubscribeEvent (std::string eventName, std::function functionHandler); + /** + * @fn UnSubscribeEvent + * @note unSubscribe event data for the specific plugin + * @param eventName Event name + * @retval true on success + * @retval false on failure + */ + bool UnSubscribeEvent (std::string eventName); + +private: + /**< The Remote object connected to specific Plugin*/ + JSONRPC::LinkType *remoteObject; + /**< The Remote object connected to controller Plugin*/ + JSONRPC::LinkType *controllerObject; + std::string pluginCallsign; +}; +#endif // THUNDERACCESSPLAYER_H_ diff --git a/middleware/externals/rdk/DeviceInterfaceBase.h b/middleware/externals/rdk/DeviceInterfaceBase.h new file mode 100644 index 000000000..88453a7d3 --- /dev/null +++ b/middleware/externals/rdk/DeviceInterfaceBase.h @@ -0,0 +1,57 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DeviceInterfaceBase.h + * @brief base class for device api interface + */ + + /* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ +/* +Remove the entire file externals/DeviceInterfaceBase.h when deprecating IARM +*/ + + +#ifndef DEVICE_INTERFACE_BASE_H +#define DEVICE_INTERFACE_BASE_H + +#include +#include + +class DeviceInterfaceBase { + + public: + + DeviceInterfaceBase() + {} + + virtual void RegisterDsMgrEventHandler() = 0; + + virtual void RegisterNtwMgrEventHandler() = 0; + + virtual void RemoveEventHandlers() = 0; + + virtual char *GetTR181Config(const char * paramName, size_t & iConfigLen) = 0; + +}; + +#endif \ No newline at end of file diff --git a/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.cpp b/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.cpp new file mode 100644 index 000000000..2f08631bd --- /dev/null +++ b/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.cpp @@ -0,0 +1,276 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DeviceFireboltInterface.cpp + * @brief Firebolt device api interface + */ + +/* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ + +/* +Move the entire folder externals/rdk/IFirebolt 's contents to externals/rdk/ +IFirebolt folder to be deleted, as IARM is no longer available as an alternative +*/ + +#include "DeviceFireboltInterface.h" +#include "fireboltaamp.h" +#include "PlayerLogManager.h" +#include "PlayerExternalsRdkInterface.h" +#include "PlayerExternalUtils.h" + +#include +#include +#include +#include +#include + +std::shared_ptr s_pDeviceFireboltInterface = nullptr; + +std::mutex mFireboltConnectionMutex; +std::condition_variable mFireboltConnectionCV; + +static void HDCPEventHandlerFirebolt(const Firebolt::Device::HDCPVersionMap& t_HDCPVersionMap); +static void ResolutionHandlerFirebolt(const std::string& t_res); +static void getActiveInterfaceEventHandlerFirebolt (const Firebolt::Device::NetworkInfoResult& t_NetworkInfoResult); + +std::shared_ptr DeviceFireboltInterface::GetInstance() +{ + if(nullptr == s_pDeviceFireboltInterface) + { + s_pDeviceFireboltInterface = std::shared_ptr(new DeviceFireboltInterface()); + } + + return s_pDeviceFireboltInterface; +} + +DeviceFireboltInterface::DeviceFireboltInterface() +{ + m_pFireboltInterface = FireboltInterface::GetInstance(); +} + +DeviceFireboltInterface::~DeviceFireboltInterface() +{ + MW_PRE_LOGGER_LOG("DeviceFireboltInterface destructor called \n"); + RemoveEventHandlers(); + m_pFireboltInterface = nullptr; +} + +void DeviceFireboltInterface::Initialize() +{ + MW_PRE_LOGGER_LOG("Initialize \n"); + if(s_pDeviceFireboltInterface) + { + MW_PRE_LOGGER_LOG("Registering events \n"); + s_pDeviceFireboltInterface->RegisterDsMgrEventHandler(); + s_pDeviceFireboltInterface->RegisterNtwMgrEventHandler(); + } + else + { + MW_PRE_LOGGER_LOG("Init called before instance \n"); + } + + MW_PRE_LOGGER_LOG("Initialize completed \n"); + +} + + +void DeviceFireboltInterface::RegisterDsMgrEventHandler() +{ + + MW_PRE_LOGGER_LOG("Subscribing to Firebolt hdcp change event \n"); + + auto result = Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().subscribeOnHdcpChanged( + [](const auto& hdcpProtocol) { + MW_LOG_ERR("[Event] HDCP changed"); + HDCPEventHandlerFirebolt(hdcpProtocol); + }); + + if(result) + { + MW_PRE_LOGGER_LOG("HDCP changed event registered \n"); + mDsMgrSubscriptionId.push_back(result.value()); + } + + else + { + MW_PRE_LOGGER_LOG("Failed to subscribe to hdcp change events: %d \n", static_cast(result.error())); + } + + MW_PRE_LOGGER_LOG("Subscribing to Firebolt resolution change event \n"); + + result = Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().subscribeOnVideoResolutionChanged( + [](const std::string& videoResolution) + { + MW_LOG_WARN("[Event] Video resolution changed: %s" , videoResolution.c_str()); + ResolutionHandlerFirebolt(videoResolution); + }); + if(result) + { + MW_PRE_LOGGER_LOG("Resolution changed event registered\n"); + mDsMgrSubscriptionId.push_back(result.value()); + } + else + { + MW_PRE_LOGGER_LOG("Failed to get video resolution %d\n", static_cast(result.error()) ); + } + +} + +void DeviceFireboltInterface::RemoveEventHandlers() +{ + //removes everything ... + Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().unsubscribeAll(); +} + +void DeviceFireboltInterface::RegisterNtwMgrEventHandler() +{ + MW_PRE_LOGGER_LOG("Subscribing to Firebolt Network change event\n"); + + auto result = Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().subscribeOnNetworkChanged( + [](const auto& network) { + MW_LOG_ERR("[Event] network changed"); + getActiveInterfaceEventHandlerFirebolt(network); + }); + + if(result) + { + MW_PRE_LOGGER_LOG("Network changed event registered\n"); + mNtwMgrSubscriptionId.push_back(result.value()); + } + else + { + MW_PRE_LOGGER_LOG("Failed to subscribe to network change events: %d\n", static_cast(result.error())); + MW_LOG_ERR("Failed to subscribe to network change events: %d", static_cast(result.error())); + } + + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + auto network = Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().network(); + + if(network) + { + if(network.value().type == Firebolt::Device::NetworkType::WIFI) + { + MW_PRE_LOGGER_LOG("Active interface wifi\n"); + pInstance->SetActiveInterface(true); + } + else + { + MW_PRE_LOGGER_LOG("Active interface eth\n"); + pInstance->SetActiveInterface(false); + } + } + +} + +char * DeviceFireboltInterface::GetTR181Config(const char * paramName, size_t & iConfigLen) +{ + MW_LOG_ERR("TR181 not supported for firebolt"); + return nullptr; +} + +static void getActiveInterfaceEventHandlerFirebolt (const Firebolt::Device::NetworkInfoResult& t_NetworkInfoResult) +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + if(t_NetworkInfoResult.state == Firebolt::Device::NetworkState::CONNECTED) + { + std::string interface = "unknown"; + if(t_NetworkInfoResult.type == Firebolt::Device::NetworkType::WIFI) + { + interface = "wlan"; + pInstance->SetActiveInterface(true); + MW_LOG_INFO("Network interface changed to wifi"); + } + else if(t_NetworkInfoResult.type == Firebolt::Device::NetworkType::ETHERNET) + { + interface = "eth"; + pInstance->SetActiveInterface(false); + MW_LOG_INFO("Network interface changed to ethernet"); + } + else + { + MW_LOG_ERR("Unsupported Interface %d", (int)t_NetworkInfoResult.type); + } + MW_LOG_INFO("getActiveInterfaceEventHandler activeinterface changed to %s\n", interface.c_str()); + } + else + { + MW_LOG_ERR("Disconnected interface type:%d state:%d\n", (int)t_NetworkInfoResult.type, (int)t_NetworkInfoResult.state); + } + + +} + +/** + * @brief IARM event handler for HDCP and HDMI hot plug events + */ +static void HDCPEventHandlerFirebolt(const Firebolt::Device::HDCPVersionMap& t_HDCPVersionMap) +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + if(t_HDCPVersionMap.hdcp2_2) + { + pInstance->setHdcpProtocol(dsHDCP_VERSION_2X); + MW_LOG_INFO("HDCP protocol updated 2_2"); + } + else if(t_HDCPVersionMap.hdcp1_4) + { + pInstance->setHdcpProtocol(dsHDCP_VERSION_1X); + MW_LOG_INFO("HDCP protocol updated 1_4"); + } + else + { + MW_LOG_ERR("Unknown HDCP protocol"); + } + + pInstance->SetHDMIStatus(); + +} + +/** + * @brief IARM event handler for resolution changes + */ +static void ResolutionHandlerFirebolt(const std::string& t_res) +{ + int width = 1280; + int height = 720; + + MW_LOG_INFO("Resolution: %s", t_res.c_str()); + + auto curr_network = Firebolt::IFireboltAampAccessor::Instance().DeviceInterface().videoResolution(); + + if(curr_network) + { + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + width = curr_network.value()[0]; + height = curr_network.value()[1]; + pInstance->SetResolution(width, height); + MW_LOG_INFO("Updating resolution [%d][%d]", curr_network.value()[0], curr_network.value()[1]); + } + else + { + MW_LOG_ERR("Failed to get current resolution"); + } + +} diff --git a/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.h b/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.h new file mode 100644 index 000000000..0a8a0ff7f --- /dev/null +++ b/middleware/externals/rdk/IFirebolt/DeviceFireboltInterface.h @@ -0,0 +1,87 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DeviceFireboltInterface.h + * @brief Firebolt device api interface + */ + + +/* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ + +/* +Move the entire folder externals/rdk/IFirebolt 's contents to externals/rdk/ +IFirebolt folder to be deleted, as IARM is no longer available as an alternative +*/ + + +#ifndef DEVICE_FIREBOLT_INTERFACE_H +#define DEVICE_FIREBOLT_INTERFACE_H + +#include "DeviceInterfaceBase.h" +#include "FireboltInterface.h" + +#include +#include + +class DeviceFireboltInterface : public DeviceInterfaceBase { + + + public: + + DeviceFireboltInterface(const DeviceFireboltInterface&) = delete; + + DeviceFireboltInterface& operator=(const DeviceFireboltInterface&) = delete; + + ~DeviceFireboltInterface(); + + char *GetTR181Config(const char * paramName, size_t & iConfigLen) override; + + static std::shared_ptr GetInstance(); + + static void Initialize(); + + private: + + std::shared_ptr m_pFireboltInterface; + + std::vector mDsMgrSubscriptionId; + + std::vector mNtwMgrSubscriptionId; + + DeviceFireboltInterface(); + + bool CreateFireboltInstance(const std::string &url); + + void ConnectionChanged(const bool connected, int error); + + void RegisterDsMgrEventHandler() override; + + void RegisterNtwMgrEventHandler() override; + + void RemoveEventHandlers() override; + + void DestroyFireboltInstance(); + +}; + +#endif \ No newline at end of file diff --git a/middleware/externals/rdk/IIarm/DeviceIARMInterface.cpp b/middleware/externals/rdk/IIarm/DeviceIARMInterface.cpp new file mode 100644 index 000000000..a3b1fe4a8 --- /dev/null +++ b/middleware/externals/rdk/IIarm/DeviceIARMInterface.cpp @@ -0,0 +1,319 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DeviceIARMInterface.cpp + * @brief IARM interface + */ + +/* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ +/* +Remove the entire folder externals/rdk/IARM +*/ + + +#include "DeviceIARMInterface.h" + +#include +#include +#include +#include +#include +#include "libIBusDaemon.h" +#include +#include "tr181api.h" +#include "_base64.h" + +#include "PlayerLogManager.h" + +#include "PlayerExternalsRdkInterface.h" + +#include "PlayerExternalUtils.h" + +/** + * @brief Enumeration for net_srv_mgr active interface event callback + */ +typedef enum _NetworkManager_EventId_t { + IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_ENABLED=50, + IARM_BUS_NETWORK_MANAGER_EVENT_INTERFACE_IPADDRESS=55, + IARM_BUS_NETWORK_MANAGER_MAX +} IARM_Bus_NetworkManager_EventId_t; + +/** + * @struct _IARM_BUS_NetSrvMgr_Iface_EventData_t + * @brief IARM Bus struct contains active streaming interface, original definition present in homenetworkingservice.h + */ +typedef struct _IARM_BUS_NetSrvMgr_Iface_EventData_t { + union{ + char activeIface[10]; + char allNetworkInterfaces[50]; + char enableInterface[10]; + }; + char interfaceCount; + bool isInterfaceEnabled; +} IARM_BUS_NetSrvMgr_Iface_EventData_t; + +std::shared_ptr s_pDeviceIARMInterface = nullptr; + +static void HDMIEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); +static void ResolutionHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); +static void getActiveInterfaceEventHandler (const char *owner, IARM_EventId_t eventId, void *data, size_t len); + + +std::shared_ptr DeviceIARMInterface::GetInstance() +{ + if(nullptr == s_pDeviceIARMInterface) + { + s_pDeviceIARMInterface = std::shared_ptr(new DeviceIARMInterface()); + } + + return s_pDeviceIARMInterface; +} + +DeviceIARMInterface::DeviceIARMInterface() +{ + + DeviceIARMInterface::IARMInit(); + + +} + +DeviceIARMInterface::~DeviceIARMInterface() +{ + MW_PRE_LOGGER_LOG("DeviceIARMInterface destructor called \n"); + + RemoveEventHandlers(); + + s_pDeviceIARMInterface = nullptr; +} + +void DeviceIARMInterface::Initialize() +{ + if(s_pDeviceIARMInterface) + { + s_pDeviceIARMInterface->RegisterDsMgrEventHandler(); + s_pDeviceIARMInterface->RegisterNtwMgrEventHandler(); + } + +} + +void DeviceIARMInterface::IARMInit() +{ + //char processName[20] = {0}; + IARM_Result_t result; + MW_PRE_LOGGER_LOG("IARM Interface Init started in Player\n"); + + //snprintf(processName, sizeof(processName), "PLAYER-%u", getpid()); + if (IARM_RESULT_SUCCESS == (result = IARM_Bus_Init("PLAYER"))) { + MW_PRE_LOGGER_LOG("IARM Interface Inited in Player\n"); + } + else { + MW_PRE_LOGGER_LOG("IARM Interface Inited Externally : %d\n", result); + } + + if (IARM_RESULT_SUCCESS == (result = IARM_Bus_Connect())) { + MW_PRE_LOGGER_LOG("IARM Interface Connected in Player\n"); + } + else { + MW_PRE_LOGGER_LOG("IARM Interface Connected Externally :%d\n", result); + } + + MW_PRE_LOGGER_LOG("IARM Interface Init completed in Player\n"); + +} + +void DeviceIARMInterface::RegisterDsMgrEventHandler() +{ + IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, HDMIEventHandler); + IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, HDMIEventHandler); + IARM_Bus_RegisterEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_RES_POSTCHANGE, ResolutionHandler); +} + +void DeviceIARMInterface::RemoveEventHandlers() +{ + IARM_Bus_RemoveEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG, HDMIEventHandler); + IARM_Bus_RemoveEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_HDCP_STATUS, HDMIEventHandler); + IARM_Bus_RemoveEventHandler(IARM_BUS_DSMGR_NAME,IARM_BUS_DSMGR_EVENT_RES_POSTCHANGE, ResolutionHandler); + IARM_Bus_RemoveEventHandler("NET_SRV_MGR", IARM_BUS_NETWORK_MANAGER_EVENT_INTERFACE_IPADDRESS, getActiveInterfaceEventHandler); +} + +void DeviceIARMInterface::RegisterNtwMgrEventHandler() +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + bool wifiStatus = false; + IARM_Result_t ret = IARM_RESULT_SUCCESS; + IARM_BUS_NetSrvMgr_Iface_EventData_t param; + + ret = IARM_Bus_Call("NET_SRV_MGR", "getActiveInterface", (void*)¶m, sizeof(param)); + if (ret != IARM_RESULT_SUCCESS) + { + MW_LOG_ERR("NET_SRV_MGR getActiveInterface read failed : %d\n", ret); + } + else + { + MW_LOG_WARN("NET_SRV_MGR getActiveInterface = %s\n", param.activeIface); + if (!strcmp(param.activeIface, "WIFI")){ + wifiStatus = true; + } + } + IARM_Bus_RegisterEventHandler("NET_SRV_MGR", IARM_BUS_NETWORK_MANAGER_EVENT_INTERFACE_IPADDRESS, getActiveInterfaceEventHandler); + pInstance->SetActiveInterface(wifiStatus); +} + +char * DeviceIARMInterface::GetTR181Config(const char * paramName, size_t & iConfigLen) +{ + char * strConfig = NULL; + IARM_Result_t result; + HOSTIF_MsgData_t param; + memset(¶m,0,sizeof(param)); + snprintf(param.paramName,TR69HOSTIFMGR_MAX_PARAM_LEN,"%s",paramName); + param.reqType = HOSTIF_GET; + + result = IARM_Bus_Call(IARM_BUS_TR69HOSTIFMGR_NAME,IARM_BUS_TR69HOSTIFMGR_API_GetParams, + (void *)¶m, sizeof(param)); + if(result == IARM_RESULT_SUCCESS) + { + if(fcNoFault == param.faultCode) + { + if(param.paramtype == hostIf_StringType && param.paramLen > 0 ) + { + std::string strforLog(param.paramValue,param.paramLen); + + iConfigLen = param.paramLen; + const char *src = (const char*)(param.paramValue); + strConfig = (char * ) base64_Decode(src,&iConfigLen); + + MW_LOG_INFO("GetTR181PlayerConfig: Got:%s En-Len:%d Dec-len:%d\n",strforLog.c_str(),param.paramLen,iConfigLen); + } + else + { + MW_LOG_ERR("GetTR181PlayerConfig: Not a string param type=%d or Invalid len:%d \n",param.paramtype, param.paramLen); + } + } + } + else + { + MW_LOG_ERR("GetTR181PlayerConfig: Failed to retrieve value result=%d\n",result); + } + return strConfig; +} + +static void getActiveInterfaceEventHandler (const char *owner, IARM_EventId_t eventId, void *data, size_t len) +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + static char previousInterface[20] = {'\0'}; + + + if (strcmp (owner, "NET_SRV_MGR") != 0) + return; + + IARM_BUS_NetSrvMgr_Iface_EventData_t *param = (IARM_BUS_NetSrvMgr_Iface_EventData_t *) data; + + if (NULL == strstr (param->activeIface, previousInterface) || (strlen(previousInterface) == 0)) + { + memset(previousInterface, 0, sizeof(previousInterface)); + strncpy(previousInterface, param->activeIface, sizeof(previousInterface) - 1); + MW_LOG_WARN("getActiveInterfaceEventHandler EventId %d activeinterface %s\n", eventId, param->activeIface); + } + + if (NULL != strstr (param->activeIface, "wlan")) + { + pInstance->SetActiveInterface(true); + } + else if (NULL != strstr (param->activeIface, "eth")) + { + pInstance->SetActiveInterface(false); + } + + +} + +/** + * @brief IARM event handler for HDCP and HDMI hot plug events + */ +static void HDMIEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + switch (eventId) + { + case IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG : + { + IARM_Bus_DSMgr_EventData_t *eventData = (IARM_Bus_DSMgr_EventData_t *)data; + int hdmi_hotplug_event = eventData->data.hdmi_hpd.event; + + const char *hdmihotplug = (hdmi_hotplug_event == dsDISPLAY_EVENT_CONNECTED) ? "connected" : "disconnected"; + MW_LOG_WARN(" Received IARM_BUS_DSMGR_EVENT_HDMI_HOTPLUG event data:%d status: %s\n", + hdmi_hotplug_event, hdmihotplug); + + pInstance->SetHDMIStatus(); + + break; + } + case IARM_BUS_DSMGR_EVENT_HDCP_STATUS : + { + IARM_Bus_DSMgr_EventData_t *eventData = (IARM_Bus_DSMgr_EventData_t *)data; + int hdcpStatus = eventData->data.hdmi_hdcp.hdcpStatus; + const char *hdcpStatusStr = (hdcpStatus == dsHDCP_STATUS_AUTHENTICATED) ? "authenticated" : "authentication failure"; + MW_LOG_WARN(" Received IARM_BUS_DSMGR_EVENT_HDCP_STATUS event data:%d status:%s\n", + hdcpStatus, hdcpStatusStr); + + pInstance->SetHDMIStatus(); + break; + } + default: + MW_LOG_WARN(" Received unknown IARM bus event:%d\n", eventId); + break; + } +} + +/** + * @brief IARM event handler for resolution changes + */ +static void ResolutionHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) +{ + std::shared_ptr pInstance = PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance(); + + switch (eventId) { + case IARM_BUS_DSMGR_EVENT_RES_PRECHANGE: + MW_LOG_WARN(" Received IARM_BUS_DSMGR_EVENT_RES_PRECHANGE \n"); + break; + case IARM_BUS_DSMGR_EVENT_RES_POSTCHANGE: + { + int width = 1280; + int height = 720; + + IARM_Bus_DSMgr_EventData_t *eventData = (IARM_Bus_DSMgr_EventData_t *)data; + width = eventData->data.resn.width ; + height = eventData->data.resn.height ; + + MW_LOG_WARN(" Received IARM_BUS_DSMGR_EVENT_RES_POSTCHANGE event width : %d height : %d\n", width, height); + pInstance->SetResolution(width, height); + break; + } + default: + MW_LOG_WARN(" Received unknown resolution event %d\n", eventId); + break; + } +} diff --git a/middleware/externals/rdk/IIarm/DeviceIARMInterface.h b/middleware/externals/rdk/IIarm/DeviceIARMInterface.h new file mode 100644 index 000000000..b8cda1235 --- /dev/null +++ b/middleware/externals/rdk/IIarm/DeviceIARMInterface.h @@ -0,0 +1,72 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file DeviceIARMInterface.h + * @brief IARM interface + */ + +/* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ +/* +Remove the entire folder externals/rdk/IARM +*/ + + +#ifndef DEVICE_IARM_INTERFACE_H +#define DEVICE_IARM_INTERFACE_H + +#include "DeviceInterfaceBase.h" + +#include + +class DeviceIARMInterface : public DeviceInterfaceBase { + + + public: + + DeviceIARMInterface(const DeviceIARMInterface&) = delete; + + DeviceIARMInterface& operator=(const DeviceIARMInterface&) = delete; + + char *GetTR181Config(const char * paramName, size_t & iConfigLen) override; + + static std::shared_ptr GetInstance(); + + static void Initialize(); + + ~DeviceIARMInterface(); + + private: + + void RegisterDsMgrEventHandler() override; + + void RegisterNtwMgrEventHandler() override; + + void RemoveEventHandlers() override; + + DeviceIARMInterface(); + + static void IARMInit(); + +}; + +#endif \ No newline at end of file diff --git a/middleware/externals/rdk/PlayerExternalsRdkInterface.cpp b/middleware/externals/rdk/PlayerExternalsRdkInterface.cpp new file mode 100644 index 000000000..339995c04 --- /dev/null +++ b/middleware/externals/rdk/PlayerExternalsRdkInterface.cpp @@ -0,0 +1,295 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerExternalsRdkInterface.cpp + * @brief player interface with IARM specific to RDK + */ +#include "PlayerExternalsRdkInterface.h" +#include "PlayerExternalUtils.h" +#include "DeviceInterfaceBase.h" +#include "DeviceIARMInterface.h" +#include "DeviceFireboltInterface.h" +#include "PlayerExternalsInterface.h" + +#include + +#define DISPLAY_WIDTH_UNKNOWN -1 /**< Parsing failed for getResolution().getName(); */ +#define DISPLAY_HEIGHT_UNKNOWN -1 /**< Parsing failed for getResolution().getName(); */ +#define DISPLAY_RESOLUTION_NA 0 /**< Resolution not available yet or not connected to HDMI */ + +std::shared_ptr s_pPlayerIarmRdkOP = nullptr; + +static bool isInterfaceWifi = false; + +/** + * @brief Singleton for object creation + */ +std::shared_ptr PlayerExternalsRdkInterface::GetPlayerExternalsRdkInterfaceInstance() +{ + if(s_pPlayerIarmRdkOP == nullptr) { + s_pPlayerIarmRdkOP = std::shared_ptr(new PlayerExternalsRdkInterface()); + } + + return s_pPlayerIarmRdkOP; +} + +PlayerExternalsRdkInterface::PlayerExternalsRdkInterface() +{ + +} + +void PlayerExternalsRdkInterface::Initialize() +{ + + MW_PRE_LOGGER_LOG("Initializing started \n"); + + /* + IARM Deprecation Note: + IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. + */ + /* + Remove the section between the comment section remove-start and remove-end when deprecating IARM + */ + + //remove-start + //initialize only if needed + if(m_initialized != InitState::NOT_INITIALIZED) + { + if(m_initialized == InitState::FIREBOLT && (m_use_firebolt_sdk || IsContainerEnvironment())) + { + MW_PRE_LOGGER_LOG("Firebolt already Inited \n"); + //firebolt already inited + return; + } + else if(m_initialized == InitState::IARM && (!m_use_firebolt_sdk) && (!IsContainerEnvironment())) + { + MW_PRE_LOGGER_LOG("IARM already Inited \n"); + //IARM already inited + return; + } + else + { + MW_PRE_LOGGER_LOG("m_use_firebolt_sdk or IsContainerEnvironment() has changed, init again \n"); + //m_use_firebolt_sdk has changed init again + } + } + else + { + MW_PRE_LOGGER_LOG("Initializing \n"); + } + //remove-end + + if(m_pDeviceInterfaceBase) + { + m_pDeviceInterfaceBase = nullptr; + } + + MW_PRE_LOGGER_LOG("m_use_firebolt_sdk : %d, IsContainerEnvironment() : %d \n", m_use_firebolt_sdk, IsContainerEnvironment()); + + //remove-start + if(m_use_firebolt_sdk || IsContainerEnvironment()) //if explicitly config'd to or if in container go for firebolt + { + //remove-end + MW_PRE_LOGGER_LOG("Using Firebolt \n"); + m_pDeviceInterfaceBase = DeviceFireboltInterface::GetInstance(); + DeviceFireboltInterface::Initialize(); + //remove-start + m_initialized = PlayerExternalsRdkInterface::InitState::FIREBOLT; + } + else + { + MW_PRE_LOGGER_LOG("Using IARM \n"); + m_pDeviceInterfaceBase = DeviceIARMInterface::GetInstance(); + DeviceIARMInterface::Initialize(); + m_initialized = PlayerExternalsRdkInterface::InitState::IARM; + } + //remove-end + + MW_PRE_LOGGER_LOG("Done getting interface \n"); + + SetHDMIStatus(); + + MW_PRE_LOGGER_LOG("Initializing completed \n"); +} + +PlayerExternalsRdkInterface::~PlayerExternalsRdkInterface() +{ + m_pDeviceInterfaceBase = nullptr; + s_pPlayerIarmRdkOP = nullptr; +} + +void PlayerExternalsRdkInterface::GetDisplayResolution(int &width, int &height) +{ + width = m_displayWidth; + height = m_displayHeight; +} + +void PlayerExternalsRdkInterface::SetResolution(int width, int height) +{ + MW_LOG_WARN(" Resolution : width %d height:%d\n",width,height); + m_displayWidth = width; + m_displayHeight = height; +} + +/** + * @brief Set the HDCP status using data from DeviceSettings + */ +void PlayerExternalsRdkInterface::SetHDMIStatus() +{ + bool isConnected = false; + bool isHDCPCompliant = false; + bool isHDCPEnabled = true; + dsHdcpProtocolVersion_t hdcpProtocol = dsHDCP_VERSION_MAX; + dsHdcpProtocolVersion_t hdcpReceiverProtocol = dsHDCP_VERSION_MAX; + dsHdcpProtocolVersion_t hdcpCurrentProtocol = dsHDCP_VERSION_MAX; + + + + + try { + //Get the HDMI port + device::Manager::Initialize(); + std::string strVideoPort = device::Host::getInstance().getDefaultVideoPortName(); + ::device::VideoOutputPort &vPort = ::device::Host::getInstance().getVideoOutputPort(strVideoPort); + isConnected = vPort.isDisplayConnected(); + hdcpProtocol = (dsHdcpProtocolVersion_t)vPort.getHDCPProtocol(); + if(isConnected) { + isHDCPCompliant = (vPort.getHDCPStatus() == dsHDCP_STATUS_AUTHENTICATED); + isHDCPEnabled = vPort.isContentProtected(); + hdcpReceiverProtocol = (dsHdcpProtocolVersion_t)vPort.getHDCPReceiverProtocol(); + hdcpCurrentProtocol = (dsHdcpProtocolVersion_t)vPort.getHDCPCurrentProtocol(); + //get the resolution of the TV + int width,height; + int iResID = vPort.getResolution().getPixelResolution().getId(); + if( device::PixelResolution::k720x480 == iResID ) + { + width = 720; + height = 480; + } + else if( device::PixelResolution::k720x576 == iResID ) + { + width = 720; + height = 576; + } + else if( device::PixelResolution::k1280x720 == iResID ) + { + width = 1280; + height = 720; + } + else if( device::PixelResolution::k1920x1080 == iResID ) + { + width = 1920; + height = 1080; + } + else if( device::PixelResolution::k3840x2160 == iResID ) + { + width = 3840; + height = 2160; + } + else if( device::PixelResolution::k4096x2160 == iResID ) + { + width = 4096; + height = 2160; + } + else + { + width = DISPLAY_WIDTH_UNKNOWN; + height = DISPLAY_HEIGHT_UNKNOWN; + std::string _res = vPort.getResolution().getName(); + MW_LOG_ERR(" ERR parse failed for getResolution().getName():%s id:%d\n",(_res.empty() ? "NULL" : _res.c_str()),iResID); + } + + SetResolution(width, height); + } + else { + isHDCPCompliant = false; + isHDCPEnabled = false; + SetResolution(DISPLAY_RESOLUTION_NA,DISPLAY_RESOLUTION_NA); + } + + device::Manager::DeInitialize(); + } + catch (...) { + MW_LOG_WARN("DeviceSettings exception caught\n"); + } + + m_isHDCPEnabled = isHDCPEnabled; + + if(m_isHDCPEnabled) { + if(hdcpCurrentProtocol == dsHDCP_VERSION_2X) { + m_hdcpCurrentProtocol = hdcpCurrentProtocol; + } + else { + m_hdcpCurrentProtocol = dsHDCP_VERSION_1X; + } + MW_LOG_WARN(" detected HDCP version %s\n", m_hdcpCurrentProtocol == dsHDCP_VERSION_2X ? "2.x" : "1.4"); + } + else { + MW_LOG_WARN("DeviceSettings HDCP is not enabled\n"); + } + + if(!isConnected) { + m_hdcpCurrentProtocol = dsHDCP_VERSION_1X; + MW_LOG_WARN(" GetHDCPVersion: Did not detect HDCP version defaulting to 1.4 (%d)\n", m_hdcpCurrentProtocol); + } + + + return; +} + +void PlayerExternalsRdkInterface::setHdcpProtocol(dsHdcpProtocolVersion_t t_protocol) +{ + m_hdcpCurrentProtocol = t_protocol; + MW_LOG_WARN(" detected HDCP version %s\n", m_hdcpCurrentProtocol == dsHDCP_VERSION_2X ? "2.x" : "1.4"); +} + +std::shared_ptr PlayerExternalsRdkInterface::GetDeviceInterface() +{ + return m_pDeviceInterfaceBase; +} + +bool PlayerExternalsRdkInterface::GetActiveInterface() +{ + return isInterfaceWifi; +} + +void PlayerExternalsRdkInterface::SetActiveInterface(bool isWifi) +{ + isInterfaceWifi = isWifi; +} + +char * PlayerExternalsRdkInterface::GetTR181Config(const char * paramName, size_t & iConfigLen) +{ + return m_pDeviceInterfaceBase->GetTR181Config(paramName, iConfigLen); +} + +void PlayerExternalsRdkInterface::SetUseFireBoltSDK(bool t_use_firebolt_sdk) +{ + MW_PRE_LOGGER_LOG("old : %d, new : %d \n", m_use_firebolt_sdk, t_use_firebolt_sdk); + if(m_use_firebolt_sdk != t_use_firebolt_sdk) + { + m_use_firebolt_sdk = t_use_firebolt_sdk; + //reinitialize + m_initialized = InitState::NOT_INITIALIZED; + Initialize(); + + } + +} diff --git a/middleware/externals/rdk/PlayerExternalsRdkInterface.h b/middleware/externals/rdk/PlayerExternalsRdkInterface.h new file mode 100644 index 000000000..98c87eb6b --- /dev/null +++ b/middleware/externals/rdk/PlayerExternalsRdkInterface.h @@ -0,0 +1,154 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerExternalsRdkInterface.h + * @brief player interface with IARM specific to RDK + */ + +#ifndef PLAYER_IARM_RDK_INTERFACE_H +#define PLAYER_IARM_RDK_INTERFACE_H +#include "manager.hpp" +#include "host.hpp" +#include "videoResolution.hpp" +#include "videoOutputPort.hpp" +#include "videoOutputPortType.hpp" +#include "dsMgr.h" +#include "dsDisplay.h" +#include "audioOutputPort.hpp" +#include "dsAudio.h" + +#include + +#include "PlayerExternalsInterfaceBase.h" + + /* +IARM Deprecation Note: +IARM is to be deprecated in favor of DeviceSettings and Firebolt Device API. +*/ + +/* +Deprecate HDCP support in PlayerExternalsRdkInterface when deprecating IARM +*/ + +/* +Remove the section between the comment section remove-start and remove-end when deprecating IARM +*/ + +/* +Replace the section between the comment section replace-start, replace-with and replace-end when deprecating IARM +*/ + +class DeviceInterfaceBase; + +//class representing IARM interface in rdk +class PlayerExternalsRdkInterface : public PlayerExternalsInterfaceBase +{ + enum InitState{ + NOT_INITIALIZED, + FIREBOLT, + IARM + }; + + dsHdcpProtocolVersion_t m_hdcpCurrentProtocol = dsHDCP_VERSION_1X; + + //replace-start + std::shared_ptr m_pDeviceInterfaceBase = nullptr; + //replace-with + //std::shared_ptr m_pDeviceInterfaceBase = nullptr; + //replace-end + + //remove-start + bool m_use_firebolt_sdk = false; + + InitState m_initialized = NOT_INITIALIZED; + //remove-end + + PlayerExternalsRdkInterface(); + + public: + + void Initialize() override; + + /** + * @fn GetDisplayResolution + * @brief Get current resolution's display width and height + * @param[out] width width of current resolution + * @param[out] height height of current resolution + */ + void GetDisplayResolution(int &width, int &height) override; + + /** + * @fn SetHDMIStatus + * @brief Checks Display Settings and sets HDMI parameters like video output resolution, HDCP protocol + */ + void SetHDMIStatus() override; + + /** + * @fn SetResolution + * @brief Sets current resolution's width and height + * @param[in] width width of current resolution + * @param[in] height height of current resolution + */ + void SetResolution(int width, int height); + + // Singleton for object creation + + /** + * @fn GetPlayerExternalsRdkInterfaceInstance + * @retval PlayerExternalsRdkInterface object + */ + static std::shared_ptr GetPlayerExternalsRdkInterfaceInstance(); + + /** + * @fn GetTR181Config + * @brief Gets appropriate TR181 Config + * @param[in] paramName String of name of the parameter to be retrieved + * @param[out] iConfigLen Length of config retrieved + * @return Parameter config retrieved + */ + char * GetTR181Config(const char * paramName, size_t & iConfigLen) override; + + /** + * @fn isHDCPConnection2_2 + * @brief Is current HDCP protocol 2.2 + * @return True if current HDCP protocol is 2.2. False, if not. + */ + bool isHDCPConnection2_2() override { return m_hdcpCurrentProtocol == dsHDCP_VERSION_2X; } + + /** + * @fn GetActiveInterface + * @brief Is current active interface wifi? + * @return True if wifi. False, if not. + */ + bool GetActiveInterface(); + + void SetActiveInterface(bool isWifi); + + std::shared_ptr GetDeviceInterface(); + + void setHdcpProtocol(dsHdcpProtocolVersion_t t_protocol); + + void SetUseFireBoltSDK(bool t_use_firebolt_sdk) override; + + ~PlayerExternalsRdkInterface(); +}; + + +#endif diff --git a/middleware/externals/rdk/PlayerThunderAccess.cpp b/middleware/externals/rdk/PlayerThunderAccess.cpp new file mode 100644 index 000000000..286ac37cb --- /dev/null +++ b/middleware/externals/rdk/PlayerThunderAccess.cpp @@ -0,0 +1,1159 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file ThunderAccess.cpp + * @brief wrapper class for accessing thunder plugins + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#ifndef DISABLE_SECURITY_TOKEN +#include +#endif +#pragma GCC diagnostic pop +#include "PlayerThunderAccess.h" + +#include "PlayerLogManager.h" + + +using namespace std; +using namespace WPEFramework; + +#define SERVER_DETAILS "127.0.0.1:9998" +#define MAX_LENGTH 1024 + +#define APP_ID "MainPlayer" + +//PLUGIN CALL_SIGNS + +#define AVINPUT_CALLSIGN "org.rdk.AVInput.1" +#define MEDIAPLAYER_CALLSIGN "org.rdk.MediaPlayer.1" +#define MEDIASETTINGS_CALLSIGN "org.rdk.MediaSettings.1" +#define RDKSHELL_CALLSIGN "org.rdk.RDKShell.1" +#define RMF_PLUGIN_CALLSIGN "org.rdk.MediaEngineRMF.1" +#define DS_CALLSIGN "org.rdk.DisplaySettings.1" +#define SECMANAGER_CALL_SIGN "org.rdk.SecManager.1" +#define WATERMARK_PLUGIN_CALLSIGN "org.rdk.Watermark.1" +#define HDMIINPUT_CALLSIGN "org.rdk.HdmiInput.1" +#define COMPOSITEINPUT_CALLSIGN "org.rdk.CompositeInput.1" + +/** + * @brief Structure to save the Thunder security token details + **/ +typedef struct PlayerThunderSecurity +{ + std::string securityToken; + int tokenStatus; + bool tokenQueried; + PlayerThunderSecurity(): securityToken(), tokenStatus(0), tokenQueried(false) { }; +}PlayerThunderSecurityData; + +PlayerThunderSecurityData gPlayerSecurityData; + + +/** + * @brief PlayerThunderAccess constructor + */ +PlayerThunderAccess::PlayerThunderAccess(PlayerThunderAccessPlugin callsign) + : remoteObject(NULL), + controllerObject(NULL), + videoInputPort(-1), + PlayerThunderAccessBase(callsign) +{ + // LOG_INFO( "[ThunderAccess]Inside"); + + switch(callsign) + { + case PlayerThunderAccessPlugin::AVINPUT : + pluginCallsign = AVINPUT_CALLSIGN; + break; + case PlayerThunderAccessPlugin::MEDIAPLAYER : + pluginCallsign = MEDIAPLAYER_CALLSIGN; + break; + case PlayerThunderAccessPlugin::MEDIASETTINGS : + pluginCallsign = MEDIASETTINGS_CALLSIGN; + break; + case PlayerThunderAccessPlugin::RDKSHELL : + pluginCallsign = RDKSHELL_CALLSIGN; + break; + case PlayerThunderAccessPlugin::RMF : + pluginCallsign = RMF_PLUGIN_CALLSIGN; + break; + case PlayerThunderAccessPlugin::DS : + pluginCallsign = DS_CALLSIGN; + break; + case PlayerThunderAccessPlugin::SECMANAGER : + pluginCallsign = SECMANAGER_CALL_SIGN; + break; + case PlayerThunderAccessPlugin::WATERMARK : + pluginCallsign = WATERMARK_PLUGIN_CALLSIGN; + break; + case PlayerThunderAccessPlugin::HDMIINPUT : + pluginCallsign = HDMIINPUT_CALLSIGN; + break; + case PlayerThunderAccessPlugin::COMPOSITEINPUT : + pluginCallsign = COMPOSITEINPUT_CALLSIGN; + break; + default: + MW_LOG_ERR("Undefined plugin tried to initialize: %d", (int)callsign); + pluginCallsign = ""; + break; + } + + uint32_t status = Core::ERROR_NONE; + + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(SERVER_DETAILS))); + string sToken = ""; +#ifdef DISABLE_SECURITY_TOKEN + gPlayerSecurityData.securityToken = "token=" + sToken; + gPlayerSecurityData.tokenQueried = true; +#else + if(!gPlayerSecurityData.tokenQueried) + { + unsigned char buffer[MAX_LENGTH] = {0}; + gPlayerSecurityData.tokenStatus = GetSecurityToken(MAX_LENGTH,buffer); + if(gPlayerSecurityData.tokenStatus > 0){ + // LOG_INFO( "[ThunderAccess] : GetSecurityToken success"); + sToken = (char*)buffer; + gPlayerSecurityData.securityToken = "token=" + sToken; + } + gPlayerSecurityData.tokenQueried = true; + } +#endif + + if (NULL == controllerObject) { + /*Passing empty string instead of Controller callsign.This is assumed as controller plugin.*/ + if(gPlayerSecurityData.tokenStatus > 0){ + controllerObject = new JSONRPC::LinkType(_T(""), _T(""), false, gPlayerSecurityData.securityToken); + } + else{ + controllerObject = new JSONRPC::LinkType(_T("")); + } + + if (NULL == controllerObject) { + MW_LOG_WARN( "[ThunderAccess] Controller object creation failed"); + } else { + MW_LOG_INFO( "[ThunderAccess] Controller object creation success"); + } + } + + if(gPlayerSecurityData.tokenStatus > 0){ + remoteObject = new JSONRPC::LinkType(_T(pluginCallsign), _T(""), false, gPlayerSecurityData.securityToken); + } + else{ + remoteObject = new JSONRPC::LinkType(_T(pluginCallsign), _T("")); + } + if (NULL == remoteObject) { + MW_LOG_WARN( "[ThunderAccess] %s Client initialization failed", pluginCallsign.c_str()); + } else { + MW_LOG_INFO( "[ThunderAccess] %s Client initialization success", pluginCallsign.c_str()); + } +} + +/** + * @brief PlayerThunderAccess destructor + */ +PlayerThunderAccess::~PlayerThunderAccess() +{ + delete controllerObject; + controllerObject = NULL; + delete remoteObject; + remoteObject = NULL; +} + +/** + * @brief ActivatePlugin + */ +bool PlayerThunderAccess::ActivatePlugin() +{ + bool ret = true; + JsonObject result; + JsonObject controlParam; + std::string response; + uint32_t status = Core::ERROR_NONE; + + if (NULL != controllerObject) { + controlParam["callsign"] = pluginCallsign; + status = controllerObject->Invoke(THUNDER_RPC_TIMEOUT, _T("activate"), controlParam, result); + if (Core::ERROR_NONE == status){ + result.ToString(response); + MW_LOG_INFO( "[ThunderAccess] %s plugin Activated. Response : %s ", pluginCallsign.c_str(), response.c_str()); + } + else + { + MW_LOG_WARN( "[ThunderAccess] %s plugin Activation failed with error status : %u ", pluginCallsign.c_str(), status); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccess] Controller Object NULL "); + ret = false; + } + + return ret; +} + + +/*To Do: Only JSON Object can be used as parameter now*/ +/** + * @brief subscribeEvent + */ +bool PlayerThunderAccess::SubscribeEvent (string eventName, std::function functionHandler) +{ + bool ret = true; + uint32_t status = Core::ERROR_NONE; + if (NULL != remoteObject) { + status = remoteObject->Subscribe(THUNDER_RPC_TIMEOUT, _T(eventName), functionHandler); + if (Core::ERROR_NONE == status) { + MW_LOG_INFO( "[ThunderAccess] Subscribed to : %s", eventName.c_str()); + } else { + MW_LOG_WARN( "[ThunderAccess] Subscription failed for : %s with error status %u", eventName.c_str(), status); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccess] remoteObject not created for the plugin!"); + ret = false; + } + return ret; +} + + +/*To Do: Only JSON Object can be used as parameter now*/ + +/** + * @brief unSubscribeEvent + */ +bool PlayerThunderAccess::UnSubscribeEvent (string eventName) +{ + bool ret = true; + if (NULL != remoteObject) { + remoteObject->Unsubscribe(THUNDER_RPC_TIMEOUT, _T(eventName)); + MW_LOG_INFO( "[ThunderAccess] UnSubscribed : %s event", eventName.c_str()); + } else { + MW_LOG_WARN( "[ThunderAccess] remoteObject not created for the plugin!"); + ret = false; + } + return ret; +} + + +/** + * @brief invokeJSONRPC + * @note Invoke JSONRPC call for the plugin + */ +bool PlayerThunderAccess::InvokeJSONRPC(std::string method, const JsonObject ¶m, JsonObject &result, const uint32_t waitTime) +{ + bool ret = true; + std::string response; + uint32_t status = Core::ERROR_NONE; + + if(NULL == remoteObject) + { + MW_LOG_WARN( "[ThunderAccess] client not initialized! "); + return false; + } + + JsonObject result_internal; + status = remoteObject->Invoke(waitTime, _T(method), param, result_internal); + if (Core::ERROR_NONE == status) + { + if (result_internal["success"].Boolean()) { + result_internal.ToString(response); + MW_LOG_TRACE( "[ThunderAccess] %s success! Response : %s", method.c_str() , response.c_str()); + } else { + result_internal.ToString(response); + MW_LOG_WARN( "[ThunderAccess] %s call failed! Response : %s", method.c_str() , response.c_str()); + ret = false; + } + } else { + MW_LOG_WARN( "[ThunderAccess] %s : invoke failed with error status %u", method.c_str(), status); + ret = false; + } + + result = result_internal; + return ret; +} + +bool PlayerThunderAccess::SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) +{ + bool bRet = false; + switch(shim) + { + case PlayerThunderAccessShim::VIDEOIN_SHIM: + bRet = SetVideoRectangle_VIDEOIN(x, y, w, h, videoInputType); + break; + case PlayerThunderAccessShim::OTA_SHIM: + bRet = SetVideoRectangle_OTA(x, y, w, h); + break; + case PlayerThunderAccessShim::RMF_SHIM: + bRet = SetVideoRectangle_RMF(x, y, w, h); + break; + default: + MW_LOG_ERR("Undefined shim used: %d", (int)shim); + break; + } + return bRet; +} + +bool PlayerThunderAccess::SetVideoRectangle_VIDEOIN(int x, int y, int w, int h, std::string videoInputType) +{ + int screenWidth = 0; + int screenHeight = 0; + int widthFromDS = 0; + int heightFromDS = 0; + float width_ratio = 1.0, height_ratio = 1.0; + if(GetScreenResolution(screenWidth,screenHeight) && GetResolutionFromDS_VIDEOIN(widthFromDS,heightFromDS)) + { + if((0 != screenWidth) && (0 != screenHeight)) + { + width_ratio = (float)widthFromDS /(float) screenWidth; + height_ratio =(float) heightFromDS / (float) screenHeight; + MW_LOG_INFO("screenWidth:%d screenHeight:%d widthFromDS:%d heightFromDS:%d width_ratio:%f height_ratio:%f",screenWidth,screenHeight,widthFromDS,heightFromDS,width_ratio,height_ratio); + } + } + + JsonObject param; + JsonObject result; + param["x"] = (int) (x * width_ratio); + param["y"] = (int) (y * height_ratio); + param["w"] = (int) (w * width_ratio); + param["h"] = (int) (h * height_ratio); + param["typeOfInput"] = videoInputType; + MW_LOG_WARN("type:%s x:%d y:%d w:%d h:%d w-ratio:%f h-ratio:%f",videoInputType.c_str(),x,y,w,h,width_ratio,height_ratio); + return InvokeJSONRPC("setVideoRectangle", param, result); + +} + +bool PlayerThunderAccess::GetResolutionFromDS_VIDEOIN(int & widthFromDS, int & heightFromDS) +{ + bool bRetVal = false; + + JsonObject param; + JsonObject result; + + PlayerThunderAccess* thunderDsObj = new PlayerThunderAccess(PlayerThunderAccessPlugin::DS); + + if( thunderDsObj->InvokeJSONRPC("getCurrentResolution", param, result) ) + { + widthFromDS = result["w"].Number(); + heightFromDS = result["h"].Number(); + MW_LOG_INFO("widthFromDS:%d heightFromDS:%d ",widthFromDS, heightFromDS); + bRetVal = true; + } + return bRetVal; +} + +bool PlayerThunderAccess::GetScreenResolution(int & screenWidth, int & screenHeight) +{ + bool bRetVal = false; + + JsonObject param; + JsonObject result; + + PlayerThunderAccess* thunderRDKShellObj = new PlayerThunderAccess(PlayerThunderAccessPlugin::RDKSHELL); + + if( thunderRDKShellObj->InvokeJSONRPC("getScreenResolution", param, result) ) + { + screenWidth = result["w"].Number(); + screenHeight = result["h"].Number(); + MW_LOG_INFO("screenWidth:%d screenHeight:%d ",screenWidth, screenHeight); + bRetVal = true; + } + return bRetVal; +} + +/** + * @brief Registers Event to input plugin and to mRegisteredEvents list for later use. + */ +void PlayerThunderAccess::RegisterEvent_VIDEOIN(string eventName, std::function functionHandler) +{ + bool bSubscribed; + bSubscribed = SubscribeEvent(_T(eventName), functionHandler); + if(bSubscribed) + { + mRegisteredEvents.push_back(eventName); + } +} + +/** + * @brief Registers all Events to input plugin + */ +void PlayerThunderAccess::RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) +{ + + mOnSignalChangedCb = OnSignalChangedCb; + mOnInputStatusChangedCb = OnInputStatusChangedCb; + + std::function inputStatusChangedMethod = std::bind(&PlayerThunderAccess::OnInputStatusChanged, this, std::placeholders::_1); + + RegisterEvent_VIDEOIN("onInputStatusChanged",inputStatusChangedMethod); + + std::function signalChangedMethod = std::bind(&PlayerThunderAccess::OnSignalChanged, this, std::placeholders::_1); + + RegisterEvent_VIDEOIN("onSignalChanged",signalChangedMethod); +} + +/** + * @brief UnRegisters all Events to input plugin + */ +void PlayerThunderAccess::UnRegisterAllEventsVideoin() +{ + for (auto const& evtName : mRegisteredEvents) { + UnSubscribeEvent(_T(evtName)); + } + mRegisteredEvents.clear(); +} + +/** + * @brief Gets onSignalChanged and translates into player events + */ +void PlayerThunderAccess::OnInputStatusChanged(const JsonObject& parameters) +{ + std::string message; + parameters.ToString(message); + MW_LOG_WARN("%s",message.c_str()); + + std::string strStatus = parameters["status"].String(); + + mOnInputStatusChangedCb(strStatus); +} + +/** + * @brief Gets onSignalChanged and translates into player events + */ +void PlayerThunderAccess::OnSignalChanged (const JsonObject& parameters) +{ + std::string message; + parameters.ToString(message); + MW_LOG_WARN("%s",message.c_str()); + std::string strStatus = parameters["signalStatus"].String(); + + mOnSignalChangedCb(strStatus); +} + +/** + * @brief calls start on video in specified by port and method name + */ +void PlayerThunderAccess::StartHelperVideoin(int port, std::string videoInputType) +{ + MW_LOG_WARN("port:%d",port); + + videoInputPort = port; + JsonObject param; + JsonObject result; + const std::string & methodName = "startInput"; + param["portId"] = videoInputPort; + param["typeOfInput"] = videoInputType; + InvokeJSONRPC(methodName, param, result); +} + +/** + * @brief Stops streaming. + */ +void PlayerThunderAccess::StopHelperVideoin(std::string videoInputType) +{ + if( videoInputPort>=0 ) + { + + JsonObject param; + JsonObject result; + const std::string methodName = "stopInput"; + param["typeOfInput"] = videoInputType; + InvokeJSONRPC(methodName, param, result); + + videoInputPort = -1; + } +} + +void PlayerThunderAccess::RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) +{ + mVideoInfoUpdatedMethodCb = videoInfoUpdatedMethodCb; + std::function videoInfoUpdatedMethod = std::bind(&PlayerThunderAccess::OnVideoStreamInfoUpdate, this, std::placeholders::_1); + RegisterEvent_VIDEOIN("videoStreamInfoUpdate", videoInfoUpdatedMethod); +} + +/** + * @brief Gets videoStreamInfoUpdate event and translates into player events + */ +void PlayerThunderAccess::OnVideoStreamInfoUpdate(const JsonObject& parameters) +{ + std::string message; + parameters.ToString(message); + MW_LOG_WARN("%s",message.c_str()); + + JsonObject videoInfoObj = parameters; + PlayerVideoStreamInfoData data; + data.progressive = videoInfoObj["progressive"].Boolean(); + data.frameRateN = static_cast (videoInfoObj["frameRateN"].Number()); + data.frameRateD = static_cast (videoInfoObj["frameRateD"].Number()); + data.width = (int)videoInfoObj["width"].Number(); + data.height = (int)videoInfoObj["height"].Number(); + mVideoInfoUpdatedMethodCb(data); +} + +void PlayerThunderAccess::RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) +{ + mOnPlayerStatusCb = onPlayerStatusCb; + std::function actualMethod = std::bind(&PlayerThunderAccess::onPlayerStatusHandler_OTA, this, std::placeholders::_1); + mEventSubscribed = SubscribeEvent(_T("onPlayerStatus"), actualMethod); +} + +void PlayerThunderAccess::onPlayerStatusHandler_OTA(const JsonObject& parameters) +{ + + PlayerStatusData data; + std::string message; + parameters.ToString(message); + + JsonObject playerData = parameters[APP_ID].Object(); + MW_LOG_TRACE( "[OTA_SHIM]Received event : message : %s ", message.c_str()); + /* For detailed event data, we can print or use details like + playerData["locator"].String(), playerData["length"].String(), playerData["position"].String() */ + data.currState = playerData["playerStatus"].String(); + data.eventUrl = playerData["locator"].String(); + data.reasonString = playerData["blockedReason"].String(); + + JsonObject ratingObj = playerData["rating"].Object(); + ratingObj.ToString(data.ratingString); + + data.ssi = playerData["ssi"].Number(); + + JsonObject videoInfoObj = playerData["videoInfo"].Object(); + data.vid_progressive = videoInfoObj["progressive"].Boolean(); + data.vid_frameRateN = static_cast (videoInfoObj["frameRateN"].Number()); + data.vid_frameRateD = static_cast (videoInfoObj["frameRateD"].Number()); + data.vid_aspectRatioWidth = videoInfoObj["aspectRatioWidth"].Number(); + data.vid_aspectRatioHeight = videoInfoObj["aspectRatioHeight"].Number(); + data.vid_width = videoInfoObj["width"].Number(); + data.vid_height = videoInfoObj["height"].Number(); + data.vid_codec = videoInfoObj["codec"].String(); + data.vid_hdrType = videoInfoObj["hdrType"].String(); + data.vid_bitrate = videoInfoObj["bitrate"].Number(); + + JsonObject audioInfoObj = playerData["audioInfo"].Object(); + data.aud_codec = audioInfoObj["codec"].String(); + data.aud_mixType = audioInfoObj["mixType"].String(); + data.aud_isAtmos = audioInfoObj["isAtmos"].Boolean(); + data.aud_bitrate = audioInfoObj["bitrate"].Number(); + + mOnPlayerStatusCb(data); + +} + +void PlayerThunderAccess::ReleaseOta() +{ + JsonObject param; + JsonObject result; + param["id"] = APP_ID; + param["tag"] = "MyApp"; + InvokeJSONRPC("release", param, result); + + // unsubscribing only if subscribed + if (mEventSubscribed) + { + UnSubscribeEvent(_T("onPlayerStatus")); + mEventSubscribed = false; + } + else + { + MW_LOG_WARN("[OTA_SHIM]OTA Destructor finds Player Status Event not Subscribed !! "); + } + + MW_LOG_INFO("[OTA_SHIM]StreamAbstraction_OTA Destructor called !! "); +} + +void PlayerThunderAccess::StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) +{ + MW_LOG_INFO( "[OTA_SHIM] url : %s ", url.c_str()); + + JsonObject result; + + SetPreferredAudioLanguages_OTA(preferredLanguagesString, atsc_preferredLanguagesString, preferredRenditionString, atsc_preferredRenditionString); + + JsonObject createParam; + createParam["id"] = APP_ID; + createParam["tag"] = "MyApp"; + InvokeJSONRPC("create", createParam, result); + + JsonObject displayParam; + displayParam["id"] = APP_ID; + displayParam["display"] = waylandDisplay; + InvokeJSONRPC("setWaylandDisplay", displayParam, result); + + JsonObject loadParam; + loadParam["id"] = APP_ID; + loadParam["url"] = url; + loadParam["autoplay"] = true; + InvokeJSONRPC("load", loadParam, result); +} + +void PlayerThunderAccess::StopOta() +{ + JsonObject param; + JsonObject result; + param["id"] = APP_ID; + InvokeJSONRPC("stop", param, result); +} + +bool PlayerThunderAccess::SetVideoRectangle_OTA(int x, int y, int w, int h) +{ + JsonObject param; + JsonObject result; + param["id"] = APP_ID; + param["x"] = x; + param["y"] = y; + param["w"] = w; + param["h"] = h; + int screenWidth = 0; + int screenHeight = 0; + if(GetScreenResolution(screenWidth,screenHeight)) + { + JsonObject meta; + meta["resWidth"] = screenWidth; + meta["resHeight"] = screenHeight; + param["meta"] = meta; + } + + return InvokeJSONRPC("setVideoRectangle", param, result); +} + +void PlayerThunderAccess::SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) +{ + switch(shim) + { + case PlayerThunderAccessShim::OTA_SHIM: + SetPreferredAudioLanguages_OTA(data.preferredLanguagesString, data.pluginPreferredLanguagesString, data.preferredRenditionString, data.pluginPreferredRenditionString); + break; + case PlayerThunderAccessShim::RMF_SHIM: + SetPreferredAudioLanguages_RMF(data.preferredLanguagesString, data.pluginPreferredLanguagesString); + break; + default: + MW_LOG_ERR("Undefined shim used: %d", (int)shim); + break; + } +} + +void PlayerThunderAccess::SetPreferredAudioLanguages_OTA(std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) +{ + JsonObject properties; + bool modifiedLang = false; + bool modifiedRend = false; + if((0 != preferredLanguagesString.length()) && (preferredLanguagesString != atsc_preferredLanguagesString)){ + properties["preferredAudioLanguage"] = preferredLanguagesString.c_str(); + modifiedLang = true; + } + if((0 != preferredRenditionString.length()) && (preferredRenditionString != atsc_preferredRenditionString)){ + + if(0 == preferredRenditionString.compare("VISUALLY_IMPAIRED")){ + properties["visuallyImpaired"] = true; + modifiedRend = true; + }else if(0 == preferredRenditionString.compare("NORMAL")){ + properties["visuallyImpaired"] = false; + modifiedRend = true; + }else{ + /*No rendition settings to MediaSettings*/ + } + } + if(modifiedLang || modifiedRend) + { + bool rpcResult = false; + JsonObject result; + JsonObject param; + + param["properties"] = properties; + PlayerThunderAccess* mediaSettingsObj = new PlayerThunderAccess(PlayerThunderAccessPlugin::MEDIASETTINGS); + mediaSettingsObj->ActivatePlugin(); + rpcResult = mediaSettingsObj->InvokeJSONRPC("setProperties", param, result); + if (rpcResult){ + if (!result["success"].Boolean()){ + std::string responseStr; + result.ToString(responseStr); + MW_LOG_WARN( "[OTA_SHIM] setProperties API failed result:%s", responseStr.c_str()); + }else{ + std::string paramStr; + param.ToString(paramStr); + MW_LOG_WARN( "[OTA_SHIM] setProperties success with param:%s", paramStr.c_str()); + } + } + } +} + +/** + * @brief GetAudioTrackInternal get the primary key for the selected audio + */ +int PlayerThunderAccess::GetAudioTrackInternal_OTA() +{ + int pk = 0; + + JsonObject param; + JsonObject result; + + MW_LOG_TRACE("[OTA_SHIM]Entered "); + param["id"] = APP_ID; + InvokeJSONRPC("getAudioTrack", param, result); + pk = result["pk"].Number(); + + return pk; +} + +/** + * @brief GetAudioTracks get the available audio tracks for the selected service / media + */ +std::string PlayerThunderAccess::GetAudioTracksOta(std::vector audData) +{ + JsonObject param; + JsonObject result; + JsonArray attributesArray; + std::string aTrackIdx = ""; + std::string output; + JsonArray outputArray; + JsonObject audioData; + int i = 0,arrayCount = 0; + long bandwidth = -1; + int currentTrackPk = 0; + + currentTrackPk = GetAudioTrackInternal_OTA(); + + attributesArray.Add("pk"); // int - Unique primary key dynamically allocated. Used for track selection. + attributesArray.Add("name"); // Name to display in the UI when doing track selection + attributesArray.Add("type"); // e,g "MPEG4_AAC" "MPEG2" etc + attributesArray.Add("description"); //Track description supplied by the content provider + attributesArray.Add("language"); //ISO 639-2 three character text language (terminology variant per DVB standard, i.e. "deu" instead of "ger") + attributesArray.Add("contentType"); // e.g "DIALOGUE" , "EMERGENCY" , "MUSIC_AND_EFFECTS" etc + attributesArray.Add("mixType"); // Signaled audio mix type - orthogonal to AudioTrackType; For example, ac3 could be either surround or stereo.e.g "STEREO" , "SURROUND_SOUND" + attributesArray.Add("isSelected"); // Is Currently selected track + + param["id"] = APP_ID; + param["attributes"] = attributesArray; + + InvokeJSONRPC("getAudioTracks", param, result); + + result.ToString(output); + MW_LOG_TRACE( "[OTA_SHIM]:audio track output : %s ", output.c_str()); + outputArray = result["table"].Array(); + arrayCount = outputArray.Length(); + + for(i = 0; i < arrayCount; i++) + { + audioData = outputArray[i].Object(); + + if(currentTrackPk == audioData["pk"].Number()){ + aTrackIdx = to_string(i); + } + + PlayerAudioData temp(audioData["language"].String(), audioData["contentType"].String(), audioData["name"].String(), audioData["type"].String(), (int)audioData["pk"].Number(), audioData["mixType"].String()); + + audData.push_back(temp); + + } + return aTrackIdx; +} + +/** + * @brief SetAudioTrack sets a specific audio track + */ +std::string PlayerThunderAccess::SetAudioTrackOta(int trackId, int primaryKey) +{ + JsonObject param; + JsonObject result; + + std::string audioTrackIndex = ""; + + param["id"] = APP_ID; + + param["trackPk"] = primaryKey; + + InvokeJSONRPC("setAudioTrack", param, result); + if (result["success"].Boolean()) { + audioTrackIndex = to_string(trackId); + } + + return audioTrackIndex; +} + +/** + * @brief GetTextTracks get the available text tracks for the selected service / media + */ +bool PlayerThunderAccess::GetTextTracksOta(std::vector txtData) +{ + MW_LOG_TRACE("[OTA_SHIM]"); + JsonObject param; + JsonObject result; + JsonArray attributesArray; + std::string output; + JsonArray outputArray; + JsonObject textData; + int arrayCount = 0; + + bool bRet = false; + + attributesArray.Add("pk"); // int - Unique primary key dynamically allocated. Used for track selection. + attributesArray.Add("name"); // Name to display in the UI when doing track selection + attributesArray.Add("type"); // Specific track type for the track - "CC" for ATSC Closed caption + attributesArray.Add("description"); //Track description supplied by the content provider + attributesArray.Add("language"); //ISO 639-2 three character text language + attributesArray.Add("contentType"); // Track content type e.g "HEARING_IMPAIRED", "EASY_READER" + attributesArray.Add("ccServiceNumber"); // Set to 1-63 for 708 CC Subtitles and 0 for 608 + attributesArray.Add("isSelected"); // Is Currently selected track + attributesArray.Add("ccTypeIs708"); // Is 708 cc track + attributesArray.Add("ccType"); // Actual cc track type + + param["id"] = APP_ID; + param["attributes"] = attributesArray; + + InvokeJSONRPC("getSubtitleTracks", param, result); + + result.ToString(output); + MW_LOG_TRACE( "[OTA_SHIM]:text track output : %s ", output.c_str()); + outputArray = result["table"].Array(); + arrayCount = outputArray.Length(); + + std::string txtTrackIdx = ""; + std::string instreamId; + int ccIndex = 0; + + for(int i = 0; i < arrayCount; i++) + { + + std::string trackType; + textData = outputArray[i].Object(); + trackType = textData["type"].String(); + + PlayerTextData temp(textData["type"].String(), textData["language"].String(), (int)textData["ccServiceNumber"].Number(), textData["ccType"].String(), textData["name"].String(), (int)textData["pk"].Number()); + + txtData.push_back(temp); + + // txtTracks.push_back(TextTrackInfo(index, languageCode, true, empty, textData["name"].String(), serviceNo, empty, (int)textData["pk"].Number())); + + } + + if(!txtData.empty()) + { + bRet = true; + } + + return bRet; +} + +/** + * @brief Disable Restrictions (unlock) till seconds mentioned + */ +void PlayerThunderAccess::DisableContentRestrictionsOta(long grace, long time, bool eventChange) +{ + JsonObject param; + JsonObject result; + param["id"] = APP_ID; + if(-1 == grace){ + + param["grace"] = -1; + param["time"] = -1; + param["eventChange"] = false; + MW_LOG_WARN( "[OTA_SHIM] unlocked till next reboot or explicit enable" ); + }else{ + param["grace"] = 0; + param["time"] = time; + param["eventChange"] = eventChange; + + if(-1 != time) + MW_LOG_WARN( "[OTA_SHIM] unlocked for %ld sec ", time); + + if(eventChange) + MW_LOG_WARN( "[OTA_SHIM] unlocked till next program "); + } + InvokeJSONRPC("disableContentRestrictionsUntil", param, result); + +} + +/** + * @brief Enable Content Restriction (lock) + */ +void PlayerThunderAccess::EnableContentRestrictionsOta() +{ + MW_LOG_WARN( "[OTA_SHIM] locked "); + JsonObject param; + JsonObject result; + param["id"] = APP_ID; + InvokeJSONRPC("enableContentRestrictions", param, result); +} + +bool PlayerThunderAccess::InitRmf() +{ + bool retval = true; + ActivatePlugin(); + JsonObject param; + JsonObject result; + param["source_type"] = "qam"; + if(false == InvokeJSONRPC("initialize", param, result)) //Note: do not terminate unless we're desperate for resources. deinit is sluggish. + { + MW_LOG_ERR("Failed to initialize RMF plugin"); + retval = false; + } + + return retval; +} + +void PlayerThunderAccess::onPlayerStatusHandler_RMF(const JsonObject& parameters) { + std::string message; + parameters.ToString(message); + + JsonObject playerData = parameters[APP_ID].Object(); + MW_LOG_WARN( "[RMF_SHIM]Received event : message : %s ", message.c_str()); + + + std::string title = parameters["title"].String(); + + mOnPlayerStatusHandlerCb(title); +} + +void PlayerThunderAccess::onPlayerErrorHandler_RMF(const JsonObject& parameters) { + std::string message; + parameters.ToString(message); + + MW_LOG_WARN( "[RMF_SHIM]Received error : message : %s ", message.c_str()); + + std::string error_message = parameters["source"].String() + ": " + parameters["title"].String() + "- " + parameters["message"].String(); + mOnPlayerErrorHandlerCb(error_message); +} + +bool PlayerThunderAccess::StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) +{ + bool bRet = true; + mOnPlayerStatusHandlerCb = onPlayerStatusHandlerCb; + mOnPlayerErrorHandlerCb = onPlayerErrorHandlerCb; + + JsonObject result; + + std::function eventHandler = std::bind(&PlayerThunderAccess::onPlayerStatusHandler_RMF, this, std::placeholders::_1); + std::function errorHandler = std::bind(&PlayerThunderAccess::onPlayerErrorHandler_RMF, this, std::placeholders::_1); + + if(true != SubscribeEvent(_T("onStatusChanged"), eventHandler)) + { + MW_LOG_ERR("Failed to register for onStatusChanged notification from RMF plugin"); + } + if(true != SubscribeEvent(_T("onError"), errorHandler)) + { + MW_LOG_ERR("Failed to register for onError notification from RMF plugin"); + } + + JsonObject playParam; + playParam["source_type"] = "qam"; + playParam["identifier"] = url; + if(true != InvokeJSONRPC("play", playParam, result)) + { + bRet = false; + } + + return bRet; +} + +/** + * @brief SetPreferredAudioLanguages set the preferred audio language list + */ +void PlayerThunderAccess::SetPreferredAudioLanguages_RMF(std::string preferredLanguagesString, std::string rmf_preferredLanguages) +{ + JsonObject properties; + bool modifiedLang = false; + bool modifiedRend = false; + + if((0 != preferredLanguagesString.length()) && (preferredLanguagesString != rmf_preferredLanguages)){ + properties["preferredAudioLanguage"] = preferredLanguagesString.c_str(); + modifiedLang = true; + } + if(modifiedLang || modifiedRend) + { + bool rpcResult = false; + JsonObject result; + JsonObject param; + + param["properties"] = properties; + //TODO: Pass preferred audio language to MediaEngineRMF. Not currently supported. + + } +} + +void PlayerThunderAccess::StopRmf() +{ + JsonObject param; + JsonObject result;; + if(true != InvokeJSONRPC("stop", param, result)) + { + MW_LOG_ERR("Failed to stop RMF playback"); + } + UnSubscribeEvent(_T("onStatusChanged")); + UnSubscribeEvent(_T("onError")); +} + +/** + * @brief SetVideoRectangle sets the position coordinates (x,y) & size (w,h) + */ +bool PlayerThunderAccess::SetVideoRectangle_RMF(int x, int y, int w, int h) +{ + bool bRet = true; + JsonObject param; + JsonObject videoRect; + JsonObject result; + videoRect["x"] = x; + videoRect["y"] = y; + videoRect["width"] = w; + videoRect["height"] = h; + + param["video_rectangle"] = videoRect; + + if(true != InvokeJSONRPC("setVideoRectangle", param, result)) + { + bRet = false; + } + + return bRet; +} + +/** + * @brief DeleteWatermark delete watermark of layer + */ +bool PlayerThunderAccess::DeleteWatermark(int layerID) +{ + bool ret = false; + JsonObject param, result; + param["id"] = layerID; + bool success = InvokeJSONRPC("deleteWatermark", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + + return ret; +} + +/** + * @brief CreateWatermark create watermark + */ +bool PlayerThunderAccess::CreateWatermark(int layerID) +{ + bool ret = false; + JsonObject param, result; + param["id"] = layerID; + param["zorder"] = 1; + bool success = InvokeJSONRPC("createWatermark", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + + return ret; +} + +/** + * @brief ShowWatermark create watermark + */ +bool PlayerThunderAccess::ShowWatermark(int opacity) +{ + bool ret = false; + JsonObject param, result; + param["show"] = true; + param["alpha"] = opacity; + bool success = InvokeJSONRPC("showWatermark", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + + return ret; +} + +/** + * @brief HideWatermark hides watermark + */ +bool PlayerThunderAccess::HideWatermark() +{ + bool ret = false; + JsonObject param, result; + param["show"] = false; + bool success = InvokeJSONRPC("showWatermark", param, result); + + if(success && result["success"].Boolean()) + { + ret = true; + } + return ret; +} + +/** + * @brief UpdateWatermark updates watermark + */ +bool PlayerThunderAccess::UpdateWatermark(int layerID, int sharedMemoryKey, int size) +{ + bool ret = false; + JsonObject param, result; + param["id"] = layerID; + param["key"] = sharedMemoryKey; + param["size"] = size; + bool success = InvokeJSONRPC("updateWatermark", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + return ret; +} + +/** + * @brief GetMetaDataWatermark gets metadata for watermark + */ +std::string PlayerThunderAccess::GetMetaDataWatermark() +{ + JsonObject param, result; + bool success = InvokeJSONRPC("PersistentStoreMetadata", param, result); + std::string metaData=""; + if(success) + { + metaData = result["metadata"].String(); + } + return metaData; +} + +/** + * @brief PersistentStoreSaveWatermark saves in persistent store + */ +bool PlayerThunderAccess::PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) +{ + bool ret = false; + JsonObject param, result; + param["image"] = base64Image; + param["metadata"] = metaData; + bool success = InvokeJSONRPC("PersistentStoreSave", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + return ret; +} + +/** + * @brief PersistentStoreLoadWatermark loads from persistent store + */ +bool PlayerThunderAccess::PersistentStoreLoadWatermark(int layerID) +{ + bool ret = false; + JsonObject param, result; + param["id"] = layerID; + bool success = InvokeJSONRPC("PersistentStoreLoad", param, result); + if(success && result["success"].Boolean()) + { + ret = true; + } + return ret; +} diff --git a/middleware/externals/rdk/PlayerThunderAccess.h b/middleware/externals/rdk/PlayerThunderAccess.h new file mode 100644 index 000000000..427d2f5ce --- /dev/null +++ b/middleware/externals/rdk/PlayerThunderAccess.h @@ -0,0 +1,452 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerThunderAccess.h + * @brief shim for dispatching UVE HDMI input playback + */ + +#ifndef PLAYER_THUNDERACCESS_H_ +#define PLAYER_THUNDERACCESS_H_ + +#include "Module.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Weffc++" +#include +#include +#pragma GCC diagnostic pop + +#include "PlayerThunderInterface.h" + +using namespace std; +using namespace WPEFramework; + +#define THUNDER_RPC_TIMEOUT 5000 + + +/** + * @class PlayerThunderAccess + * @brief Support Thunder Plugin Access from Player + */ +class PlayerThunderAccess : public PlayerThunderAccessBase +{ +public: + /** + * @fn PlayerThunderAccess + * @note Security token acquisition, controller object creation + * @param callsign plugin callsign + */ + PlayerThunderAccess(PlayerThunderAccessPlugin callsign); + + /** + * @fn ~PlayerThunderAccess + * @note clean up + */ + ~PlayerThunderAccess(); + + /** + * @brief PlayerThunderAccess copy constructor disabled + */ + PlayerThunderAccess(const PlayerThunderAccess&) = delete; + + /** + * @brief PlayerThunderAccess assignment disabled + */ + PlayerThunderAccess& operator=(const PlayerThunderAccess&) = delete; + + /** + * @brief ActivatePlugin + * @note Plugin activation and Remote object creation + * @param Plugin Callsign + * @retval true on success + * @retval false on failure + */ + bool ActivatePlugin() override; + + /** + * @fn UnSubscribeEvent + * @note unSubscribe event data for the specific plugin + * @param eventName Event name + * @retval true on success + * @retval false on failure + */ + bool UnSubscribeEvent (string eventName)override; + + /** + * @fn InvokeJSONRPC + * @note Invoke JSONRPC call for the plugin + * @param method,param,result, method,waitTime reference to input param, result and waitTime (default = THUNDER_RPC_TIMEOUT) + * @retval true on success + * @retval false on failure + */ + bool InvokeJSONRPC(std::string method, const JsonObject ¶m, JsonObject &result, const uint32_t waitTime = THUNDER_RPC_TIMEOUT); + + /** + * @fn SetVideoRectangle + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @param videoInputType string video input type + * @param shim shim to set video rectangle for + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) override; + + /** + * @fn SetPreferredAudioLanguages_OTA + * @param data player's input on preferred languages + * @param shim shim to set preferred language for + */ + void SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) override; + + /** + * @fn RegisterAllEventsVideoin + * @param OnSignalChangedCb callback for when signal change + * @param OnInputStatusChangedCb callback for when input status changes + */ + void RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) override; + + /** + * @fn UnRegisterAllEventsVideoin + */ + void UnRegisterAllEventsVideoin() override; + + /** + * @fn StartHelper + * @param port port number + * @param videoInputType string video input type + */ + void StartHelperVideoin(int port, std::string videoInputType) override; + /** + * @fn StopHelper + * @param videoInputType string video input type + */ + void StopHelperVideoin(std::string videoInputType) override; + + /** + * @fn RegisterEventOnVideoStreamInfoUpdateHdmiin + * @param videoInfoUpdatedMethodCb callback for when video info updated + */ + void RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) override; + + /** + * @fn RegisterOnPlayerStatusOta + * @param onPlayerStatusCb callback for player when player status update + */ + void RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) override; + + /** + * @fn ReleaseOta + */ + void ReleaseOta() override; + + /** + * @fn StartOta + * @param url string containing url + * @param waylandDisplay string wayland display + * @param preferredLanguagesString player's preferred languages + * @param atsc_preferredLanguagesString ATSC preferred languages + * @param preferredRenditionString player's preferred rendition + * @param atsc_preferredRenditionString ATSC preferred rendition + */ + void StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) override; + + /** + * @fn StopOta + */ + void StopOta() override; + + /** + * @fn GetAudioTracksOta + * @param audData vector to be filled with audio data + * @return string of current track pk + */ + std::string GetAudioTracksOta(std::vector audData) override; + + /** + * @fn SetAudioTrackOta + * @param index audio index to be set + * @param primaryKey primary key + * @return string of audioTrackIndex on success, empty string on failure + */ + std::string SetAudioTrackOta(int index, int primaryKey) override; + + /** + * @fn GetTextTracksOta + * @param txtData vector to be filled with text data + * @return true if successful, false if failed + */ + bool GetTextTracksOta(std::vector txtData) override; + + /** + * @fn DisableContentRestrictionsOta + * + * @param[in] grace - seconds from current time, grace period, grace = -1 will allow an unlimited grace period + * @param[in] time - seconds from current time,time till which the channel need to be kept unlocked + * @param[in] eventChange - disable restriction handling till next program event boundary + */ + void DisableContentRestrictionsOta(long grace, long time, bool eventChange) override; + + /** + * @fn EnableContentRestrictions + * + */ + void EnableContentRestrictionsOta() override; + + /** + * @fn InitRmf + * @return true if successful, false if failed + */ + bool InitRmf() override; + + /** + * @fn StartRmf + * @param url url string + * @param onPlayerStatusHandlerCb player status handler callback + * @param onPlayerErrorHandlerCb player error handler callback + * @return true if successful, false if failed + */ + bool StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) override; + + /** + * @fn StopRmf + * + */ + void StopRmf() override; + + /** + * @fn DeleteWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool DeleteWatermark(int layerID) override; + + /** + * @fn CreateWatermark + * @param layerID layer ID + * @return true if successful, false if failed + */ + bool CreateWatermark(int layerID) override; + + /** + * @fn ShowWatermark + * @param opacity opacity + * @return true if successful, false if failed + */ + bool ShowWatermark(int opacity) override; + + /** + * @fn HideWatermark + * @return true if successful, false if failed + */ + bool HideWatermark() override; + + /** + * @fn UpdateWatermark + * @param layerID layed id + * @param sharedMemoryKey key of shared mem + * @param size size + * @return true if successful, false if failed + */ + bool UpdateWatermark(int layerID, int sharedMemoryKey, int size) override; + + /** + * @fn GetMetaDataWatermark + * @return string of meta data if successful, empty string if failed + */ + std::string GetMetaDataWatermark() override; + + /** + * @fn PersistentStoreSaveWatermark + * @param base64Image image data + * @param metaData metaData + * @return true if successful, false if failed + */ + bool PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) override; + + /** + * @fn PersistentStoreLoadWatermark + * @param layerID layer id + * @return true if successful, false if failed + */ + bool PersistentStoreLoadWatermark(int layerID) override; + + /** + * @fn IsThunderAccess + * @return true if thunder access available + * @return false f thunder access not available + */ + bool IsThunderAccess() override {return true;} + + +protected: + + /**< The Remote object connected to specific Plugin*/ + JSONRPC::LinkType *remoteObject; + /**< The Remote object connected to controller Plugin*/ + JSONRPC::LinkType *controllerObject; + +private: + + std::list mRegisteredEvents; + + bool mEventSubscribed; + + int videoInputPort; + + std::function mOnSignalChangedCb; //callback to player on signal change + std::function mOnInputStatusChangedCb; //callback to player on input status change + std::function mVideoInfoUpdatedMethodCb; //callback to player on video info update + std::function mOnPlayerStatusCb; //callback to player on player status update + std::function mOnPlayerStatusHandlerCb; //callback to player on status change, rmf + std::function mOnPlayerErrorHandlerCb; //callback to player on error, rmf + + /** + * @fn RegisterEvent + * @param[in] eventName : Event name + * @param[in] functionHandler : Event function pointer + * @return NA + */ + void RegisterEvent_VIDEOIN(string eventName, std::function functionHandler); + + /** + * @fn GetScreenResolution + * @param[out] screenWidth : width + * @param[in] screenHeight : height + * @return true if success, false if failure + */ + bool GetScreenResolution(int & screenWidth, int & screenHeight); + + /** + * @fn SetVideoRectangle + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @param videoInputType string video input type + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle_VIDEOIN(int x, int y, int w, int h, std::string videoInputType); + + /** + * @fn SetVideoRectangle_OTA + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle_OTA(int x, int y, int w, int h); + + /** + * @fn SetVideoRectangle_RMF + * @note Set video rectangle dimensions + * @param x x offset + * @param y y offset + * @param w width + * @param h height + * @retval true if success + * @retval false if failure + */ + bool SetVideoRectangle_RMF(int x, int y, int w, int h); + + /** + * @fn SetPreferredAudioLanguages_OTA + * @param preferredLanguagesString string containing preferred language + * @param atsc_preferredLanguagesString string containing ATSC preferred language + * @param preferredRenditionString string containing preferred rendition + * @param atsc_preferredRenditionString string containing ATSC preferred rendition + */ + void SetPreferredAudioLanguages_OTA(std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString); + + /** + * @fn SetPreferredAudioLanguages_RMF + * @param preferredLanguagesString string containing preferred language to be set + * @param rmf_preferredLanguages string RMF preferred language + */ + void SetPreferredAudioLanguages_RMF(std::string preferredLanguagesString, std::string rmf_preferredLanguages); + + /** + * @fn GetResolutionFromDS_VIDEOIN + * @param[out] widthFromDS : width + * @param[in] heightFromDS : height + * @return true if success, false if failure + */ + bool GetResolutionFromDS_VIDEOIN(int & widthFromDS, int & heightFromDS); + + /** + * @fn SubscribeEvent + * @note Subscribe event data for the specific plugin + * @param eventName,functionHandler Event name, Event handler + * @retval true on success + * @retval false on failure + */ + bool SubscribeEvent (string eventName, std::function functionHandler); + + /** + * @fn GetAudioTrackInternal + * + */ + int GetAudioTrackInternal_OTA(); + + /** + * @fn OnInputStatusChanged callback + * + */ + void OnInputStatusChanged(const JsonObject& parameters); + + /** + * @fn OnSignalChanged callback + * + */ + void OnSignalChanged (const JsonObject& parameters); + + /** + * @fn OnVideoStreamInfoUpdate callback + * + */ + void OnVideoStreamInfoUpdate(const JsonObject& parameters); + + /** + * @fn onPlayerStatusHandler_OTA callback + * + */ + void onPlayerStatusHandler_OTA(const JsonObject& parameters); + + /** + * @fn onPlayerStatusHandler_RMF callback + * + */ + void onPlayerStatusHandler_RMF(const JsonObject& parameters); + + /** + * @fn onPlayerErrorHandler_RMF callback + * + */ + void onPlayerErrorHandler_RMF(const JsonObject& parameters); + +}; +#endif // PLAYER_THUNDERACCESS_H_ diff --git a/middleware/gst-plugins/CMakeLists.txt b/middleware/gst-plugins/CMakeLists.txt new file mode 100755 index 000000000..13f1e3bb4 --- /dev/null +++ b/middleware/gst-plugins/CMakeLists.txt @@ -0,0 +1,116 @@ +########################################################################## +# Copyright 2025 RDK Management +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation, version 2.1 +# of the license. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. +######################################################################### + +cmake_minimum_required (VERSION 2.6) +project (GST-PLUGINS) + +set(MW_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../") + +#find_package(GStreamer 1.4 REQUIRED) +#add_subdirectory(jsbindings) +find_package(PkgConfig REQUIRED) + +message("using gstreamer-1.0") +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) +pkg_check_modules(GSTREAMERBASE REQUIRED gstreamer-app-1.0) + +pkg_check_modules(CURL REQUIRED libcurl) + +# Mac OS X +if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(OS_CXX_FLAGS "${OS_CXX_FLAGS} -g -x objective-c++") + set(OS_LD_FLAGS "${OS_LD_FLAGS} -framework Cocoa") + string(STRIP ${OS_LD_FLAGS} OS_LD_FLAGS) + set(CLI_LD_FLAGS "${CLI_LD_FLAGS} -lgstvideo-1.0") + string(STRIP ${CLI_LD_FLAGS} CLI_LD_FLAGS) + #set(AAMP_OS_SOURCES cocoa_window.mm) #to be decided +endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + +find_package (Threads REQUIRED) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${CURL_INCLUDE_DIRS}) +include_directories(${GSTREAMERBASE_INCLUDE_DIRS}) +include_directories(${MW_ROOT}/closedcaptions) +include_directories(${MW_ROOT}/) + +set(GST_COMMON_DEPENDENCIES ${OS_LD_FLAGS} ${GSTREAMERBASE_LIBRARIES} ${GSTREAMER_LIBRARIES} ${CURL_LIBRARIES} ${CLI_LD_FLAGS} -ldl -luuid ${SEC_CLIENT_LIB}) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -std=c++11") + +if(CMAKE_IARM_MGR) + message("CMAKE_IARM_MGR set") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIARM_MGR") +endif() + +set(GST_MIDDLEWARE_SOURCES gstinit.cpp + ${MW_ROOT}/playerLogManager/PlayerLogManager.cpp) + +if(CMAKE_CDM_DRM) + message("CMAKE_CDM_DRM set") + set(GST_MIDDLEWARE_SOURCES "${GST_MIDDLEWARE_SOURCES}" drm/gst/gstcdmidecryptor.cpp drm/gst/gstplayreadydecryptor.cpp drm/gst/gstwidevinedecryptor.cpp drm/gst/gstclearkeydecryptor.cpp drm/gst/gstverimatrixdecryptor.cpp) +endif() + +message("AAMP consolidated build, enabling gst_subtec") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGST_SUBTEC_ENABLED") +add_subdirectory(gst_subtec) + +add_library(gstaamp SHARED ${GST_MIDDLEWARE_SOURCES}) + +if(CMAKE_CDM_DRM) + target_include_directories (gstaamp PRIVATE drm/gst) + if(CMAKE_USE_OPENCDM_ADAPTER) + message("CMAKE_USE_OPENCDM_ADAPTER set") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_OPENCDM_ADAPTER") + set(GST_COMMON_DEPENDENCIES "${GST_COMMON_DEPENDENCIES} -locdm") + find_path (STAGING_INCDIR opencdm) + include_directories(${STAGING_INCDIR}/opencdm) + else() + message("CMAKE_USE_OPENCDM_ADAPTER not set") + endif() + set(PLUGIN_DEFINES "${PLUGIN_DEFINES} -DDRM_BUILD_PROFILE=DRM_BUILD_PROFILE_OEM -DTARGET_LITTLE_ENDIAN=1 -DTARGET_SUPPORTS_UNALIGNED_DWORD_POINTERS=0 ${SEC_CONTENT_METADATA_ENABLED}") +else() + message("CMAKE_CDM_DRM not set") +endif() + +target_link_libraries (gstaamp playergstinterface ${GST_COMMON_DEPENDENCIES}) +target_include_directories (gstaamp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_SOURCE_DIR}/../../drm ${CMAKE_CURRENT_SOURCE_DIR}/../../drm/helper ${CMAKE_CURRENT_SOURCE_DIR}/../../tsb/api ${CMAKE_CURRENT_SOURCE_DIR}/../../downloader ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/gst-plugins ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/vendor ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/drm ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/drm/ocdm ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/drm/helper ${CMAKE_CURRENT_SOURCE_DIR}/../../middleware/externals/contentsecuritymanager) + +set(LIBPLUGIN_DEFINES "${PLUGIN_DEFINES}") + + +#TODO Remove once PrivateInstance_Player is compilation flag independent. +#if(CMAKE_USE_SECCLIENT) +# message("CMAKE_USE_SECCLIENT set") +# set(LIBPLUGIN_DEFINES "${LIBPLUGIN_DEFINES} -DUSE_SECCLIENT") +#else if(CMAKE_USE_SECMANAGER) +# message("CMAKE_USE_SECMANAGER set") +# set(LIBPLUGIN_DEFINES "${LIBPLUGIN_DEFINES} -DUSE_SECMANAGER") +#endif() + +install(TARGETS gstaamp DESTINATION lib/gstreamer-1.0) + +if(CMAKE_WPEWEBKIT_JSBINDINGS) + message("CMAKE_WPEWEBKIT_JSBINDINGS set") + #target_link_libraries (gstaamp aampjsbindings) + #set(LIBPLUGIN_DEFINES "${LIBPLUGIN_DEFINES} -DAAMP_JSCONTROLLER_ENABLED") +endif() + +set_target_properties(gstaamp PROPERTIES COMPILE_FLAGS "${LIBPLUGIN_DEFINES}") diff --git a/middleware/gst-plugins/COPYING b/middleware/gst-plugins/COPYING new file mode 100644 index 000000000..5836b7229 --- /dev/null +++ b/middleware/gst-plugins/COPYING @@ -0,0 +1,22 @@ +This software is licensed by RDK Management under the GNU Lesser General +Public License version 2.1 only, as per COPYING.LGPL and subject to any +additional licenses included in this file. + +Please use the following copyright for source within this repository: + + Copyright RDK Management + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1 + of the license. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301, USA. diff --git a/middleware/gst-plugins/COPYING.LGPL b/middleware/gst-plugins/COPYING.LGPL new file mode 100644 index 000000000..51c6803ed --- /dev/null +++ b/middleware/gst-plugins/COPYING.LGPL @@ -0,0 +1,176 @@ + +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author`s reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user`s freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users` freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library`s complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + +a) The modified work must itself be a software library. +b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. +c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. +d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer`s own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) +b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user`s computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. +c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. +d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. +e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. +b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients` exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + +one line to give the library`s name and an idea of what it does. +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +version 2.1 of the License. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob` (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That`s all there is to it! + diff --git a/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp new file mode 100755 index 000000000..1666b5293 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.cpp @@ -0,0 +1,1097 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include "gstcdmidecryptor.h" +#include +#include +#include +#include +#include "DrmConstants.h" +#include "SocInterface.h" + +GST_DEBUG_CATEGORY_STATIC ( gst_cdmidecryptor_debug_category); +#define GST_CAT_DEFAULT gst_cdmidecryptor_debug_category +#define DECRYPT_FAILURE_THRESHOLD 5 + +enum +{ + PROP_0, PROP_PLAYER, PROP_DRM_SESSION_MANAGER +}; + +enum +{ + ePROF_BEGIN, ePROF_END , ePROF_ERR +}; + +//#define FUNCTION_DEBUG 1 +#ifdef FUNCTION_DEBUG +#define DEBUG_FUNC() g_warning("####### %s : %d ####\n", __FUNCTION__, __LINE__); +#else +#define DEBUG_FUNC() +#endif + +/** + * @brief Replaces the Key ID in Widevine PSSH data with the Key ID from Clear Key PSSH data. + * + * This function modifies the Widevine PSSH data by replacing its Key ID with the Key ID + * from the Clear Key PSSH data. It allocates memory for the modified PSSH data and returns it. + * + * @param[in] InputData Pointer to the input PSSH data. + * @param[in] InputDataLength Length of the input PSSH data. + * @param[out] OutputDataLength Reference to store the length of the modified PSSH data. + * @return Pointer to the modified PSSH data. Returns NULL if the input data is invalid + * or memory allocation fails. + * + * @note The caller is responsible for freeing the memory allocated for the modified PSSH data. + */ +static unsigned char* ReplaceKIDPsshData(const unsigned char *InputData, const size_t InputDataLength, size_t & OutputDataLength) +{ + unsigned char *outputData = NULL; + unsigned int WIDEVINE_PSSH_KEYID_OFFSET = 36u; + unsigned int CK_PSSH_KEYID_OFFSET = 32u; + unsigned int COMMON_KEYID_SIZE = 16u; + unsigned char WVSamplePSSH[] = { + 0x00, 0x00, 0x00, 0x3c, + 0x70, 0x73, 0x73, 0x68, + 0x00, 0x00, 0x00, 0x00, + 0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, + 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, + 0x00, 0x00, 0x00, 0x1c, 0x08, 0x01, 0x12, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //dummy KeyId (16 byte) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //dummy KeyId (16 byte) + 0x22, 0x06, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x37 + }; + if (InputData) + { + MW_LOG_INFO("Converting system UUID of PSSH data size (%zu)", InputDataLength); +#ifdef ENABLE_DUMP + MW_LOG_INFO("PSSH Data (%d) Before Modification : ", InputDataLength); + DumpBlob(InputData, InputDataLength); +#endif + /** Replace KeyID of WV PSSH Data with Key ID of CK PSSH Data **/ + int iWVpssh = WIDEVINE_PSSH_KEYID_OFFSET; + int CKPssh = CK_PSSH_KEYID_OFFSET; + int size = 0; + if (CK_PSSH_KEYID_OFFSET+COMMON_KEYID_SIZE <= InputDataLength) + { + for (; size < COMMON_KEYID_SIZE; ++size, ++iWVpssh, ++CKPssh ) + { + /** Transfer KeyID from CK PSSH data to WV PSSH Data **/ + WVSamplePSSH[iWVpssh] = InputData[CKPssh]; + } + /** Allocate WV PSSH Data memory and transfer local data **/ + outputData = (unsigned char *)malloc(sizeof(WVSamplePSSH)); + if (outputData) + { + memcpy(outputData, WVSamplePSSH, sizeof(WVSamplePSSH)); + OutputDataLength = sizeof(WVSamplePSSH); +#ifdef ENABLE_DUMP + MW_LOG_INFO("PSSH Data (%d) after Modification : ", OutputDataLength); DumpBlob(outputData, OutputDataLength); +#endif + return outputData; + } + else + { + MW_LOG_ERR("PSSH Data Memory allocation failed "); + } + } + else + { + //Invalid PSSH data + MW_LOG_ERR("Invalid Clear Key PSSH data "); } + } + else + { //Invalid argument - PSSH Data + MW_LOG_ERR("Invalid Argument of PSSH data "); + } + return NULL; +} + +static const gchar *srcMimeTypes[] = { "video/x-h264", "video/x-h264(memory:SecMem)", "audio/mpeg", "video/x-h265", "video/x-h265(memory:SecMem)", "audio/x-eac3", "audio/x-gst-fourcc-ec_3", "audio/x-ac3","audio/x-opus", nullptr }; + +/* class initialization */ +G_DEFINE_TYPE_WITH_CODE (GstCDMIDecryptor, gst_cdmidecryptor, GST_TYPE_BASE_TRANSFORM, + GST_DEBUG_CATEGORY_INIT (gst_cdmidecryptor_debug_category, "cdmidecryptor", 0, + "debug category for cdmidecryptor element")); + +#if defined(UBUNTU) +// stubs to avoid strange ubuntu-specific SegFault while running L2 Plugin tests +static void gst_cdmidecryptor_class_init( GstCDMIDecryptorClass * klass) +{ + printf( "gst_cdmidecryptor_class_init\n" ); +} +static void gst_cdmidecryptor_init( GstCDMIDecryptor *cdmidecryptor) +{ + printf( "gst_cdmidecryptor_init\n" ); +} +#else +/* prototypes */ +static void gst_cdmidecryptor_dispose(GObject*); +static GstCaps *gst_cdmidecryptor_transform_caps( + GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps, + GstCaps * filter); +static gboolean gst_cdmidecryptor_sink_event(GstBaseTransform * trans, + GstEvent * event); +static GstFlowReturn gst_cdmidecryptor_transform_ip( + GstBaseTransform * trans, GstBuffer * buf); +static GstStateChangeReturn gst_cdmidecryptor_changestate( + GstElement* element, GstStateChange transition); +static void gst_cdmidecryptor_set_property(GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static gboolean gst_cdmidecryptor_accept_caps(GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps); +static OpenCDMError(*OCDMGstTransformCaps)(GstCaps **); + +static void gst_cdmidecryptor_class_init( + GstCDMIDecryptorClass *klass) +{ + DEBUG_FUNC(); + + std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstBaseTransformClass *base_transform_class = GST_BASE_TRANSFORM_CLASS(klass); + + gobject_class->set_property = gst_cdmidecryptor_set_property; + gobject_class->dispose = gst_cdmidecryptor_dispose; + + g_object_class_install_property(gobject_class, PROP_PLAYER, + g_param_spec_pointer("aamp", "AAMP", + "DrmSessionManager instance for DrmCallback", G_PARAM_WRITABLE)); + + g_object_class_install_property(gobject_class, PROP_DRM_SESSION_MANAGER, + g_param_spec_pointer("drm-session-manager", "DRM Session Manager", + "Pointer to DRM session manager", G_PARAM_WRITABLE)); + + GST_ELEMENT_CLASS(klass)->change_state = + gst_cdmidecryptor_changestate; + + base_transform_class->transform_caps = GST_DEBUG_FUNCPTR( + gst_cdmidecryptor_transform_caps); + base_transform_class->sink_event = GST_DEBUG_FUNCPTR( + gst_cdmidecryptor_sink_event); + base_transform_class->transform_ip = GST_DEBUG_FUNCPTR( + gst_cdmidecryptor_transform_ip); + + if (socInterface) + { + socInterface->ConfigureAcceptCaps(base_transform_class, gst_cdmidecryptor_accept_caps); + } + + base_transform_class->transform_ip_on_passthrough = FALSE; + + gst_element_class_set_static_metadata(GST_ELEMENT_CLASS(klass), + "Decrypt encrypted content with CDMi", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts streams encrypted using Encryption.", + "comcast"); + //GST_DEBUG_OBJECT(cdmidecryptor, "Inside custom plugin init\n"); +} + +static void gst_cdmidecryptor_init( + GstCDMIDecryptor *cdmidecryptor) +{ + DEBUG_FUNC(); + const char* ocdmgsttransformcaps = "opencdm_gstreamer_transform_caps"; + GstBaseTransform* base = GST_BASE_TRANSFORM(cdmidecryptor); + + gst_base_transform_set_in_place(base, TRUE); + gst_base_transform_set_passthrough(base, FALSE); + gst_base_transform_set_gap_aware(base, FALSE); + + g_mutex_init(&cdmidecryptor->mutex); + //GST_DEBUG_OBJECT(cdmidecryptor, "\n Initialized plugin mutex\n"); + g_cond_init(&cdmidecryptor->condition); + cdmidecryptor->streamReceived = false; + // Lock access to canWait to keep Coverity happy + g_mutex_lock(&cdmidecryptor->mutex); + cdmidecryptor->canWait = false; + g_mutex_unlock(&cdmidecryptor->mutex); + cdmidecryptor->protectionEvent = NULL; + cdmidecryptor->sessionManager = NULL; + cdmidecryptor->drmSession = NULL; + cdmidecryptor->player = NULL; + cdmidecryptor->mediaType = eGST_MEDIATYPE_MANIFEST; + cdmidecryptor->firstsegprocessed = false; + cdmidecryptor->selectedProtection = NULL; + cdmidecryptor->decryptFailCount = 0; + cdmidecryptor->hdcpOpProtectionFailCount = 0; + cdmidecryptor->notifyDecryptError = true; + cdmidecryptor->streamEncrypted = false; + cdmidecryptor->ignoreSVP = false; + cdmidecryptor->sinkCaps = NULL; + cdmidecryptor->svpCtx = NULL; + + OCDMGstTransformCaps = (OpenCDMError(*)(GstCaps**))dlsym(RTLD_DEFAULT, ocdmgsttransformcaps); + if (OCDMGstTransformCaps) + GST_INFO_OBJECT(cdmidecryptor, "Has opencdm_gstreamer_transform_caps support \n"); + else + GST_INFO_OBJECT(cdmidecryptor, "No opencdm_gstreamer_transform_caps support \n"); + //GST_DEBUG_OBJECT(cdmidecryptor, "******************Init called**********************\n"); +} + +void gst_cdmidecryptor_dispose(GObject * object) +{ + DEBUG_FUNC(); + + GstCDMIDecryptor *cdmidecryptor = + GST_CDMI_DECRYPTOR(object); + + GST_DEBUG_OBJECT(cdmidecryptor, "dispose"); + + if (cdmidecryptor->protectionEvent) + { + gst_event_unref(cdmidecryptor->protectionEvent); + cdmidecryptor->protectionEvent = NULL; + } + if (cdmidecryptor->sinkCaps) + { + gst_caps_unref(cdmidecryptor->sinkCaps); + cdmidecryptor->sinkCaps = NULL; + } + + g_mutex_clear(&cdmidecryptor->mutex); + g_cond_clear(&cdmidecryptor->condition); + + G_OBJECT_CLASS(gst_cdmidecryptor_parent_class)->dispose(object); +} + +/* + Append modified caps to dest, but only if it does not already exist in updated caps. + */ +static void gst_cdmicapsappendifnotduplicate(GstCaps* destCaps, + GstStructure* cap) +{ + DEBUG_FUNC(); + + bool duplicate = false; + unsigned size = gst_caps_get_size(destCaps); + for (unsigned index = 0; !duplicate && index < size; ++index) + { + GstStructure* tempCap = gst_caps_get_structure(destCaps, index); + if (gst_structure_is_equal(tempCap, cap)) + duplicate = true; + } + + if (!duplicate) + gst_caps_append_structure(destCaps, cap); + else + gst_structure_free(cap); +} + +static GstCaps * +gst_cdmidecryptor_transform_caps(GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter) +{ + DEBUG_FUNC(); + + std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + GstCDMIDecryptor *cdmidecryptor = GST_CDMI_DECRYPTOR(trans); + g_return_val_if_fail(direction != GST_PAD_UNKNOWN, NULL); + unsigned size = gst_caps_get_size(caps); + GstCaps* transformedCaps = gst_caps_new_empty(); + + GST_DEBUG_OBJECT(trans, "direction: %s, caps: %" GST_PTR_FORMAT " filter:" + " %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "src" : "sink", caps, filter); + + if(!cdmidecryptor->selectedProtection) + { + GstStructure *capstruct = gst_caps_get_structure(caps, 0); + const gchar* capsinfo = gst_structure_get_string(capstruct, "protection-system"); + if(capsinfo != NULL) + { + if(!g_strcmp0(capsinfo, PLAYREADY_UUID)) + { + cdmidecryptor->selectedProtection = PLAYREADY_UUID; + } + else if(!g_strcmp0(capsinfo, WIDEVINE_UUID)) + { + cdmidecryptor->selectedProtection = WIDEVINE_UUID; + } + else if(!g_strcmp0(capsinfo, CLEARKEY_UUID)) + { + cdmidecryptor->selectedProtection = CLEARKEY_UUID; + cdmidecryptor->ignoreSVP = true; + } + else if(!g_strcmp0(capsinfo, VERIMATRIX_UUID)) + { + cdmidecryptor->selectedProtection = VERIMATRIX_UUID; + } + } + else + { + GST_DEBUG_OBJECT(trans, "can't find protection-system field from caps: %" GST_PTR_FORMAT, caps); + } + } + + for (unsigned i = 0; i < size; ++i) + { + GstStructure* in = gst_caps_get_structure(caps, i); + GstStructure* out = NULL; + + if (direction == GST_PAD_SRC) + { + + out = gst_structure_copy(in); + /* filter out the video related fields from the up-stream caps, + because they are not relevant to the input caps of this element and + can cause caps negotiation failures with adaptive bitrate streams */ + for (int index = gst_structure_n_fields(out) - 1; index >= 0; + --index) + { + const gchar* fieldName = gst_structure_nth_field_name(out, + index); + + if (g_strcmp0(fieldName, "base-profile") + && g_strcmp0(fieldName, "codec_data") + && g_strcmp0(fieldName, "height") + && g_strcmp0(fieldName, "framerate") + && g_strcmp0(fieldName, "level") + && g_strcmp0(fieldName, "pixel-aspect-ratio") + && g_strcmp0(fieldName, "profile") + && g_strcmp0(fieldName, "rate") + && g_strcmp0(fieldName, "width")) + { + continue; + } + else + { + gst_structure_remove_field(out, fieldName); + GST_TRACE_OBJECT(cdmidecryptor, "Removing field %s", fieldName); + } + } + + gst_structure_set(out, "protection-system", G_TYPE_STRING, + cdmidecryptor->selectedProtection, "original-media-type", + G_TYPE_STRING, gst_structure_get_name(in), NULL); + + gst_structure_set_name(out, "application/x-cenc"); + + } + else + { + if (!gst_structure_has_field(in, "original-media-type")) + { + GST_DEBUG_OBJECT(trans, "No original-media-type field in caps: %" GST_PTR_FORMAT, out); + + // Check if these caps are present in supported src pad caps in case direction is GST_PAD_SINK, + // we can allow caps in this case, since plugin will let the data passthrough + gboolean found = false; + for (int j = 0; srcMimeTypes[j]; j++) + { + if (gst_structure_has_name(in, srcMimeTypes[j])) + { + found = true; + break; + } + } + if (found) + { + //From supported src type format + out = gst_structure_copy(in); + } + else + { + continue; + } + } + else + { + + out = gst_structure_copy(in); + gst_structure_set_name(out, + gst_structure_get_string(out, "original-media-type")); + + /* filter out the DRM related fields from the down-stream caps */ + for (int j = 0; j < gst_structure_n_fields(in); ++j) + { + const gchar* fieldName = gst_structure_nth_field_name(in, j); + + if (g_str_has_prefix(fieldName, "protection-system") + || g_str_has_prefix(fieldName, "original-media-type")) + { + gst_structure_remove_field(out, fieldName); + } + } + } + } + + gst_cdmicapsappendifnotduplicate(transformedCaps, out); + + if (socInterface && socInterface->IsTransformCapsRequired()) + { + if (direction == GST_PAD_SINK && !gst_caps_is_empty(transformedCaps) && OCDMGstTransformCaps) + OCDMGstTransformCaps(&transformedCaps); + } + + } + + if (filter) + { + GstCaps* intersection; + + GST_LOG_OBJECT(trans, "Using filter caps %" GST_PTR_FORMAT, filter); + intersection = gst_caps_intersect_full(transformedCaps, filter, + GST_CAPS_INTERSECT_FIRST); + gst_caps_unref(transformedCaps); + transformedCaps = intersection; + } + + GST_LOG_OBJECT(trans, "returning %" GST_PTR_FORMAT, transformedCaps); + if (direction == GST_PAD_SINK && !gst_caps_is_empty(transformedCaps)) + { + g_mutex_lock(&cdmidecryptor->mutex); + // clean up previous caps + if (cdmidecryptor->sinkCaps) + { + gst_caps_unref(cdmidecryptor->sinkCaps); + cdmidecryptor->sinkCaps = NULL; + } + cdmidecryptor->sinkCaps = gst_caps_copy(transformedCaps); + g_mutex_unlock(&cdmidecryptor->mutex); + GST_DEBUG_OBJECT(trans, "Set sinkCaps to %" GST_PTR_FORMAT, cdmidecryptor->sinkCaps); + } + return transformedCaps; +} + +#ifdef USE_OPENCDM_ADAPTER + +static GstFlowReturn gst_cdmidecryptor_transform_ip( + GstBaseTransform * trans, GstBuffer * buffer) +{ + DEBUG_FUNC(); + + std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + GstCDMIDecryptor *cdmidecryptor = + GST_CDMI_DECRYPTOR(trans); + + GstFlowReturn result = GST_FLOW_OK; + + guint subSampleCount = 0; + guint ivSize; + gboolean encrypted; + const GValue* value; + GstBuffer* ivBuffer = NULL; + GstBuffer* keyIDBuffer = NULL; + GstBuffer* subsamplesBuffer = NULL; + GstMapInfo subSamplesMap; + GstProtectionMeta* protectionMeta = NULL; + gboolean mutexLocked = FALSE; + int errorCode; + + GST_DEBUG_OBJECT(cdmidecryptor, "Processing buffer"); + + if (!buffer) + { + GST_ERROR_OBJECT(cdmidecryptor,"Failed to get writable buffer"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + protectionMeta = + reinterpret_cast(gst_buffer_get_protection_meta(buffer)); + + g_mutex_lock(&cdmidecryptor->mutex); + mutexLocked = TRUE; + if (!protectionMeta) + { + GST_DEBUG_OBJECT(cdmidecryptor, + "Failed to get GstProtection metadata from buffer %p, could be clear buffer",buffer); + if (socInterface && socInterface->IsDecryptRequired()) + { + // call decrypt even for clear samples in order to copy it to a secure buffer. If secure buffers are not supported + // decrypt() call will return without doing anything + if (cdmidecryptor->drmSession != NULL) + errorCode = cdmidecryptor->drmSession->decrypt(keyIDBuffer, ivBuffer, buffer, subSampleCount, subsamplesBuffer, cdmidecryptor->sinkCaps); + else + { /* If drmSession creation failed, then the call will be aborted here */ + result = GST_FLOW_NOT_SUPPORTED; + GST_ERROR_OBJECT(cdmidecryptor, "drmSession is **** NULL ****, returning GST_FLOW_NOT_SUPPORTED"); + } + } + goto free_resources; + } + + GST_TRACE_OBJECT(cdmidecryptor, + "Mutex acquired, stream received: %s canWait: %d", + cdmidecryptor->streamReceived ? "yes" : "no", cdmidecryptor->canWait); + + if (!cdmidecryptor->canWait + && !cdmidecryptor->streamReceived) + { + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + if (!cdmidecryptor->firstsegprocessed) + { + GST_DEBUG_OBJECT(cdmidecryptor, "\n\nWaiting for key\n"); + } + // The key might not have been received yet. Wait for it. + if (!cdmidecryptor->streamReceived) + g_cond_wait(&cdmidecryptor->condition, + &cdmidecryptor->mutex); + + if (!cdmidecryptor->streamReceived) + { + GST_ERROR_OBJECT(cdmidecryptor, + "Condition signaled from state change transition. Aborting."); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + /* If drmSession creation failed, then the call will be aborted here */ + if (cdmidecryptor->drmSession == NULL) + { + GST_ERROR_OBJECT(cdmidecryptor, "drmSession is invalid **** NULL ****. Aborting."); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + GST_TRACE_OBJECT(cdmidecryptor, "Got key event ; Proceeding with decryption"); + + if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) + { + GST_ERROR_OBJECT(cdmidecryptor, "failed to get iv_size"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", + &encrypted)) + { + GST_ERROR_OBJECT(cdmidecryptor, + "failed to get encrypted flag"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + // Unencrypted sample. + if (!ivSize || !encrypted) + goto free_resources; + + GST_TRACE_OBJECT(trans, "protection meta: %" GST_PTR_FORMAT, protectionMeta->info); + if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", + &subSampleCount)) + { + GST_ERROR_OBJECT(cdmidecryptor, + "failed to get subsample_count"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + value = gst_structure_get_value(protectionMeta->info, "iv"); + if (!value) + { + GST_ERROR_OBJECT(cdmidecryptor, "Failed to get IV for sample"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + ivBuffer = gst_value_get_buffer(value); + + value = gst_structure_get_value(protectionMeta->info, "kid"); + if (!value) { + GST_ERROR_OBJECT(cdmidecryptor, "Failed to get kid for sample"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + + keyIDBuffer = gst_value_get_buffer(value); + + if (subSampleCount) + { + value = gst_structure_get_value(protectionMeta->info, "subsamples"); + if (!value) + { + GST_ERROR_OBJECT(cdmidecryptor, + "Failed to get subsamples"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + subsamplesBuffer = gst_value_get_buffer(value); + if (!gst_buffer_map(subsamplesBuffer, &subSamplesMap, GST_MAP_READ)) + { + GST_ERROR_OBJECT(cdmidecryptor, + "Failed to map subsample buffer"); + result = GST_FLOW_NOT_SUPPORTED; + goto free_resources; + } + } + + errorCode = cdmidecryptor->drmSession->decrypt(keyIDBuffer, ivBuffer, buffer, subSampleCount, subsamplesBuffer, cdmidecryptor->sinkCaps); + + cdmidecryptor->streamEncrypted = true; + if (errorCode != 0 || cdmidecryptor->hdcpOpProtectionFailCount) + { + if(errorCode == HDCP_OUTPUT_PROTECTION_FAILURE) + { + cdmidecryptor->hdcpOpProtectionFailCount++; + } + else if(cdmidecryptor->hdcpOpProtectionFailCount) + { + if(cdmidecryptor->hdcpOpProtectionFailCount >= DECRYPT_FAILURE_THRESHOLD) { + GstStructure *newmsg = gst_structure_new("HDCPProtectionFailure", "message", G_TYPE_STRING,"HDCP Output Protection Error", NULL); + gst_element_post_message(reinterpret_cast(cdmidecryptor),gst_message_new_application (GST_OBJECT (cdmidecryptor), newmsg)); + } + cdmidecryptor->hdcpOpProtectionFailCount = 0; + } + else + { + GST_ERROR_OBJECT(cdmidecryptor, "decryption failed; error code %d\n",errorCode); + cdmidecryptor->decryptFailCount++; + if(cdmidecryptor->decryptFailCount >= DECRYPT_FAILURE_THRESHOLD && cdmidecryptor->notifyDecryptError ) + { + cdmidecryptor->notifyDecryptError = false; + GError *error; + if(errorCode == HDCP_COMPLIANCE_CHECK_FAILURE) + { + // Failure - 2.2 vs 1.4 HDCP + error = g_error_new(GST_STREAM_ERROR , GST_STREAM_ERROR_FAILED, "HDCP Compliance Check Failure"); + } + else + { + error = g_error_new(GST_STREAM_ERROR , GST_STREAM_ERROR_FAILED, "Decrypt Error: code %d", errorCode); + } + gst_element_post_message(reinterpret_cast(cdmidecryptor), gst_message_new_error (GST_OBJECT (cdmidecryptor), error, "Decrypt Failed")); + g_error_free(error); + result = GST_FLOW_ERROR; + } + goto free_resources; + } + } + else + { + cdmidecryptor->decryptFailCount = 0; + cdmidecryptor->hdcpOpProtectionFailCount = 0; + if (cdmidecryptor->mediaType == eGST_MEDIATYPE_AUDIO) + { + GST_DEBUG_OBJECT(cdmidecryptor, "Decryption successful for Audio packets"); + } + else + { + GST_DEBUG_OBJECT(cdmidecryptor, "Decryption successful for Video packets"); + } + } + + if (!cdmidecryptor->firstsegprocessed + && cdmidecryptor->sessionManager) + { + + if(cdmidecryptor->sessionManager->profileDecryptProfileCb) + { + cdmidecryptor->sessionManager->profileDecryptProfileCb(((int)cdmidecryptor->mediaType), ePROF_BEGIN, 0); + } + cdmidecryptor->firstsegprocessed = true; + } + + free_resources: + + if (!cdmidecryptor->firstsegprocessed + && cdmidecryptor->sessionManager) + { + if(!cdmidecryptor->streamEncrypted) + { + if(cdmidecryptor->sessionManager->profileDecryptProfileCb) + { + cdmidecryptor->sessionManager->profileDecryptProfileCb(((int)cdmidecryptor->mediaType), ePROF_END, 0); + } + } + else + { + if(cdmidecryptor->sessionManager->profileDecryptProfileCb) + { + cdmidecryptor->sessionManager->profileDecryptProfileCb(((int)cdmidecryptor->mediaType), ePROF_ERR, result); + } + } + cdmidecryptor->firstsegprocessed = true; + } + + if (subsamplesBuffer) + gst_buffer_unmap(subsamplesBuffer, &subSamplesMap); + + if (protectionMeta) + gst_buffer_remove_meta(buffer, + reinterpret_cast(protectionMeta)); + + if (mutexLocked) + g_mutex_unlock(&cdmidecryptor->mutex); + return result; +} +#endif // USE_OPENCDM_ADAPTER + + +/* sink event handlers */ +static gboolean gst_cdmidecryptor_sink_event(GstBaseTransform * trans, + GstEvent * event) +{ + DEBUG_FUNC(); + + GstCDMIDecryptor *cdmidecryptor = + GST_CDMI_DECRYPTOR(trans); + gboolean result = FALSE; + + switch (GST_EVENT_TYPE(event)) + { + + //GST_EVENT_PROTECTION has information about encryption and contains initData for DRM library + //This is the starting point of DRM activities. + case GST_EVENT_PROTECTION: + { + const gchar* systemId; + const gchar* origin; + unsigned char *outData = NULL; + size_t outDataLen = 0; + GstBuffer* initdatabuffer; + + if(NULL == cdmidecryptor) + { + GST_ERROR_OBJECT(cdmidecryptor, + "Invalid CDMI Decryptor Instance\n"); + result = FALSE; + break; + } + + //We need to get the sinkpad for sending upstream queries and + //getting the current pad capability ie, VIDEO or AUDIO + //in order to support tune time profiling + GstPad * sinkpad = gst_element_get_static_pad( + reinterpret_cast(cdmidecryptor), "sink"); + + if(cdmidecryptor->sessionManager == NULL) + { + const GValue *val; + GstStructure * structure = gst_structure_new("get_aamp_instance", + "aamp_instance", G_TYPE_POINTER, 0, NULL); + GstQuery *query = gst_query_new_custom(GST_QUERY_CUSTOM, structure); + gboolean res = gst_pad_peer_query(sinkpad, query); + if (res) + { + structure = (GstStructure *) gst_query_get_structure(query); + val = (gst_structure_get_value(structure, "aamp_instance")); + cdmidecryptor->sessionManager = + (DrmSessionManager*) g_value_get_pointer(val); + } + gst_query_unref(query); + } + if(cdmidecryptor->sessionManager == NULL) + { + GST_ERROR_OBJECT(cdmidecryptor, + "cdmidecryptor unable to retrieve player instance\n"); + result = FALSE; + break; + } + + + GST_DEBUG_OBJECT(cdmidecryptor, + "Received encrypted event: Proceeding to parse initData\n"); + gst_event_parse_protection(event, &systemId, &initdatabuffer, &origin); + GST_DEBUG_OBJECT(cdmidecryptor, "systemId: %s", systemId); + GST_DEBUG_OBJECT(cdmidecryptor, "origin: %s", origin); + /** If WideVine KeyID workaround is present check the systemId is clearKey **/ + if (cdmidecryptor->sessionManager->m_drmConfigParam->mIsWVKIDWorkaround){ + if(!g_str_equal(systemId, CLEARKEY_UUID) ){ + gst_event_unref(event); + result = TRUE; + break; + } + GST_DEBUG_OBJECT(cdmidecryptor, "\nWideVine KeyID workaround is present, Select KeyID from Clear Key\n"); + systemId = WIDEVINE_UUID ; + + }else{ /* else check the selected protection system */ + if (!g_str_equal(systemId, cdmidecryptor->selectedProtection)) + { + gst_event_unref(event); + result = TRUE; + break; + } + } + + GstMapInfo mapInfo; + if (!gst_buffer_map(initdatabuffer, &mapInfo, GST_MAP_READ)) + break; + GST_DEBUG_OBJECT(cdmidecryptor, "scheduling keyNeeded event"); + + if (eGST_MEDIATYPE_MANIFEST == cdmidecryptor->mediaType) + { + GstCaps* caps = gst_pad_get_current_caps(sinkpad); + GstStructure *capstruct = gst_caps_get_structure(caps, 0); + const gchar* capsinfo = gst_structure_get_string(capstruct, + "original-media-type"); + + if (!g_strcmp0(capsinfo, "audio/mpeg")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_AUDIO; + } + else if (!g_strcmp0(capsinfo, "audio/x-opus")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_AUDIO; + } + else if (!g_strcmp0(capsinfo, "audio/x-eac3") || !g_strcmp0(capsinfo, "audio/x-ac3")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_AUDIO; + } + else if (!g_strcmp0(capsinfo, "audio/x-gst-fourcc-ec_3")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_AUDIO; + } + else if (!g_strcmp0(capsinfo, "video/x-h264")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_VIDEO; + } + else if (!g_strcmp0(capsinfo, "video/x-h265")) + { + cdmidecryptor->mediaType = eGST_MEDIATYPE_VIDEO; + } + else + { + gst_caps_unref(caps); + result = false; + break; + } + gst_caps_unref(caps); + } + + if (cdmidecryptor->sessionManager->m_drmConfigParam->mIsWVKIDWorkaround){ + GST_DEBUG_OBJECT(cdmidecryptor, "\nWideVine KeyID workaround is present, Applying WideVine KID workaround\n"); + outData = ReplaceKIDPsshData(reinterpret_cast(mapInfo.data), mapInfo.size, outDataLen); + if (NULL == outData){ + GST_ERROR_OBJECT(cdmidecryptor, "\nFailed to Apply WideVine KID workaround!\n"); + break; + } + } + + cdmidecryptor->sessionManager->laprofileBeginCb(cdmidecryptor->mediaType); + g_mutex_lock(&cdmidecryptor->mutex); + GST_DEBUG_OBJECT(cdmidecryptor, "\n acquired lock for mutex\n"); + std::shared_ptr e = cdmidecryptor->sessionManager->DrmMetaDataCb(); + int err = -1; + int responseCode =-1; + if (cdmidecryptor->sessionManager->m_drmConfigParam->mIsWVKIDWorkaround){ + cdmidecryptor->drmSession = cdmidecryptor->sessionManager->createDrmSession(responseCode, err, + reinterpret_cast(systemId), eMEDIAFORMAT_DASH, + outData, outDataLen, (int)cdmidecryptor->mediaType, cdmidecryptor->player, e.get(), nullptr, false); + }else{ + cdmidecryptor->drmSession = + cdmidecryptor->sessionManager->createDrmSession(responseCode, err, + reinterpret_cast(systemId), eMEDIAFORMAT_DASH, + reinterpret_cast(mapInfo.data), + mapInfo.size, (int)cdmidecryptor->mediaType, cdmidecryptor->player, e.get(), nullptr, false); + } + if(err != -1) + { + cdmidecryptor->sessionManager->setfailureCb(e.get(),err); + } + if (NULL == cdmidecryptor->drmSession) + { +/* For Avoided setting 'streamReceived' as FALSE if createDrmSession() failed after a successful case. + * Set to FALSE is already handled on gst_cdmidecryptor_init() as part of initialization. + */ +#if 0 + cdmidecryptor->streamReceived = FALSE; +#endif /* 0 */ + + /* Need to reset canWait to skip conditional wait in "gst_cdmidecryptor_transform_ip to avoid deadlock + * scenario on drm session failure + */ + cdmidecryptor->canWait = false; + /* session manager fails to create session when state is inactive. Skip sending error event + * in this scenario. Later player will change it to active after processing SetLanguage(), or for the next Tune. + */ + if(SessionMgrState::eSESSIONMGR_ACTIVE == cdmidecryptor->sessionManager->getSessionMgrState()) + { + cdmidecryptor->sessionManager->laprofileErrorCb(e.get()); + GST_ERROR_OBJECT(cdmidecryptor,"Failed to create DRM Session\n"); + } + result = TRUE; + } + else + { + cdmidecryptor->streamReceived = TRUE; + cdmidecryptor->sessionManager->laprofileEndCb(cdmidecryptor->mediaType); + if (!cdmidecryptor->firstsegprocessed) + { + + + /** profilebegin -0, profileEnd -1 , profileError -2 */ + if(cdmidecryptor->sessionManager->profileDecryptProfileCb) + { + cdmidecryptor->sessionManager->profileDecryptProfileCb(((int)cdmidecryptor->mediaType), 0, 0); + } + } + + result = TRUE; + } + g_cond_signal(&cdmidecryptor->condition); + g_mutex_unlock(&cdmidecryptor->mutex); + GST_DEBUG_OBJECT(cdmidecryptor, "\n releasing ...................... mutex\n"); + + gst_object_unref(sinkpad); + gst_buffer_unmap(initdatabuffer, &mapInfo); + gst_event_unref(event); + if(outData){ + free(outData); + outData = NULL; + } + + break; + } + default: + result = GST_BASE_TRANSFORM_CLASS( + gst_cdmidecryptor_parent_class)->sink_event(trans, + event); + break; + } + + return result; +} + +static GstStateChangeReturn gst_cdmidecryptor_changestate( + GstElement* element, GstStateChange transition) +{ + + DEBUG_FUNC(); + + std::shared_ptr socInterface = SocInterface::CreateSocInterface(); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstCDMIDecryptor* cdmidecryptor = + GST_CDMI_DECRYPTOR(element); + + switch (transition) + { + case GST_STATE_CHANGE_READY_TO_PAUSED: + GST_DEBUG_OBJECT(cdmidecryptor, "READY->PAUSED"); + g_mutex_lock(&cdmidecryptor->mutex); + cdmidecryptor->canWait = true; + g_mutex_unlock(&cdmidecryptor->mutex); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + GST_DEBUG_OBJECT(cdmidecryptor, "PAUSED->READY"); + g_mutex_lock(&cdmidecryptor->mutex); + cdmidecryptor->canWait = false; + g_cond_signal(&cdmidecryptor->condition); + g_mutex_unlock(&cdmidecryptor->mutex); + break; + case GST_STATE_CHANGE_NULL_TO_READY: + GST_DEBUG_OBJECT(cdmidecryptor, "NULL->READY"); + if (cdmidecryptor->svpCtx == NULL) + socInterface->SvpGetContext(&cdmidecryptor->svpCtx, 0); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + GST_DEBUG_OBJECT(cdmidecryptor, "READY->NULL"); + if (cdmidecryptor->svpCtx) { + socInterface->SvpFreeContext(cdmidecryptor->svpCtx); + cdmidecryptor->svpCtx = NULL; + } + break; + default: + break; + } + + ret = + GST_ELEMENT_CLASS(gst_cdmidecryptor_parent_class)->change_state( + element, transition); + return ret; +} + +static void gst_cdmidecryptor_set_property(GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + DEBUG_FUNC(); + + GstCDMIDecryptor* cdmidecryptor = + GST_CDMI_DECRYPTOR(object); + switch (prop_id) + { + case PROP_DRM_SESSION_MANAGER: + GST_OBJECT_LOCK(cdmidecryptor); + cdmidecryptor->player = + (DrmCallbacks*) g_value_get_pointer(value); + GST_DEBUG_OBJECT(cdmidecryptor, + "Received player instance from appsrc\n"); + GST_OBJECT_UNLOCK(cdmidecryptor); + break; + case PROP_PLAYER: + GST_OBJECT_LOCK(cdmidecryptor); + cdmidecryptor->sessionManager = + (DrmSessionManager*)g_value_get_pointer(value); + GST_DEBUG_OBJECT(cdmidecryptor, + "Received DRM session manager"); + GST_OBJECT_UNLOCK(cdmidecryptor); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static gboolean gst_cdmidecryptor_accept_caps(GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps) +{ + gboolean ret = TRUE; + GST_DEBUG_OBJECT (trans, "received accept caps with direction: %s caps: %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "src" : "sink", caps); + + GstCaps *allowedCaps = NULL; + + if (direction == GST_PAD_SINK) + { + allowedCaps = gst_pad_query_caps(trans->sinkpad, caps); + } + else + { + allowedCaps = gst_pad_query_caps(trans->srcpad, caps); + } + + if (!allowedCaps) + { + GST_ERROR_OBJECT(trans, "Error while query caps on %s pad of plugin with filter caps: %" GST_PTR_FORMAT, (direction == GST_PAD_SRC) ? "src" : "sink", caps); + ret = FALSE; + } + else + { + GST_DEBUG_OBJECT(trans, "Allowed caps: %" GST_PTR_FORMAT, allowedCaps); + ret = gst_caps_is_subset(caps, allowedCaps); + gst_caps_unref(allowedCaps); + } + + // Check if these are same as src pad caps in case direction is GST_PAD_SINK, + // we can let it through in this case + if (ret == FALSE && direction == GST_PAD_SINK) + { + guint size = gst_caps_get_size(caps); + for (guint i = 0; i < size; i++) + { + GstStructure* inCaps = gst_caps_get_structure(caps, i); + for (int j = 0; srcMimeTypes[j]; j++) + { + if (gst_structure_has_name(inCaps, srcMimeTypes[j])) + { + GST_DEBUG_OBJECT(trans, "found the requested caps in supported src mime types (type:%s), respond as supported!", srcMimeTypes[j]); + ret = TRUE; + break; + } + } + } + } + GST_DEBUG_OBJECT(trans, "Return from accept_caps: %d", ret); + return ret; +} +#endif diff --git a/middleware/gst-plugins/drm/gst/gstcdmidecryptor.h b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.h new file mode 100644 index 000000000..aeaff2078 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstcdmidecryptor.h @@ -0,0 +1,88 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + + +#ifndef _GST_CDMIDECRYPTOR_H_ +#define _GST_CDMIDECRYPTOR_H_ + + +#include +#include +#include "DrmSessionManager.h" +#include "DrmCallbacks.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CDMI_DECRYPTOR (gst_cdmidecryptor_get_type()) +#define GST_CDMI_DECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_CDMI_DECRYPTOR, GstCDMIDecryptor)) +#define GST_CDMI_DECRYPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CDMI_DECRYPTOR, GstCDMIDecryptorClass)) +#define GST_IS_CDMI_DECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_CDMI_DECRYPTOR)) +#define GST_IS_CDMI_DECRYPTOR_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CDMI_DECRYPTOR)) + +typedef struct _GstCDMIDecryptor GstCDMIDecryptor; +typedef struct _GstCDMIDecryptorClass GstCDMIDecryptorClass; + +/** + * @struct _GstCDMIDecryptor + * @brief GstElement structure override for CDMI decryptor + */ +struct _GstCDMIDecryptor +{ + GstBaseTransform base_cdmidecryptor; + class DrmSessionManager* sessionManager; + class DrmSession* drmSession; + class DrmCallbacks * player; + gboolean streamReceived; + gboolean canWait; + gboolean firstsegprocessed; + GstMediaType mediaType; + + GMutex mutex; + GCond condition; + + GstEvent* protectionEvent; + const gchar* selectedProtection; + gushort decryptFailCount; + gushort hdcpOpProtectionFailCount; + gboolean notifyDecryptError; + gboolean streamEncrypted; + gboolean ignoreSVP; //No need for svp for clearKey streams + GstCaps* sinkCaps; + //GstBuffer* initDataBuffer; + void* svpCtx; +}; + +/** + * @struct _GstCDMIDecryptorClass + * @brief GstElementClass structure override for CDMI decryptor + */ +struct _GstCDMIDecryptorClass +{ + GstBaseTransformClass base_cdmidecryptor_class; +}; + +/** + * @brief Get type of CDMI decryptor + * @retval Type of CDMI decryptor + */ +GType gst_cdmidecryptor_get_type (void); + +G_END_DECLS + +#endif diff --git a/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.cpp b/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.cpp new file mode 100644 index 000000000..5895a0b18 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.cpp @@ -0,0 +1,113 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstclearkeydecryptor.cpp + * @brief player clearkey decryptor plugin definitions + */ + +#include +#include +#include +#include "gstclearkeydecryptor.h" +//#define FUNCTION_DEBUG 1 +#ifdef FUNCTION_DEBUG +#define DEBUG_FUNC() g_warning("####### %s : %d ####\n", __FUNCTION__, __LINE__); +#else +#define DEBUG_FUNC() +#endif + +/* prototypes */ +static void gst_clearkeydecryptor_finalize(GObject*); + +/* class initialization */ +#define gst_clearkeydecryptor_parent_class parent_class +G_DEFINE_TYPE(Gstclearkeydecryptor, gst_clearkeydecryptor, GST_TYPE_CDMI_DECRYPTOR); + +GST_DEBUG_CATEGORY(gst_clearkeydecryptor_debug_category); +#define GST_CAT_DEFAULT gst_clearkeydecryptor_debug_category + + +/* pad templates */ + +static GstStaticPadTemplate gst_clearkeydecryptor_src_template = +GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264;audio/mpeg;video/x-h265;audio/x-eac3;audio/x-gst-fourcc-ec_3;audio/x-ac3")); + +static GstStaticPadTemplate gst_clearkeydecryptor_sink_template = +GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-eac3, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-ac3, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-gst-fourcc-ec_3, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" CLEARKEY_PROTECTION_SYSTEM_ID)); + +static GstStaticPadTemplate gst_clearkeydecryptor_dummy_sink_template = +GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("clearkey/x-unused")); + +/** + * @brief clearkey decryptor class initialization + * @param klass Gstreamer Class + */ +static void gst_clearkeydecryptor_class_init( + GstclearkeydecryptorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + + DEBUG_FUNC(); + + gobject_class->finalize = gst_clearkeydecryptor_finalize; + + /* Setting up pads and setting metadata should be moved to + base_class_init if you intend to subclass this class. */ + gst_element_class_add_static_pad_template(elementClass, &gst_clearkeydecryptor_src_template); + gst_element_class_add_static_pad_template(elementClass, &gst_clearkeydecryptor_sink_template); + + gst_element_class_set_static_metadata(elementClass, + "Decrypt ClearKey encrypted contents", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts streams encrypted using ClearKey Encryption.", + "comcast"); +} + +/** + * @brief ClearKey decryptor element initialization + * @param clearkeydecryptor clearkey decryptor element pointer + */ +static void gst_clearkeydecryptor_init(Gstclearkeydecryptor *clearkeydecryptor) +{ + DEBUG_FUNC(); +} + + +/** + * @brief clearkey decryptor element termination + * @param object clearkey decryptor element pointer + */ +static void gst_clearkeydecryptor_finalize(GObject * object) +{ + DEBUG_FUNC(); + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + + diff --git a/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.h b/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.h new file mode 100644 index 000000000..559913d54 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstclearkeydecryptor.h @@ -0,0 +1,82 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstclearkeydecryptor.h + * @brief clear key decryptor plugin declarations + */ + + +#ifndef GSTCLEARKEYDECRYPTOR_H_ +#define GSTCLEARKEYDECRYPTOR_H_ + + +#include +#include +#include "DrmSessionManager.h" + +#include "gstcdmidecryptor.h" // For base gobject + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameCK = "clearkeydecryptor"; + +G_BEGIN_DECLS + +#define CLEARKEY_PROTECTION_SYSTEM_ID "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" + +#define GST_TYPE_CLEARKEYDECRYPTOR (gst_clearkeydecryptor_get_type()) +#define GST_CLEARKEYDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_CLEARKEYDECRYPTOR, Gstclearkeydecryptor)) +#define GST_CLEARKEYDECRYPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_CLEARKEYDECRYPTOR, GstclearkeydecryptorClass)) +#define GST_IS_CLEARKEYDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_CLEARKEYDECRYPTOR)) +#define GST_IS_CLEARKEYDECRYPTOR_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_CLEARKEYDECRYPTOR)) + +typedef struct _Gstclearkeydecryptor Gstclearkeydecryptor; +typedef struct _GstclearkeydecryptorClass GstclearkeydecryptorClass; + +/** + * @struct _Gstclearkeydecryptor + * @brief GstElement structure override for clearkey decryptor + */ +struct _Gstclearkeydecryptor +{ + GstCDMIDecryptor parent; +}; + +/** + * @struct _GstclearkeydecryptorClass + * @brief GstElementClass structure override for clearkey decryptor + */ +struct _GstclearkeydecryptorClass +{ + GstCDMIDecryptorClass parentClass; +}; + + +/** + * @brief Get type of clearkey decryptor + * @retval Type of clearkey decryptor + */ +GType gst_clearkeydecryptor_get_type (void); + +G_END_DECLS + + +#endif /* GSTCLEARKEYDECRYPTOR_H_ */ diff --git a/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.cpp b/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.cpp new file mode 100644 index 000000000..d6a21cfc2 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.cpp @@ -0,0 +1,110 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstplayreadydecryptor.cpp + * @brief Playready decryptor plugin definitions + */ + +#include +#include +#include +#include "gstplayreadydecryptor.h" +//#define FUNCTION_DEBUG 1 +#ifdef FUNCTION_DEBUG +#define DEBUG_FUNC() g_warning("####### %s : %d ####\n", __FUNCTION__, __LINE__); +#else +#define DEBUG_FUNC() +#endif + +/* prototypes */ +static void gst_playreadydecryptor_finalize(GObject*); + +/* class initialization */ +#define gst_playreadydecryptor_parent_class parent_class +G_DEFINE_TYPE(Gstplayreadydecryptor, gst_playreadydecryptor, GST_TYPE_CDMI_DECRYPTOR); + +GST_DEBUG_CATEGORY(gst_playreadydecryptor_debug_category); +#define GST_CAT_DEFAULT gst_playreadydecryptor_debug_category + + +/* pad templates */ + +static GstStaticPadTemplate gst_playreadydecryptor_src_template = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264;video/x-h264(memory:SecMem);audio/mpeg;video/x-h265;video/x-h265(memory:SecMem);audio/x-eac3;audio/x-gst-fourcc-ec_3;audio/x-ac3")); + +static GstStaticPadTemplate gst_playreadydecryptor_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-eac3, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-ac3, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-gst-fourcc-ec_3, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" PLAYREADY_PROTECTION_SYSTEM_ID)); + +static GstStaticPadTemplate gst_playreadydecryptor_dummy_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("playready/x-unused")); + +/** + * @brief Playready decryptor class initialization + * @param klass Gstreamer Class + */ +static void gst_playreadydecryptor_class_init( + GstplayreadydecryptorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + + DEBUG_FUNC(); + + gobject_class->finalize = gst_playreadydecryptor_finalize; + + /* Setting up pads and setting metadata should be moved to + base_class_init if you intend to subclass this class. */ + gst_element_class_add_static_pad_template(elementClass, &gst_playreadydecryptor_src_template); + gst_element_class_add_static_pad_template(elementClass, &gst_playreadydecryptor_sink_template); + + gst_element_class_set_static_metadata(elementClass, + "Decrypt PlayReady encrypted contents", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts streams encrypted using PlayReady Encryption.", + "Comcast"); +} + +/** + * @brief Playready decryptor element initialization + * @param playreadydecryptor playready decryptor element pointer + */ +static void gst_playreadydecryptor_init(Gstplayreadydecryptor *playreadydecryptor) +{ + DEBUG_FUNC(); +} + +/** + * @brief Playready decryptor element termination + * @param object playready decryptor element pointer + */ +static void gst_playreadydecryptor_finalize(GObject * object) +{ + DEBUG_FUNC(); + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} diff --git a/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.h b/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.h new file mode 100644 index 000000000..d49db76ea --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstplayreadydecryptor.h @@ -0,0 +1,80 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstplayreadydecryptor.h + * @brief Playready decryptor plugin declarations + */ + +#ifndef _GST_PLAYREADYDECRYPTOR_H_ +#define _GST_PLAYREADYDECRYPTOR_H_ + +#include +#include +#include "DrmSessionManager.h" + +#include "gstcdmidecryptor.h" // For base gobject + +// Declared static here because this string exists in player and gstplugin .so +// libraries This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNamePR = "playreadydecryptor"; + +G_BEGIN_DECLS + +#define PLAYREADY_PROTECTION_SYSTEM_ID "9a04f079-9840-4286-ab92-e65be0885f95" + +#define GST_TYPE_PLAYREADYDECRYPTOR (gst_playreadydecryptor_get_type()) +#define GST_PLAYREADYDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_PLAYREADYDECRYPTOR, Gstplayreadydecryptor)) +#define GST_PLAYREADYDECRYPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_PLAYREADYDECRYPTOR, GstplayreadydecryptorClass)) +#define GST_IS_PLAYREADYDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_PLAYREADYDECRYPTOR)) +#define GST_IS_PLAYREADYDECRYPTOR_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_PLAYREADYDECRYPTOR)) + +typedef struct _Gstplayreadydecryptor Gstplayreadydecryptor; +typedef struct _GstplayreadydecryptorClass GstplayreadydecryptorClass; + +/** + * @struct _Gstplayreadydecryptor + * @brief GstElement structure override for playready decryptor + */ +struct _Gstplayreadydecryptor +{ + GstCDMIDecryptor parent; +}; + +/** + * @struct _GstplayreadydecryptorClass + * @brief GstElementClass structure override for playready decryptor + */ +struct _GstplayreadydecryptorClass +{ + GstCDMIDecryptorClass parentClass; +}; + + +/** + * @brief Get type of playready decryptor + * @retval Type of playready decryptor + */ +GType gst_playreadydecryptor_get_type (void); + +G_END_DECLS + + +#endif diff --git a/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.cpp b/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.cpp new file mode 100755 index 000000000..3776a9f5a --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.cpp @@ -0,0 +1,91 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +#include +#include +#include +#include "gstverimatrixdecryptor.h" + +#define FUNCTION_DEBUG 1 +#ifdef FUNCTION_DEBUG +#define DEBUG_FUNC() g_warning("####### %s : %d ####\n", __FUNCTION__, __LINE__); +#else +#define DEBUG_FUNC() +#endif + +/* prototypes */ +static void gst_verimatrixdecryptor_finalize(GObject*); + +/* class initialization */ +#define gst_verimatrixdecryptor_parent_class parent_class +G_DEFINE_TYPE(Gstverimatrixdecryptor, gst_verimatrixdecryptor, GST_TYPE_CDMI_DECRYPTOR); + +GST_DEBUG_CATEGORY(gst_verimatrixdecryptor_debug_category); +#define GST_CAT_DEFAULT gst_verimatrixdecryptor_debug_category + + +/* pad templates */ + +static GstStaticPadTemplate gst_verimatrixdecryptor_src_template = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264;audio/mpeg;video/x-h265;audio/x-eac3;audio/x-gst-fourcc-ec_3;audio/x-ac3")); + +static GstStaticPadTemplate gst_verimatrixdecryptor_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-eac3, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-ac3, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-gst-fourcc-ec_3, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" VERIMATRIX_PROTECTION_SYSTEM_ID)); + +static GstStaticPadTemplate gst_verimatrixdecryptor_dummy_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("verimatrix/x-unused")); // unused? + +static void gst_verimatrixdecryptor_class_init(GstverimatrixdecryptorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + + DEBUG_FUNC(); + + gobject_class->finalize = gst_verimatrixdecryptor_finalize; + + gst_element_class_add_static_pad_template(elementClass, &gst_verimatrixdecryptor_src_template); + gst_element_class_add_static_pad_template(elementClass, &gst_verimatrixdecryptor_sink_template); + + gst_element_class_set_static_metadata(elementClass, + "Decrypt Verimatrix encrypted contents", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts streams encrypted using Verimatrix Encryption.", + "Comcast"); +} + +static void gst_verimatrixdecryptor_init(Gstverimatrixdecryptor *verimatrixdecryptor) +{ + DEBUG_FUNC(); +} + +static void gst_verimatrixdecryptor_finalize(GObject * object) +{ + DEBUG_FUNC(); + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} diff --git a/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.h b/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.h new file mode 100755 index 000000000..8ca472e67 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstverimatrixdecryptor.h @@ -0,0 +1,82 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstverimatrixdecryptor.h + * @brief Widevine decryptor plugin declarations + */ + + +#ifndef _GST_VERIMATRIXDECRYPTOR_H_ +#define _GST_VERIMATRIXDECRYPTOR_H_ + +#include +#include +#include "DrmSessionManager.h" + +#include "gstcdmidecryptor.h" // For base gobject + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameVMX = "verimatrixdecryptor"; + +G_BEGIN_DECLS + +#define VERIMATRIX_PROTECTION_SYSTEM_ID "9a27dd82-fde2-4725-8cbc-4234aa06ec09" + +#define GST_TYPE_VERIMATRIXDECRYPTOR (gst_verimatrixdecryptor_get_type()) +#define GST_VERIMATRIXDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_VERIMATRIXDECRYPTOR, Gstverimatrixdecryptor)) +#define GST_VERIMATRIXDECRYPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_VERIMATRIXDECRYPTOR, GstverimatrixdecryptorClass)) +#define GST_IS_VERIMATRIXDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_VERIMATRIXDECRYPTOR)) +#define GST_IS_VERIMATRIXDECRYPTOR_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_VERIMATRIXDECRYPTOR)) + +typedef struct _Gstverimatrixdecryptor Gstverimatrixdecryptor; +typedef struct _GstverimatrixdecryptorClass GstverimatrixdecryptorClass; +//typedef struct _GstverimatrixdecryptorPrivate GstverimatrixdecryptorPrivate; + +/** + * @struct _Gstverimatrixdecryptor + * @brief GstElement structure override for Widevine decryptor + */ +struct _Gstverimatrixdecryptor +{ + GstCDMIDecryptor parent; +// GstverimatrixdecryptorPrivate priv; +}; + +/** + * @struct _GstverimatrixdecryptorClass + * @brief GstElementClass structure override for Widevine decryptor + */ +struct _GstverimatrixdecryptorClass +{ + GstCDMIDecryptorClass parentClass; +}; + +/** + * @brief Get type of Verimatrix decryptor + * @retval Type of Verimatrix decryptor + */ +GType gst_verimatrixdecryptor_get_type (void); + +G_END_DECLS + + +#endif diff --git a/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.cpp b/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.cpp new file mode 100644 index 000000000..7e8db0a83 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.cpp @@ -0,0 +1,98 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ +/** + * @file gstwidevinedecryptor.cpp + * @brief widevine decryptor plugin definitions + */ +#include +#include +#include +#include "gstwidevinedecryptor.h" + +#define FUNCTION_DEBUG 1 +#ifdef FUNCTION_DEBUG +#define DEBUG_FUNC() g_warning("####### %s : %d ####\n", __FUNCTION__, __LINE__); +#else +#define DEBUG_FUNC() +#endif + +/* prototypes */ +static void gst_widevinedecryptor_finalize(GObject*); + +/* class initialization */ +#define gst_widevinedecryptor_parent_class parent_class +G_DEFINE_TYPE(Gstwidevinedecryptor, gst_widevinedecryptor, GST_TYPE_CDMI_DECRYPTOR); + +GST_DEBUG_CATEGORY(gst_widevinedecryptor_debug_category); +#define GST_CAT_DEFAULT gst_widevinedecryptor_debug_category + + +/* pad templates */ + +static GstStaticPadTemplate gst_widevinedecryptor_src_template = + GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264;video/x-h264(memory:SecMem);audio/mpeg;video/x-h265;video/x-h265(memory:SecMem);audio/x-eac3;audio/x-gst-fourcc-ec_3;audio/x-ac3;audio/x-opus")); + +static GstStaticPadTemplate gst_widevinedecryptor_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-cenc, original-media-type=(string)video/x-h264, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)video/x-h265, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-eac3, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-ac3, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-opus, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/x-gst-fourcc-ec_3, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, protection-system=(string)" WIDEVINE_PROTECTION_SYSTEM_ID)); + +static GstStaticPadTemplate gst_widevinedecryptor_dummy_sink_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS("widevine/x-unused")); // unused? + + +static void gst_widevinedecryptor_class_init(GstwidevinedecryptorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(klass); + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + + DEBUG_FUNC(); + + gobject_class->finalize = gst_widevinedecryptor_finalize; + + /* Setting up pads and setting metadata should be moved to + base_class_init if you intend to subclass this class. */ + gst_element_class_add_static_pad_template(elementClass, &gst_widevinedecryptor_src_template); + gst_element_class_add_static_pad_template(elementClass, &gst_widevinedecryptor_sink_template); + + gst_element_class_set_static_metadata(elementClass, + "Decrypt Widevine encrypted contents", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts streams encrypted using Widevine Encryption.", + "Comcast"); +} + +static void gst_widevinedecryptor_init(Gstwidevinedecryptor *widevinedecryptor) +{ + DEBUG_FUNC(); +} + +static void gst_widevinedecryptor_finalize(GObject * object) +{ + DEBUG_FUNC(); + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} diff --git a/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.h b/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.h new file mode 100644 index 000000000..961f4eee8 --- /dev/null +++ b/middleware/gst-plugins/drm/gst/gstwidevinedecryptor.h @@ -0,0 +1,82 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstwidevinedecryptor.h + * @brief Widevine decryptor plugin declarations + */ + + +#ifndef _GST_WIDEVINEDECRYPTOR_H_ +#define _GST_WIDEVINEDECRYPTOR_H_ + +#include +#include +#include "DrmSessionManager.h" + +#include "gstcdmidecryptor.h" // For base gobject + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameWV = "widevinedecryptor"; + +G_BEGIN_DECLS + +#define WIDEVINE_PROTECTION_SYSTEM_ID "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" + +#define GST_TYPE_WIDEVINEDECRYPTOR (gst_widevinedecryptor_get_type()) +#define GST_WIDEVINEDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_WIDEVINEDECRYPTOR, Gstwidevinedecryptor)) +#define GST_WIDEVINEDECRYPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_WIDEVINEDECRYPTOR, GstwidevinedecryptorClass)) +#define GST_IS_WIDEVINEDECRYPTOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_WIDEVINEDECRYPTOR)) +#define GST_IS_WIDEVINEDECRYPTOR_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_WIDEVINEDECRYPTOR)) + +typedef struct _Gstwidevinedecryptor Gstwidevinedecryptor; +typedef struct _GstwidevinedecryptorClass GstwidevinedecryptorClass; +//typedef struct _GstwidevinedecryptorPrivate GstwidevinedecryptorPrivate; + +/** + * @struct _Gstwidevinedecryptor + * @brief GstElement structure override for Widevine decryptor + */ +struct _Gstwidevinedecryptor +{ + GstCDMIDecryptor parent; +// GstwidevinedecryptorPrivate priv; +}; + +/** + * @struct _GstwidevinedecryptorClass + * @brief GstElementClass structure override for Widevine decryptor + */ +struct _GstwidevinedecryptorClass +{ + GstCDMIDecryptorClass parentClass; +}; + +/** + * @brief Get type of Widevine decryptor + * @retval Type of Widevine decryptor + */ +GType gst_widevinedecryptor_get_type (void); + +G_END_DECLS + + +#endif diff --git a/middleware/gst-plugins/gst_subtec/CMakeLists.txt b/middleware/gst-plugins/gst_subtec/CMakeLists.txt new file mode 100644 index 000000000..7f2e439b0 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/CMakeLists.txt @@ -0,0 +1,82 @@ +# +# Copyright (C) 2022 RDK Management +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; +# version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +cmake_minimum_required (VERSION 3.5) +project (gst_subtec) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +find_package(PkgConfig REQUIRED) + +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) +pkg_check_modules(GSTREAMERBASE REQUIRED gstreamer-app-1.0) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GSTREAMERBASE_INCLUDE_DIRS}) + +if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../middleware/subtec/libsubtec) + link_directories(${GSTREAMERBASE_LIBRARY_DIRS} ${GSTREAMER_LIBRARY_DIRS}) + link_directories(${CMAKE_INSTALL_PREFIX}) +endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -DSUBTEC_PACKET_DEBUG") + +set(GSTSUBTEC_DEPENDENCIES ${GSTREAMERBASE_LIBRARIES} ${GSTREAMER_LIBRARIES}) + +add_library(gstsubtecsink SHARED gstsubtecsink.cpp) + +message(STATUS "GSTSUBTEC_DEPENDENCIES: ${GSTSUBTEC_DEPENDENCIES}") +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../middleware/subtec/libsubtec) +target_link_libraries(gstsubtecsink PRIVATE subtec ${GSTSUBTEC_DEPENDENCIES}) + +add_library(gstsubtecbin SHARED gstsubtecbin.cpp) + +target_link_libraries(gstsubtecbin ${GSTSUBTEC_DEPENDENCIES}) + +add_library(gstsubtecmp4transform SHARED gstsubtecmp4transform.cpp) + +target_link_libraries(gstsubtecmp4transform ${GSTSUBTEC_DEPENDENCIES}) + +add_library(gstvipertransform SHARED gstvipertransform.cpp) + +target_link_libraries(gstvipertransform ${GSTSUBTEC_DEPENDENCIES}) + +install (TARGETS gstsubtecsink + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib/gstreamer-1.0 + ARCHIVE DESTINATION lib/gstreamer-1.0 +) + +install (TARGETS gstsubtecbin + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib/gstreamer-1.0 + ARCHIVE DESTINATION lib/gstreamer-1.0 +) + +install (TARGETS gstsubtecmp4transform + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib/gstreamer-1.0 + ARCHIVE DESTINATION lib/gstreamer-1.0 +) + +install (TARGETS gstvipertransform + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib/gstreamer-1.0 + ARCHIVE DESTINATION lib/gstreamer-1.0 +) diff --git a/middleware/gst-plugins/gst_subtec/Readme.md b/middleware/gst-plugins/gst_subtec/Readme.md new file mode 100644 index 000000000..2cfb01a67 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/Readme.md @@ -0,0 +1,23 @@ +# Subtec GStreamer Plugin for sending subtitles to Subtec + +Subtitles can be sent from PLAYER directly to the Subtec Application or via this GStreamer plugin. +Sending via the GStreamer plugin is enabled in PLAYER through config "gstSubtecEnabled", which is +disabled by default in the PLAYER simulator builds. + +## Building and installing the Subtec Gstreamer Plugin for PLAYER Simulator + +### For Ubuntu + +From the player(a_mp) folder run: + +cmake -Bbuild/gst_subtec -H./Linux/gst-plugins/gst_subtec -DCMAKE_INSTALL_PREFIX=./Linux -DCMAKE_PLATFORM_UBUNTU=1 +make -C build/gst_subtec +make -C build/gst_subtec install + +### For macOS + +From the player(a_mp) folder run: + +PKG_CONFIG_PATH=/Library/Frameworks/GStreamer.framework/Versions/1.0/lib/pkgconfig/ cmake -Bgst_subtec -H./.libs/gst-plugins/gst_subtec -DCMAKE_INSTALL_PREFIX=./Debug +make -C build/gst_subtec +make -C build/gst_subtec install diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecbin.cpp b/middleware/gst-plugins/gst_subtec/gstsubtecbin.cpp new file mode 100644 index 000000000..c0d007628 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecbin.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:element-gstsubtecbin + * + * Plugs the subtec pipeline - adding mp4transform and/or vipertransform + * elements as required + */ + +#include +#include "gstsubtecbin.h" + +GST_DEBUG_CATEGORY_STATIC (gst_subtecbin_debug_category); +#define GST_CAT_DEFAULT gst_subtecbin_debug_category + +static void gst_subtecbin_dispose (GObject * object); +static void gst_subtecbin_finalize (GObject * object); +static void gst_subtecbin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * spec); +static void gst_subtecbin_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static bool try_lock_element_state (GstSubtecBin * subtecbin); + + +static GstStaticPadTemplate gst_subtecbin_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/ttml+xml; text/vtt; application/mp4;") + ); + +/* class initialization */ +G_DEFINE_TYPE_WITH_CODE (GstSubtecBin, gst_subtecbin, GST_TYPE_BIN, + GST_DEBUG_CATEGORY_INIT (gst_subtecbin_debug_category, "subtecbin", 0, + "debug category for subtecbin element")); + +enum +{ + PROP_0, + PROP_MUTE, + PROP_NO_EOS, + PROP_ASYNC, + PROP_SYNC, + PROP_SUBTEC_SOCKET, + PROP_PTS_OFFSET, + PROP_ATTRIBUTE_VALUES +}; + +static void +gst_subtecbin_class_init (GstSubtecBinClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBinClass *base_sink_class = GST_BIN_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + GST_LOG("gst_subtecbin_class_init"); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS(klass), + &gst_subtecbin_sink_template); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS(klass), + "OOB Subtec data sink", "Sink/Parser/Subtitle", "Packs TTML or WebVTT data into SubTtxRend APP suitable packets", "Comcast"); + + gobject_class->set_property = gst_subtecbin_set_property; + gobject_class->get_property = gst_subtecbin_get_property; + + g_object_class_install_property(gobject_class, + PROP_MUTE, + g_param_spec_boolean("mute", "Mute", "Mutes the subtitles", + FALSE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_NO_EOS, + g_param_spec_boolean("no-eos", "No EOS", "Eats the EOS and stops the stream exiting", + FALSE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_ASYNC, + g_param_spec_boolean("async", "Async", "Sets async on children (require preroll before returning async_done)", + TRUE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_SYNC, + g_param_spec_boolean("sync", "Sync", "Sets sync on children (synchronize render on clock)", + TRUE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_SUBTEC_SOCKET, + g_param_spec_string("subtec-socket", "Subtec socket", "Alternative subtec socket (default /var/run/subttx/pes_data_main)", + NULL, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_PTS_OFFSET, + g_param_spec_uint64("pts-offset", "PTS offset", "PTS offset for mpeg-2 ts HLS streams", + 0, G_MAXUINT64, 0, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_ATTRIBUTE_VALUES, + g_param_spec_boxed("attribute-values", "Attribute values", "GstStructure specifying attributes", + GST_TYPE_STRUCTURE, + (GParamFlags)(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS))); + + gobject_class->dispose = gst_subtecbin_dispose; + gobject_class->finalize = gst_subtecbin_finalize; +} + +static void gst_subtecbin_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSubtecBin *subtecbin = GST_SUBTECBIN (object); + + GST_DEBUG_OBJECT (subtecbin, "set_property %d", prop_id); + + switch (prop_id) { + case PROP_MUTE: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "mute", value); + subtecbin->mute = g_value_get_boolean(value); + break; + case PROP_NO_EOS: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "no-eos", value); + subtecbin->no_eos = g_value_get_boolean(value); + break; + case PROP_ASYNC: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "async", value); + subtecbin->async = g_value_get_boolean(value); + break; + case PROP_SYNC: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "sync", value); + subtecbin->sync = g_value_get_boolean(value); + break; + case PROP_SUBTEC_SOCKET: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "subtec-socket", value); + subtecbin->subtec_socket = g_value_get_string(value); + break; + case PROP_PTS_OFFSET: + { + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "pts-offset", value); + subtecbin->pts_offset = g_value_get_uint64(value); + } + break; + case PROP_ATTRIBUTE_VALUES: + if (subtecbin->sink) + g_object_set_property(G_OBJECT(subtecbin->sink), "attribute-values", value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +void +gst_subtecbin_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstSubtecBin *subtecbin = GST_SUBTECBIN (object); + + GST_DEBUG_OBJECT (subtecbin, "get_property %d", property_id); + + switch (property_id) { + case PROP_MUTE: + g_value_set_boolean(value, subtecbin->mute); + break; + case PROP_NO_EOS: + g_value_set_boolean(value, subtecbin->no_eos); + break; + case PROP_ASYNC: + g_value_set_boolean(value, subtecbin->async); + break; + case PROP_SYNC: + g_value_set_boolean(value, subtecbin->sync); + break; + case PROP_SUBTEC_SOCKET: + g_value_set_string(value, subtecbin->subtec_socket.c_str()); + break; + case PROP_PTS_OFFSET: + g_value_set_uint64(value, subtecbin->pts_offset); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static bool +try_lock_element_state (GstSubtecBin * subtecbin) +{ + guint count = 10; + bool locked = GST_STATE_TRYLOCK(GST_ELEMENT(subtecbin)); + while (!locked && (--count > 0)) + { + g_usleep(10000); //10ms wait + locked = GST_STATE_TRYLOCK(GST_ELEMENT(subtecbin)); + } + return locked; +} + +static void +type_found (GstElement * typefind, guint probability, + GstCaps * caps, GstSubtecBin * subtecbin) +{ + GST_DEBUG_OBJECT (subtecbin, "subs_typefind found caps %" GST_PTR_FORMAT, caps); + subtecbin->sink = gst_element_factory_make("subtecsink", NULL); + + g_return_if_fail (subtecbin->sink != NULL); + + g_object_set(G_OBJECT(subtecbin->sink), "mute", subtecbin->mute, NULL); + g_object_set(G_OBJECT(subtecbin->sink), "no-eos", subtecbin->no_eos, NULL); + g_object_set(G_OBJECT(subtecbin->sink), "async", subtecbin->async, NULL); + g_object_set(G_OBJECT(subtecbin->sink), "sync", subtecbin->sync, NULL); + g_object_set(G_OBJECT(subtecbin->sink), "pts-offset", subtecbin->pts_offset, NULL); + + GstElementFactory *ttml_transform_factory = NULL; + GList *sub_parser_factories = gst_element_factory_list_filter (gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_MEDIA_SUBTITLE, + GST_RANK_PRIMARY), + caps, + GST_PAD_SINK, + TRUE); + GList *tmp = sub_parser_factories; + + for (; tmp; tmp = tmp->next) { + GstElementFactory *factory = (GstElementFactory *) tmp->data; + GST_DEBUG_OBJECT(subtecbin, "factory name %s can sink caps %d", gst_plugin_feature_get_name ((GstPluginFeature *) factory), gst_element_factory_can_sink_all_caps(factory, caps)); + if (gst_element_factory_can_sink_all_caps(factory, caps)) + { + if (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DEMUXER)) + { + subtecbin->demux = gst_element_factory_create(factory, NULL); + + GstPad *demux_pad = gst_element_get_static_pad(subtecbin->demux, "src"); + GstCaps *demux_caps = gst_pad_get_pad_template_caps(demux_pad); + + GST_DEBUG_OBJECT(subtecbin, "seek allowed caps %" GST_PTR_FORMAT, demux_caps); + auto ttml_formatter_factory = gst_element_factory_find("vipertransform"); + if (gst_element_factory_can_sink_all_caps(ttml_formatter_factory, demux_caps)) + { + subtecbin->formatter = gst_element_factory_make("vipertransform", NULL); + } + } + } + } + gst_plugin_feature_list_free(sub_parser_factories); + + GList *chain = NULL; + + if (subtecbin->demux) + { + GST_DEBUG_OBJECT(subtecbin, "demuxer"); + chain = g_list_append(chain, gst_object_ref(subtecbin->demux)); + } + if (subtecbin->formatter) + { + GST_DEBUG_OBJECT(subtecbin, "formatter"); + chain = g_list_append(chain, gst_object_ref(subtecbin->formatter)); + } + if (subtecbin->sink) + { + GST_DEBUG_OBJECT(subtecbin, "sink"); + chain = g_list_append(chain, gst_object_ref(subtecbin->sink)); + } + + tmp = chain; + + // Try to acquire the element stream mutex before calling gst_element_sync_state_with_parent() + // If an attempt to set the pipeline state to NULL occurs during startup, this can lead to + // a deadlock between pad and element mutexes + if (!try_lock_element_state(subtecbin)) + { + GST_WARNING("%s unable to acquire element state lock!", __func__); + goto lock_failed; + } + + gst_element_sync_state_with_parent(GST_ELEMENT(subtecbin)); + for(; tmp; tmp = tmp->next) + { + GstElement *current = GST_ELEMENT(tmp->data); + GST_DEBUG_OBJECT(subtecbin, "element %s", gst_element_get_name(current)); + gst_bin_add(GST_BIN(subtecbin), current); + if (tmp->prev) + gst_element_link(GST_ELEMENT(tmp->prev->data), current); + else + gst_element_link(typefind, current); + gst_object_unref(current); + } + + tmp = chain; + for(; tmp; tmp = tmp->next) + { + gst_element_sync_state_with_parent(GST_ELEMENT(tmp->data)); + } + + GST_STATE_UNLOCK(GST_ELEMENT(subtecbin)); +lock_failed: + + if (chain) + { + g_list_free(chain); + } +} + +static void +gst_subtecbin_init (GstSubtecBin *subtecbin) +{ + GST_DEBUG_OBJECT(subtecbin, "init1"); + + subtecbin->typefind = gst_element_factory_make("typefind", "subs_typefind"); + gst_bin_add(GST_BIN(subtecbin), subtecbin->typefind); + gst_element_add_pad (GST_ELEMENT (subtecbin), gst_ghost_pad_new("sink", gst_element_get_static_pad(subtecbin->typefind, "sink"))); + gst_element_sync_state_with_parent(GST_ELEMENT(subtecbin)); + + g_signal_connect(subtecbin->typefind, "have-type", G_CALLBACK(type_found), subtecbin); +} + +void +gst_subtecbin_dispose (GObject * object) +{ + GstSubtecBin *subtecbin = GST_SUBTECBIN (object); + + GST_DEBUG_OBJECT (subtecbin, "dispose"); + + /* clean up as possible. may be called multiple times */ +} + +void +gst_subtecbin_finalize (GObject * object) +{ + GstSubtecBin *subtecbin = GST_SUBTECBIN (object); + + GST_DEBUG_OBJECT (subtecbin, "finalize"); + + /* clean up object here */ +} + + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "subtecbin", GST_RANK_PRIMARY, + GST_TYPE_SUBTECBIN); +} + +#ifndef VERSION +#define VERSION "0.1.0" +#endif +#ifndef PACKAGE +#define PACKAGE "MWGstreamerPlugins" +#endif +#ifndef PACKAGE_NAME +#define PACKAGE_NAME "MWGstreamerPlugins" +#endif +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "http://comcast.com/" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + subtecbin, + "SubTtxRend autoplug bin", + plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecbin.h b/middleware/gst-plugins/gst_subtec/gstsubtecbin.h new file mode 100644 index 000000000..36ce349f1 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecbin.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GST_SUBTECBIN_H_ +#define _GST_SUBTECBIN_H_ + +#include +#include + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameSubtecBin = "subtecbin"; + +G_BEGIN_DECLS + +#define GST_TYPE_SUBTECBIN (gst_subtecbin_get_type()) +#define GST_SUBTECBIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SUBTECBIN,GstSubtecBin)) +#define GST_SUBTECBIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SUBTECBIN,GstSubtecBinClass)) +#define GST_IS_SUBTECBIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SUBTECBIN)) +#define GST_IS_SUBTECBIN_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SUBTECBIN)) + +typedef struct _GstSubtecBin GstSubtecBin; +typedef struct _GstSubtecBinClass GstSubtecBinClass; + +struct _GstSubtecBin +{ + GstBin parent; + GstPad *sinkpad; + GstElement *typefind; + GstElement *demux; + GstElement *formatter; + GstElement *sink; + bool no_eos = false; + bool mute = false; + bool async = true; + bool sync = true; + std::string subtec_socket{}; + guint64 pts_offset{0}; +}; + +struct _GstSubtecBinClass +{ + GstBinClass parent_class; +}; + +GType gst_subtecbin_get_type (void); + +G_END_DECLS + +#endif diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.cpp b/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.cpp new file mode 100644 index 000000000..8b2c894ab --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "gstsubtecmp4transform.h" + +#include +#include + +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_subtecmp4transform_debug_category); +#define GST_CAT_DEFAULT gst_subtecmp4transform_debug_category + +/* prototypes */ + + +static void gst_subtecmp4transform_dispose (GObject * object); +static void gst_subtecmp4transform_finalize (GObject * object); + +static GstCaps *gst_subtecmp4transform_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter); +static GstCaps *gst_subtecmp4transform_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); +static gboolean gst_subtecmp4transform_accept_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps); +static gboolean gst_subtecmp4transform_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps); +static gboolean gst_subtecmp4transform_query (GstBaseTransform * trans, + GstPadDirection direction, GstQuery * query); +static gboolean gst_subtecmp4transform_decide_allocation (GstBaseTransform * trans, + GstQuery * query); +static gboolean gst_subtecmp4transform_filter_meta (GstBaseTransform * trans, + GstQuery * query, GType api, const GstStructure * params); +static gboolean gst_subtecmp4transform_propose_allocation (GstBaseTransform * trans, + GstQuery * decide_query, GstQuery * query); +static gboolean gst_subtecmp4transform_transform_size (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps, + gsize * othersize); +static gboolean gst_subtecmp4transform_get_unit_size (GstBaseTransform * trans, + GstCaps * caps, gsize * size); +static gboolean gst_subtecmp4transform_start (GstBaseTransform * trans); +static gboolean gst_subtecmp4transform_stop (GstBaseTransform * trans); +static gboolean gst_subtecmp4transform_sink_event (GstBaseTransform * trans, + GstEvent * event); +static gboolean gst_subtecmp4transform_src_event (GstBaseTransform * trans, + GstEvent * event); +static GstFlowReturn gst_subtecmp4transform_prepare_output_buffer (GstBaseTransform * + trans, GstBuffer * input, GstBuffer ** outbuf); +static gboolean gst_subtecmp4transform_copy_metadata (GstBaseTransform * trans, + GstBuffer * input, GstBuffer * outbuf); +static gboolean gst_subtecmp4transform_transform_meta (GstBaseTransform * trans, + GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf); +static void gst_subtecmp4transform_before_transform (GstBaseTransform * trans, + GstBuffer * buffer); +static GstFlowReturn gst_subtecmp4transform_transform (GstBaseTransform * trans, + GstBuffer * inbuf, GstBuffer * outbuf); +static GstFlowReturn gst_subtecmp4transform_transform_ip (GstBaseTransform * trans, + GstBuffer * buf); + +enum +{ + PROP_0 +}; + +/* pad templates */ + +static GstStaticPadTemplate gst_subtecmp4transform_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/ttml+xml") + ); + +static GstStaticPadTemplate gst_subtecmp4transform_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/mp4") + ); + + +/* class initialization */ + +G_DEFINE_TYPE_WITH_CODE (GstSubtecMp4Transform, gst_subtecmp4transform, GST_TYPE_BASE_TRANSFORM, + GST_DEBUG_CATEGORY_INIT (gst_subtecmp4transform_debug_category, "subtecmp4transform", 0, + "debug category for subtecmp4transform element")); + +static void +gst_subtecmp4transform_class_init (GstSubtecMp4TransformClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseTransformClass *base_transform_class = GST_BASE_TRANSFORM_CLASS (klass); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS(klass), + &gst_subtecmp4transform_src_template); + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS(klass), + &gst_subtecmp4transform_sink_template); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS(klass), + "Subtec ISO MP4 demuxer", "Demuxer/Subtitle", "Pulls subtitle data from stpp box", "Comcast"); + + base_transform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_subtecmp4transform_transform_caps); + base_transform_class->transform_ip = GST_DEBUG_FUNCPTR (gst_subtecmp4transform_transform_ip); +} + +static void +gst_subtecmp4transform_init (GstSubtecMp4Transform *subtecmp4transform) +{ +} + +static GstCaps * +gst_subtecmp4transform_transform_caps (GstBaseTransform * trans, GstPadDirection direction, + GstCaps * caps, GstCaps * filter) +{ + GstSubtecMp4Transform *subtecmp4transform = GST_SUBTECMP4TRANSFORM (trans); + GstCaps *othercaps; + + static GstStaticCaps ttml_caps = + GST_STATIC_CAPS ("application/ttml+xml"); + static GstStaticCaps vtt_caps = + GST_STATIC_CAPS ("text/vtt"); + static GstStaticCaps mp4_caps = + GST_STATIC_CAPS ("application/mp4"); + + GST_DEBUG_OBJECT (subtecmp4transform, "transform_caps caps"); + GST_DEBUG_OBJECT (subtecmp4transform, "direction %s from caps %" GST_PTR_FORMAT, + direction == GST_PAD_SRC ? "src" : "sink", caps); + GST_DEBUG_OBJECT (subtecmp4transform, "filter %s NULL", filter == NULL ? "is" : "is not"); + + /* Copy other caps and modify as appropriate */ + /* This works for the simplest cases, where the transform modifies one + * or more fields in the caps structure. It does not work correctly + * if passthrough caps are preferred. */ + const GstStructure *s = gst_caps_get_structure (caps, 0); + + if (direction == GST_PAD_SRC) + { + /* transform caps going upstream */ + if (gst_structure_has_name (s, "application/ttml+xml")) + { + othercaps = gst_caps_new_empty(); + othercaps = gst_caps_merge(othercaps, gst_static_caps_get(&mp4_caps)); + /* transform caps going downstream */ + } + else + { + othercaps = gst_caps_copy(caps); + } + } + else + { + if (gst_structure_has_name (s, "application/mp4")) + { + othercaps = gst_caps_new_empty(); + othercaps = gst_caps_merge(othercaps, gst_static_caps_get(&ttml_caps)); + const GValue* viper_hd = gst_structure_get_value(s, "viper_ttml_format"); + if (NULL != viper_hd) + gst_caps_set_value(othercaps, "viper_ttml_format", viper_hd); + + /* transform caps going downstream */ + } + else if (gst_structure_has_name (s, "text/vtt")) + { + othercaps = gst_caps_new_empty(); + othercaps = gst_caps_merge(othercaps, gst_static_caps_get(&vtt_caps)); + /* transform caps going downstream */ + } + else + { + othercaps = gst_caps_copy(caps); + } + } + + GST_DEBUG_OBJECT (subtecmp4transform, "othercaps %" GST_PTR_FORMAT, othercaps); + + if (filter) { + GstCaps *intersect; + + intersect = gst_caps_intersect (othercaps, filter); + gst_caps_unref (othercaps); + + return intersect; + } else { + return othercaps; + } +} + +static std::uint32_t parse32(std::uint8_t *ptr, std::size_t len, std::size_t offset) +{ + if (offset + 4 > len) throw std::out_of_range("len too short"); + + std::uint32_t value = 0; + + const std::uint32_t byte0 = static_cast(ptr[offset]) & 0xFF; + const std::uint32_t byte1 = static_cast(ptr[offset + 1]) & 0xFF; + const std::uint32_t byte2 = static_cast(ptr[offset + 2]) & 0xFF; + const std::uint32_t byte3 = static_cast(ptr[offset + 3]) & 0xFF; + + value |= byte3; + value |= byte2 << 8; + value |= byte1 << 16; + value |= byte0 << 24; + + return value; +} + +static bool parseName(std::uint8_t *ptr, std::size_t len, std::size_t offset, std::string &value) +{ + if (offset + 4 > len) return false; + + const char byte0 = static_cast(ptr[offset]) & 0xFF; + const char byte1 = static_cast(ptr[offset + 1]) & 0xFF; + const char byte2 = static_cast(ptr[offset + 2]) & 0xFF; + const char byte3 = static_cast(ptr[offset + 3]) & 0xFF; + + value += byte0; + value += byte1; + value += byte2; + value += byte3; + + return true; +} + +static bool isContainer(const std::string &name) +{ + static const std::vector containerNames{"moov", "trak", "mdia", "minf", "dinf", "stbl", "mvex"}; + + for (const auto &container : containerNames) + { + if (!container.compare(name)) return true; + } + + return false; +} + +static bool printBoxes(GstSubtecMp4Transform *subtecmp4transform, uint8_t *boxes, size_t len) +{ + size_t offset = 0; + + GST_DEBUG_OBJECT (subtecmp4transform, "printBoxes: len %zu", len); + + while (offset < len) + { + std::size_t boxSize; + std::string boxName; + + try + { + boxSize = parse32(boxes, len, offset); + offset += 4; + parseName(boxes, len, offset, boxName); + } + catch (const std::out_of_range &e) + { + GST_WARNING_OBJECT (subtecmp4transform, "parse error:%s", e.what()); + return false; + } + + if (isContainer(boxName)) + { + offset += 4; + } + else + offset += boxSize - 4; + + GST_DEBUG_OBJECT (subtecmp4transform, "box name: %s size %zu offset %zu", boxName.c_str(), boxSize, offset); + } + + return true; +} + +static bool findBoxOffsetAndLength(GstSubtecMp4Transform *subtecmp4transform, uint8_t *buf, size_t len, const std::string &name, size_t &boxOffset, size_t &boxLength) +{ + size_t offset = 0; + + while (offset < len) + { + std::size_t boxSize; + std::string boxName; + + try + { + boxSize = parse32(buf, len, offset); + offset += 4; + parseName(buf, len, offset, boxName); + } + catch (const std::out_of_range &e) + { + GST_WARNING_OBJECT (subtecmp4transform, "parse error:%s", e.what()); + return false; + } + + GST_DEBUG_OBJECT (subtecmp4transform, "boxName %s name %s len %zu", boxName.c_str(), name.c_str(), boxSize); + + if (!boxName.compare(name)) + { + boxOffset = offset + 4; + boxLength = boxSize; + + return true; + } + + if (isContainer(boxName)) + { + offset += 4; + } + else + offset += boxSize - 4; + } + + return false; +} + +static GstFlowReturn +gst_subtecmp4transform_transform_ip (GstBaseTransform * trans, GstBuffer * buf) +{ + GstSubtecMp4Transform *subtecmp4transform = GST_SUBTECMP4TRANSFORM (trans); + + GST_DEBUG_OBJECT (subtecmp4transform, "transform_ip"); + + GstMapInfo map; + + gst_buffer_map(buf, &map, (GstMapFlags)GST_MAP_READWRITE); + + size_t offset = 0, length = 0; + + if (!findBoxOffsetAndLength(subtecmp4transform, reinterpret_cast(map.data), map.size, "ftyp", offset, length)) + { + if (findBoxOffsetAndLength(subtecmp4transform, reinterpret_cast(map.data), map.size, "mdat", offset, length)) + { + GST_DEBUG_OBJECT (subtecmp4transform, "offset %zu length %zu", offset, length); + memcpy(map.data, map.data + offset, length); + gst_buffer_resize(buf, 0, length); + } + } + else + { + GST_DEBUG_OBJECT (subtecmp4transform, "DROPPING FRAME"); + gst_buffer_unmap(buf, &map); + return GST_BASE_TRANSFORM_FLOW_DROPPED; + } + + gst_buffer_unmap(buf, &map); + + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + + return gst_element_register (plugin, "subtecmp4transform", GST_RANK_PRIMARY, + GST_TYPE_SUBTECMP4TRANSFORM); +} + +#ifndef VERSION +#define VERSION "0.1.0" +#endif +#ifndef PACKAGE +#define PACKAGE "MWGstreamerPlugins" +#endif +#ifndef PACKAGE_NAME +#define PACKAGE_NAME "MWGstreamerPlugins" +#endif +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "http://comcast.com/" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + subtecmp4transform, + "MP4 demuxer for subtitle packets", + plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) + diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.h b/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.h new file mode 100644 index 000000000..15359b5a4 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecmp4transform.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GST_SUBTECMP4TRANSFORM_H_ +#define _GST_SUBTECMP4TRANSFORM_H_ + +#include + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameSubtecMp4Transform = "subtecmp4transform"; + +G_BEGIN_DECLS + +#define GST_TYPE_SUBTECMP4TRANSFORM (gst_subtecmp4transform_get_type()) +#define GST_SUBTECMP4TRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SUBTECMP4TRANSFORM,GstSubtecMp4Transform)) +#define GST_SUBTECMP4TRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SUBTECMP4TRANSFORM,GstSubtecMp4TransformClass)) +#define GST_IS_SUBTECMP4TRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SUBTECMP4TRANSFORM)) +#define GST_IS_SUBTECMP4TRANSFORM_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SUBTECMP4TRANSFORM)) + +typedef struct _GstSubtecMp4Transform GstSubtecMp4Transform; +typedef struct _GstSubtecMp4TransformClass GstSubtecMp4TransformClass; + +struct _GstSubtecMp4Transform +{ + GstBaseTransform base_subtecmp4transform; + +}; + +struct _GstSubtecMp4TransformClass +{ + GstBaseTransformClass base_subtecmp4transform_class; +}; + +GType gst_subtecmp4transform_get_type (void); + +G_END_DECLS + +#endif diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecsink.cpp b/middleware/gst-plugins/gst_subtec/gstsubtecsink.cpp new file mode 100644 index 000000000..00328ea9c --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecsink.cpp @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include +#include +#include "gstsubtecsink.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_subtecsink_debug_category); +#define GST_CAT_DEFAULT gst_subtecsink_debug_category + +static GstBaseSinkClass* parentClass = nullptr; + +/* prototypes */ +static void gst_subtecsink_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_subtecsink_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_subtecsink_dispose (GObject * object); +static void gst_subtecsink_finalize (GObject * object); + +static void set_mute(GstSubtecSink *subtecsink); +static void set_attribute(GstSubtecSink *subtecsink); + +static gboolean gst_subtecsink_set_caps (GstBaseSink * sink, GstCaps * caps); +static gboolean gst_subtecsink_start (GstBaseSink * sink); +static gboolean gst_subtecsink_stop (GstBaseSink * sink); +static gboolean gst_subtecsink_query (GstBaseSink * sink, GstQuery * query); +static gboolean gst_subtecsink_event (GstBaseSink * sink, GstEvent * event); +static GstFlowReturn gst_subtecsink_render (GstBaseSink * sink, + GstBuffer * buffer); +static GstStateChangeReturn gst_subtecsink_change_state(GstElement *element, + GstStateChange transition); +static GstFlowReturn gst_subtecsink_prepare (GstBaseSink * sink, + GstBuffer * buffer); +static GstFlowReturn gst_subtecsink_preroll (GstBaseSink * sink, + GstBuffer * buffer); + +enum +{ + PROP_0, + PROP_MUTE, + PROP_NO_EOS, + PROP_SUBTEC_SOCKET, + PROP_PTS_OFFSET, + PROP_ATTRIBUTE_VALUES +}; + +enum +{ + FONT_COLOR_INDEX = 0, + BACKGROUND_COLOR_INDEX, + FONT_OPACITY_INDEX, + BACKGROUND_OPACITY_INDEX, + FONT_STYLE_INDEX, + FONT_SIZE_INDEX, + FONT_ITALIC_INDEX, + FONT_UNDERLINE_INDEX, + BORDER_TYPE_INDEX, + BORDER_COLOR_INDEX, + WIN_COLOR_INDEX, + WIN_OPACITY_INDEX, + EDGE_TYPE_INDEX, + EDGE_COLOR_INDEX +}; + +static GstStaticPadTemplate gst_subtecsink_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("application/ttml+xml; text/vtt") + ); + + +G_DEFINE_TYPE_WITH_CODE (GstSubtecSink, gst_subtecsink, GST_TYPE_BASE_SINK, + GST_DEBUG_CATEGORY_INIT (gst_subtecsink_debug_category, "subtecsink", 0, + "debug category for subtecsink element")); + +static void +gst_subtecsink_class_init (GstSubtecSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS(klass); + + parentClass = GST_BASE_SINK_CLASS(g_type_class_peek_parent(klass)); + + GST_LOG("gst_subtecsink_class_init"); + + gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS(klass), + &gst_subtecsink_sink_template); + + gst_element_class_set_static_metadata (GST_ELEMENT_CLASS(klass), + "OOB Subtec data sink", "Sink/Parser/Subtitle", "Packs TTML or WebVTT data into SubTtxRend APP suitable packets", "Comcast"); + + + gobject_class->set_property = gst_subtecsink_set_property; + gobject_class->get_property = gst_subtecsink_get_property; + gobject_class->dispose = gst_subtecsink_dispose; + gobject_class->finalize = gst_subtecsink_finalize; + + g_object_class_install_property(gobject_class, + PROP_MUTE, + g_param_spec_boolean("mute", "Mute", "Mutes the subtitles", + FALSE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_NO_EOS, + g_param_spec_boolean("no-eos", "No EOS", "Eats the EOS and stops the stream exiting", + FALSE, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_SUBTEC_SOCKET, + g_param_spec_string("subtec-socket", "Subtec socket", "Alternative subtec socket (default /var/run/subttx/pes_data_main)", + NULL, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_PTS_OFFSET, + g_param_spec_uint64("pts-offset", "PTS offset", "PTS offset for mpeg-2 ts HLS streams", + 0, G_MAXUINT64, 0, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property(gobject_class, + PROP_ATTRIBUTE_VALUES, + g_param_spec_boxed("attribute-values", "Attribute values", "GstStructure specifying attributes", + GST_TYPE_STRUCTURE, + (GParamFlags)(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS))); + + + base_sink_class->set_caps = GST_DEBUG_FUNCPTR (gst_subtecsink_set_caps); + base_sink_class->start = GST_DEBUG_FUNCPTR (gst_subtecsink_start); + base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_subtecsink_stop); + base_sink_class->query = GST_DEBUG_FUNCPTR (gst_subtecsink_query); + base_sink_class->event = GST_DEBUG_FUNCPTR (gst_subtecsink_event); + base_sink_class->render = GST_DEBUG_FUNCPTR (gst_subtecsink_render); + base_sink_class->prepare = GST_DEBUG_FUNCPTR (gst_subtecsink_prepare); + base_sink_class->preroll = GST_DEBUG_FUNCPTR (gst_subtecsink_preroll); + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_subtecsink_change_state); +} + +static GstFlowReturn +gst_subtecsink_prepare (GstBaseSink * sink, GstBuffer * buffer) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "prepare PTS %" GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_PTS(buffer))); + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_subtecsink_preroll (GstBaseSink * sink, GstBuffer * buffer) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "preroll PTS %" GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_PTS(buffer))); + + return GST_FLOW_OK; +} + + +GstStateChangeReturn gst_subtecsink_change_state(GstElement *element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstSubtecSink *subtecsink = GST_SUBTECSINK(element); + + GST_DEBUG_OBJECT(subtecsink, "change_state 0x%X", transition); + + switch (transition) + { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + { + GST_DEBUG_OBJECT(subtecsink, "changing state to playing"); + if (subtecsink->m_channel) + { + subtecsink->m_channel->SendResumePacket(); + + if (subtecsink->m_mute) + subtecsink->m_channel->SendMutePacket(); + else + subtecsink->m_channel->SendUnmutePacket(); + + set_attribute(subtecsink); + } + break; + } + default: + break; + } + ret = GST_ELEMENT_CLASS (parentClass)->change_state(element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + { + return ret; + } + + switch (transition) + { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + { + GST_DEBUG_OBJECT(subtecsink, "changing state to paused"); + if (subtecsink->m_channel) + { + subtecsink->m_channel->SendMutePacket(); + subtecsink->m_channel->SendPausePacket(); + } + break; + } + default: + break; + } + + return ret; +} + +static void +gst_subtecsink_init (GstSubtecSink *subtecsink) +{ + GST_DEBUG_OBJECT(subtecsink, "init"); + + gst_base_sink_set_async_enabled(GST_BASE_SINK(subtecsink), FALSE); + + subtecsink->m_attribute_mask = 0; + subtecsink->m_attribute_values = {0}; + subtecsink->m_pts_offset = 0; +} + +void +set_mute(GstSubtecSink *subtecsink) +{ + if (subtecsink->m_mute) + if (subtecsink->m_channel) subtecsink->m_channel->SendMutePacket(); else GST_WARNING_OBJECT (subtecsink, "Mute failed due to NULL channel"); + else + if (subtecsink->m_channel) subtecsink->m_channel->SendUnmutePacket(); else GST_WARNING_OBJECT (subtecsink, "Unmute failed due to NULL channel"); +} + +void +set_attribute(GstSubtecSink *subtecsink) +{ + if (subtecsink->m_channel) + { + subtecsink->m_channel->SendCCSetAttributePacket(0, subtecsink->m_attribute_mask, subtecsink->m_attribute_values); + } + else + { + GST_WARNING_OBJECT (subtecsink, "SetAttribute failed due to NULL channel"); + } +} + +void +gst_subtecsink_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (object); + + GST_DEBUG_OBJECT (subtecsink, "set_property"); + + switch (property_id) { + case PROP_MUTE: + { + auto mute = g_value_get_boolean (value); + GST_TRACE_OBJECT(subtecsink, "%s current mute %s setting mute to %s", __func__, subtecsink->m_mute ? "true" : "false", mute ? "true" : "false"); + + //Pause senderthread + subtecsink->m_mute = mute; + set_mute(subtecsink); + } + break; + case PROP_NO_EOS: + { + auto no_eos = g_value_get_boolean(value); + GST_TRACE_OBJECT(subtecsink, "%s setting no_eos to %s", __func__, no_eos ? "true" : "false"); + subtecsink->m_no_eos = no_eos; + } + break; + case PROP_SUBTEC_SOCKET: + { + auto subtec_socket = g_value_get_string(value); + GST_TRACE_OBJECT(subtecsink, "%s setting subtec_socket to %s", __func__, subtec_socket); + subtecsink->m_subtec_socket = subtec_socket; + } + break; + case PROP_PTS_OFFSET: + { + auto pts_offset = g_value_get_uint64(value); + GST_TRACE_OBJECT(subtecsink, "%s setting pts_offset to %" G_GUINT64_FORMAT, __func__, pts_offset); + subtecsink->m_pts_offset = pts_offset; + subtecsink->m_send_timestamp = true; + } + break; + case PROP_ATTRIBUTE_VALUES: + { + const GstStructure *attributevalues = gst_value_get_structure(value); + if (attributevalues != NULL) + { + gst_structure_get_uint(attributevalues, "font_color", &subtecsink->m_attribute_values[FONT_COLOR_INDEX]); + gst_structure_get_uint(attributevalues, "background_color", &subtecsink->m_attribute_values[BACKGROUND_COLOR_INDEX]); + gst_structure_get_uint(attributevalues, "font_opacity", &subtecsink->m_attribute_values[FONT_OPACITY_INDEX]); + gst_structure_get_uint(attributevalues, "background_opacity", &subtecsink->m_attribute_values[BACKGROUND_OPACITY_INDEX]); + gst_structure_get_uint(attributevalues, "font_style", &subtecsink->m_attribute_values[FONT_STYLE_INDEX]); + gst_structure_get_uint(attributevalues, "font_size", &subtecsink->m_attribute_values[FONT_SIZE_INDEX]); + gst_structure_get_uint(attributevalues, "window_color", &subtecsink->m_attribute_values[WIN_COLOR_INDEX]); + gst_structure_get_uint(attributevalues, "window_opacity", &subtecsink->m_attribute_values[WIN_OPACITY_INDEX]); + gst_structure_get_uint(attributevalues, "edge_type", &subtecsink->m_attribute_values[EDGE_TYPE_INDEX]); + gst_structure_get_uint(attributevalues, "edge_color", &subtecsink->m_attribute_values[EDGE_COLOR_INDEX]); + gst_structure_get_uint(attributevalues, "attribute_mask", &subtecsink->m_attribute_mask); + + set_attribute(subtecsink); + } + else + { + GST_ERROR_OBJECT(subtecsink, "%s attribute value NULL", __func__); + } + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_subtecsink_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (object); + + GST_DEBUG_OBJECT (subtecsink, "get_property %d", property_id); + + switch (property_id) { + case PROP_MUTE: + g_value_set_boolean (value, subtecsink->m_mute); + break; + case PROP_NO_EOS: + g_value_set_boolean (value, subtecsink->m_no_eos); + break; + case PROP_SUBTEC_SOCKET: + g_value_set_string (value, subtecsink->m_subtec_socket.c_str()); + break; + case PROP_PTS_OFFSET: + g_value_set_uint64 (value, subtecsink->m_pts_offset); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_subtecsink_dispose (GObject * object) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (object); + + GST_DEBUG_OBJECT (subtecsink, "dispose"); + + if (subtecsink->m_channel) subtecsink->m_channel->SendResetAllPacket(); + + G_OBJECT_CLASS (gst_subtecsink_parent_class)->dispose (object); +} + +void +gst_subtecsink_finalize (GObject * object) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (object); + + GST_DEBUG_OBJECT (subtecsink, "finalize"); + + G_OBJECT_CLASS (gst_subtecsink_parent_class)->finalize (object); +} + +/* notify subclass of new caps */ +static gboolean +gst_subtecsink_set_caps (GstBaseSink * sink, GstCaps * caps) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "set_caps"); + + const GstStructure *s = gst_caps_get_structure (caps, 0); + const gchar* caps_name = gst_structure_get_name(s); + + GST_DEBUG_OBJECT (subtecsink, "set_caps name %s", caps_name); + + if (!strcmp("text/vtt", caps_name) || !strcmp("application/x-subtitle-vtt", caps_name)) + subtecsink->m_channel = SubtecChannel::SubtecChannelFactory(SubtecChannel::ChannelType::WEBVTT); + else if (!strcmp("application/ttml+xml", caps_name)) + subtecsink->m_channel = SubtecChannel::SubtecChannelFactory(SubtecChannel::ChannelType::TTML); + else + { + GST_ERROR_OBJECT(subtecsink, "Unknown caps - cannot create subtec channel"); + return FALSE; + } + + subtecsink->m_channel->SendResetAllPacket(); + subtecsink->m_channel->SendSelectionPacket(1920, 1080); + set_mute(subtecsink); + set_attribute(subtecsink); + + return TRUE; +} + + +static gboolean +gst_subtecsink_start (GstBaseSink * sink) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "start"); + bool ret = false; + + if (subtecsink->m_subtec_socket.empty()) + ret = SubtecChannel::InitComms(); + else + ret = SubtecChannel::InitComms(subtecsink->m_subtec_socket.c_str()); + + if (!ret) + { + GST_WARNING_OBJECT (subtecsink, "Init failed - subtitle parsing disabled"); + } + + subtecsink->m_send_timestamp = true; + + return ret; +} + +static gboolean +gst_subtecsink_stop (GstBaseSink * sink) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + if (subtecsink->m_channel) + { + subtecsink->m_channel->SendResetChannelPacket(); + subtecsink->m_channel->SendResetAllPacket(); + } + + GST_DEBUG_OBJECT (subtecsink, "stop"); + + return TRUE; +} + +static gboolean +gst_subtecsink_query (GstBaseSink * sink, GstQuery * query) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "query %s", GST_QUERY_TYPE_NAME(query)); + + return GST_BASE_SINK_CLASS(G_OBJECT_CLASS (gst_subtecsink_parent_class))->query(sink, query); +} + +/** + * @brief Get the subtec timestamp in ms - applies the offset sent from upstream if applicable + * + * @param sink + * @param pts + * @param offset + * @return std::uint64_t + */ +static std::uint64_t get_timestamp_ms(GstBaseSink * sink, GstClockTime pts, GstClockTime offset) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + //Get timestamp - if segment time > pts, this means we have skipped into the middle of the segment + auto timestampMs = static_cast(std::max(pts, sink->segment.time) / GST_MSECOND); + + GST_DEBUG_OBJECT (subtecsink, "offset %" G_GINT64_FORMAT " pts %" GST_TIME_FORMAT , offset, GST_TIME_ARGS(pts)); + + // Offset -1 means no offset + if (static_cast(offset) > 0) + { + GST_DEBUG_OBJECT (subtecsink, "add %" GST_TIME_FORMAT " offset to %" GST_TIME_FORMAT " to give %" GST_TIME_FORMAT, + GST_TIME_ARGS(offset), GST_TIME_ARGS(timestampMs*GST_MSECOND), GST_TIME_ARGS((timestampMs+offset)*GST_MSECOND)); + timestampMs += offset; + } + //For debug - this is what you get from filesrc when debugging using gst-launch1.0 + else if (static_cast(pts) < 0) + { + GST_DEBUG_OBJECT (subtecsink, "offset is -1 and pts is -1 - setting time to 0"); + timestampMs = 0; + } + + return timestampMs; +} + +static gboolean +gst_subtecsink_event (GstBaseSink * sink, GstEvent * event) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + gboolean res = TRUE; + GST_TRACE_OBJECT(subtecsink, "%s eventType %s", __func__, gst_event_type_get_name(GST_EVENT_TYPE(event))); + + switch(GST_EVENT_TYPE(event)) + { + case GST_EVENT_FLUSH_START: + if (subtecsink->m_channel) subtecsink->m_channel->SendResetChannelPacket(); + break; + case GST_EVENT_FLUSH_STOP: + if (subtecsink->m_channel) subtecsink->m_channel->SendSelectionPacket(1920, 1080); + subtecsink->m_send_timestamp = true; + set_mute(subtecsink); + set_attribute(subtecsink); + break; + case GST_EVENT_SEGMENT: + { + const GstSegment *segment; + gst_event_parse_segment(event, &segment); + subtecsink->m_segmentstart = segment->start; + GST_DEBUG_OBJECT(subtecsink, "segment.start: %" G_GUINT64_FORMAT, segment->start); + } + break; + case GST_EVENT_EOS: + //Useful for debug with fakesrc + if (subtecsink->m_no_eos) + { + GST_DEBUG_OBJECT(subtecsink, "Eating EOS (nomnom)"); + return TRUE; + } + else + { + GST_DEBUG_OBJECT(subtecsink, "Received EOS"); + } + break; + case GST_EVENT_CUSTOM_DOWNSTREAM: + { + const GstStructure *structure = gst_event_get_structure(event); + if (gst_structure_has_name(structure, "sub_clock_sync")) + { + GstClockTime pts = g_value_get_uint64(gst_structure_get_value(structure, "current-pts")); + auto timestampMs = get_timestamp_ms(sink, pts, subtecsink->m_pts_offset); + GST_DEBUG_OBJECT(subtecsink, + "%s generating timestamp %u", + __func__, + static_cast(timestampMs)); + subtecsink->m_channel->SendTimestampPacket((timestampMs)); + subtecsink->m_send_timestamp = false; + } + gst_event_unref(event); + res = TRUE; + goto drop; + } + break; + default: + break; + } + + GST_DEBUG_OBJECT (subtecsink, "event"); + + res = GST_BASE_SINK_CLASS(G_OBJECT_CLASS (gst_subtecsink_parent_class))->event(sink, event); +drop: + return res; +} + +static GstFlowReturn +gst_subtecsink_render (GstBaseSink * sink, GstBuffer * buffer) +{ + GstSubtecSink *subtecsink = GST_SUBTECSINK (sink); + + GST_DEBUG_OBJECT (subtecsink, "render PTS %" GST_TIME_FORMAT, GST_TIME_ARGS(GST_BUFFER_PTS(buffer))); + + GstMapInfo map; + std::vector dataBuffer; + + if (gst_buffer_map(buffer, &map, GST_MAP_READ)) + { + auto inputData = static_cast(map.data); + auto inputSize = static_cast(map.size); + std::string instr(const_cast(reinterpret_cast(map.data)), map.size); + + GST_TRACE("%s unpacking GstBuffer size: %d\n", __func__, inputSize); + + for (std::uint32_t i = 0; i < inputSize; i++) + { + dataBuffer.push_back(inputData[i]); + } + + gst_buffer_unmap(buffer, &map); + } + else + { + GST_WARNING("%s error unpacking GstBuffer!", __func__); + } + + if (!dataBuffer.empty()) + { + if (subtecsink->m_send_timestamp) + { + //On seek, segment "time" will be ahead of buffer PTS - otherwise just use PTS + auto timestampMs = get_timestamp_ms(sink, subtecsink->m_segmentstart, subtecsink->m_pts_offset); + + GST_DEBUG_OBJECT(subtecsink, + "%s generating timestamp %u", + __func__, + static_cast(timestampMs)); + + subtecsink->m_channel->SendTimestampPacket((timestampMs)); + subtecsink->m_send_timestamp = false; + } + + auto offset = static_cast(GST_BUFFER_OFFSET(buffer)); + if (offset == -1) + offset = 0; + + GST_DEBUG_OBJECT (subtecsink, "sending data packet with offset %" G_GINT64_FORMAT " buffer %" G_GUINT64_FORMAT, offset, GST_BUFFER_OFFSET(buffer)); + subtecsink->m_channel->SendDataPacket(std::move(dataBuffer), 0 - offset); + } + + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + + return gst_element_register (plugin, "subtecsink", GST_RANK_PRIMARY, + GST_TYPE_SUBTECSINK); +} + +#ifndef VERSION +#define VERSION "0.1.0" +#endif +#ifndef PACKAGE +#define PACKAGE "MWGstreamerPlugins" +#endif +#ifndef PACKAGE_NAME +#define PACKAGE_NAME "MWGstreamerPlugins" +#endif +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "http://comcast.com/" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + subtecsink, + "SubTtxRend text/ttml sink", + plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) + diff --git a/middleware/gst-plugins/gst_subtec/gstsubtecsink.h b/middleware/gst-plugins/gst_subtec/gstsubtecsink.h new file mode 100644 index 000000000..56f3b085a --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstsubtecsink.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GST_SUBTECSINK_H_ +#define _GST_SUBTECSINK_H_ + +#include +#include +#include +#include +#include "SubtecChannel.hpp" + +G_BEGIN_DECLS + +#define GST_TYPE_SUBTECSINK (gst_subtecsink_get_type()) +#define GST_SUBTECSINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SUBTECSINK,GstSubtecSink)) +#define GST_SUBTECSINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SUBTECSINK,GstSubtecSinkClass)) +#define GST_IS_SUBTECSINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SUBTECSINK)) +#define GST_IS_SUBTECSINK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SUBTECSINK)) + +typedef struct _GstSubtecSink GstSubtecSink; +typedef struct _GstSubtecSinkClass GstSubtecSinkClass; + +struct _GstSubtecSink +{ + GstBaseSink base_subtecsink; + + std::unique_ptr m_channel; + gboolean m_mute; + gboolean m_no_eos; + gboolean m_send_timestamp{true}; + guint64 m_segmentstart{0}; + guint64 m_pts_offset{0}; + std::string m_subtec_socket{}; + attributesType m_attribute_values{0}; + guint m_attribute_mask{0}; +}; + +struct _GstSubtecSinkClass +{ + GstBaseSinkClass base_subtecsink_class; +}; + +GType gst_subtecsink_get_type (void); + +G_END_DECLS + +#endif diff --git a/middleware/gst-plugins/gst_subtec/gstvipertransform.cpp b/middleware/gst-plugins/gst_subtec/gstvipertransform.cpp new file mode 100644 index 000000000..f2acf788d --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstvipertransform.cpp @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * SECTION:element-gstvipertransform + * + * The vipertransform element performs the necessary transforms for legacy linear content. + * These are needed to overcome limitations in the TTML delivery + * + */ + +#include +#include +#include "gstvipertransform.h" + +#include +#include +#include +#include +#include + +namespace { constexpr auto XmlStart = "dispose = gst_vipertransform_dispose; + gobject_class->finalize = gst_vipertransform_finalize; + + base_transform_class->set_caps = GST_DEBUG_FUNCPTR (gst_vipertransform_set_caps); + base_transform_class->transform = GST_DEBUG_FUNCPTR (gst_vipertransform_transform); + base_transform_class->before_transform = GST_DEBUG_FUNCPTR (gst_vipertransform_before_transform); + base_transform_class->sink_event = GST_DEBUG_FUNCPTR (gst_vipertransform_event); +} + + +static gboolean gst_vipertransform_event (GstBaseTransform * trans, GstEvent * event) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (trans); + + GST_TRACE_OBJECT(vipertransform, "%s eventType %s", __func__, gst_event_type_get_name(GST_EVENT_TYPE(event))); + + switch(GST_EVENT_TYPE(event)) + { + case GST_EVENT_FLUSH_START: + vipertransform->m_content_type = ViperContentType::UNKNOWN; + vipertransform->m_linear_begin_offset = 0; + break; + case GST_EVENT_FLUSH_STOP: + vipertransform->m_content_type = ViperContentType::UNKNOWN; + vipertransform->m_linear_begin_offset = 0; + break; + } + + GST_DEBUG_OBJECT (vipertransform, "event"); + + return GST_BASE_TRANSFORM_CLASS(G_OBJECT_CLASS (gst_vipertransform_parent_class))->sink_event(trans, event); +} + +static void +gst_vipertransform_init (GstViperTransform *vipertransform) +{ + GST_DEBUG_OBJECT (vipertransform, "init"); + vipertransform->m_content_type = ViperContentType::UNKNOWN; +} + +static gboolean gst_vipertransform_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (trans); + + GST_DEBUG_OBJECT (vipertransform, "set_caps"); + + const GstStructure *s = gst_caps_get_structure (incaps, 0); + const gchar* caps_name = gst_structure_get_name(s); + + GST_DEBUG_OBJECT (vipertransform, "caps %s", caps_name); + + // If not TTML set to passthrough mode + if (strcmp("application/ttml+xml", caps_name)) + { + gst_base_transform_set_passthrough(trans, TRUE); + GST_DEBUG_OBJECT (vipertransform, "Setting passthrough"); + } + + vipertransform->m_search_first_begin = true; + vipertransform->m_linear_begin_offset = 0; + + return TRUE; +} + +void +gst_vipertransform_dispose (GObject * object) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (object); + + GST_DEBUG_OBJECT (vipertransform, "dispose"); + + G_OBJECT_CLASS (gst_vipertransform_parent_class)->dispose (object); +} + +void +gst_vipertransform_finalize (GObject * object) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (object); + + GST_DEBUG_OBJECT (vipertransform, "finalize"); + + G_OBJECT_CLASS (gst_vipertransform_parent_class)->finalize (object); +} + +/** + * @brief Convert a time string (HHHH:MM:SS.000) to milliseconds + * + * @param timeString + * @return gint64 + */ +gint64 convertTime(const std::string &timeString) +{ + gint64 retMs = 0; + int hours, mins; + float secs; + + if (std::sscanf(timeString.c_str(), "%d:%d:%f", &hours, &mins, &secs)) + { + retMs += static_cast(hours) * 3600; + retMs += mins * 60; + retMs *= 1000; + retMs += static_cast(secs * 1000.0); + } + + return retMs; +} + +/** + * @brief Find a specific tag and return the contents, position and contents length + * + * @param instring + * @param tag + * @param start_pos + * @return std::tuple + */ +std::tuple findTag(const std::string &instring, const std::string &tag, std::size_t start_pos) +{ + std::string tag_start{tag+"=\""}; + std::size_t pos = start_pos; + + pos = instring.find(tag_start, pos); + + if (pos == std::string::npos) + return {"", std::string::npos, 0}; + + pos += tag_start.length(); // Takes us to contents + std::size_t endpos = instring.find_first_of('"', pos); // following " + std::size_t len = endpos - pos; + std::string tag_contents = instring.substr(pos, len); + + return {tag_contents, pos, len}; +} + + +/** + * @brief Search the TTML for the first begin="" tag. We will use this to calculate a timestamp offset + * eg if the first begin tag is begin="458:34:12:123" then we take this and subtract the + * buffer PTS ms to get an offset for the start of the stream + * PTS 0 2 4 + * time|----------|--b----b--|-b------b-etc + * ttml empty 1st b=2:40 + * + * 2:40 - 2s = 2:38 offset at the start of the stream + * + * We can then use this offset to sync the subtitles and audio PTS + * + * @param ss + * @param firstBeginMs + * @return true + * @return false + */ +static bool findFirstBegin(const std::string &ttml, gint64 &firstBeginMs) +{ + std::size_t pos; + std::string tag_contents; + + std::tie(tag_contents, pos, std::ignore) = findTag(ttml, "begin", 0); + + if (pos != std::string::npos) + { + firstBeginMs = convertTime(tag_contents); + return true; + } + + return false; +} + +/** + * @brief Find the offset in ms between the buffer PTS and the first "begin" tag in the TTML + * + * @param ttml + * @param buf + * @param offset_ms + * @return true + * @return false + */ +static bool find_offset_ms_from_pts(const std::string &ttml, GstBuffer *buf, gint64 &offset_ms) +{ + gint64 first_begin_ms = 0; + + if (findFirstBegin(ttml, first_begin_ms)) + { + guint64 pts_ms = GST_BUFFER_PTS(buf) / GST_MSECOND; + auto duration_ms = GST_BUFFER_DURATION(buf) / GST_MSECOND; + offset_ms = first_begin_ms - pts_ms; + } + else + return false; + + return true; +} + +/** + * @brief Get the restamp pts offset from the buffer + * + * @param buf + * @return gint64 + */ +static gint64 GetRestampPTSOffsetMS(GstBuffer * buf) +{ + return GST_BUFFER_OFFSET(buf); +} + +/** + * @brief Check if there are more than one xml docs in the current segment + * + * @param vipertransform + * @param ttml + * @return true + * @return false + */ +static bool is_harmonic_uhd(GstViperTransform *vipertransform, const std::string &ttml) +{ + // Check for more than one element (Harmonic UHD case) + const char *xml_marker = XmlStart; + auto first_marker = ttml.find(xml_marker); + auto second_marker = ttml.find(xml_marker, first_marker+1); + + return (second_marker != std::string::npos); +} + +/** + * @brief Split Harmonic muxed buffers into a string vector + * In the case of Harmonic linear content, the TTML docs are concatenated in a single segment + * The timestamps are referenced to the start of the TTML doc, so the structure will be as follows: + * <------First TTML doc + * + * ... + * + *

+ * Text from 0s - 1s + *

+ * + *
+ * <------Second TTML doc + * + * ... + * + *

+ * Text from 1s - 2s + *

+ * + *
+ * + * @param vipertransform + * @param ttml + * @return true + * @return false + */ +static std::vector split_buffer(const std::string &ttml) +{ + std::vector ttml_vec; + std::size_t pos=0, start_pos=0, end_pos = 0; + + while ((start_pos = ttml.find(XmlStart, pos)) != std::string::npos) + { + end_pos = ttml.find(XmlStart, start_pos+1); + auto sub = ttml.substr(start_pos, end_pos); + + ttml_vec.emplace_back(std::move(sub)); + pos = end_pos; + } + + return ttml_vec; +} + +/** + * @brief Check if the incoming content needs an offset. If so, parse it from the TTML + * + * @param GstBaseTransform *trans + * @param GstBuffer *buf + */ +static void gst_vipertransform_before_transform (GstBaseTransform * trans, + GstBuffer * buf) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (trans); + + GST_DEBUG_OBJECT (vipertransform, "before_transform PTS %" GST_TIME_FORMAT " mode %d", GST_TIME_ARGS(GST_BUFFER_PTS(buf)), (int)vipertransform->m_content_type); + + if (gst_base_transform_is_passthrough(trans)) + { + GST_DEBUG_OBJECT (vipertransform, "Passthrough set"); + return; + } + + //Parse TTML string from buffer + std::string ttml; + { + GstMapInfo inmap; + if (gst_buffer_map(buf, &inmap, (GstMapFlags)GST_MAP_READ)) + { + GST_DEBUG_OBJECT (vipertransform, "map.size %" G_GSIZE_FORMAT, inmap.size); + ttml.assign(reinterpret_cast(inmap.data), inmap.size); + gst_buffer_unmap(buf, &inmap); + } + else + { + GST_ERROR_OBJECT (vipertransform, "Could not open for reading"); + return; + } + } + + //If there is more than one segment in the buffer, just take the first one + if (is_harmonic_uhd(vipertransform, ttml)) + { + auto ttml_vec = split_buffer(ttml); + if (!ttml_vec.empty()) + { + ttml = ttml_vec.front(); + GST_DEBUG_OBJECT (vipertransform, "uhd[0] %s", ttml.c_str()); + } + } + + gint64 first_begin_ms = 0; + //Find the gap between the PTS and the first "begin" tag + + if (findFirstBegin(ttml, first_begin_ms)) + { + gint64 restamp_pts_offset_ms = GetRestampPTSOffsetMS(buf); + gint64 offset_from_pts_ms = first_begin_ms - (GST_BUFFER_PTS(buf) / GST_MSECOND); + + GST_DEBUG_OBJECT(vipertransform, "first_begin_ms %" G_GINT64_FORMAT " offset_from_pts_ms %" G_GINT64_FORMAT " restamp_pts_offset_ms %" G_GINT64_FORMAT " buf duration %" G_GUINT64_FORMAT " lbo %" G_GINT64_FORMAT " ", + first_begin_ms, offset_from_pts_ms, restamp_pts_offset_ms, GST_BUFFER_DURATION(buf) / GST_MSECOND, vipertransform->m_linear_begin_offset); + + if (ViperContentType::UNKNOWN == vipertransform->m_content_type) + { + //If first cue time does not line up with PTS, this is linear content and needs an offset + // If the offset is greater than the buffer duration, we need to set the offset + // to the first cue time + // If the offset is less than the buffer duration, we need to set the offset + // to the restamp offset + if (std::abs(offset_from_pts_ms - restamp_pts_offset_ms) > GST_BUFFER_DURATION(buf) / GST_MSECOND) + { + vipertransform->m_content_type = ViperContentType::LINEAR_OFFSET; + if (vipertransform->m_linear_begin_offset == 0 || abs(offset_from_pts_ms) < vipertransform->m_linear_begin_offset) + { + vipertransform->m_linear_begin_offset = offset_from_pts_ms; + } + } + // If no misalignment was found, correct for any PTS re-stamping offset applied by Player for linear content + else if (restamp_pts_offset_ms != 0) + { + vipertransform->m_linear_begin_offset = restamp_pts_offset_ms; + vipertransform->m_content_type = ViperContentType::LINEAR_OFFSET; + } + else + { + //First cue time lines up with PTS so no offset needed + vipertransform->m_content_type = ViperContentType::PASSTHROUGH; + gst_base_transform_set_passthrough(trans, TRUE); + } + } + //See if we can get the offset a bit closer + //If the new offset is less, go for that + //If it's equal to the last, we're probably at the beginning of the segment so stick with this + else if (ViperContentType::LINEAR_OFFSET == vipertransform->m_content_type) + { + if ((std::abs(offset_from_pts_ms - restamp_pts_offset_ms) > GST_BUFFER_DURATION(buf) / GST_MSECOND)) + { + vipertransform->m_linear_begin_offset = offset_from_pts_ms; + } + else + { + vipertransform->m_linear_begin_offset = restamp_pts_offset_ms; + } + } + else + { + GST_DEBUG_OBJECT (vipertransform, "Empty segment - no cue found"); + } + } + return; +} + +/** + * @brief Add the offset to the GstBuffer if required. Also split the incoming buffer + * if needed for Harmonic content + * + * @param trans + * @param inbuf + * @param outbuf + * @return GstFlowReturn + */ +static GstFlowReturn +gst_vipertransform_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstViperTransform *vipertransform = GST_VIPERTRANSFORM (trans); + + GST_DEBUG_OBJECT (vipertransform, "transform"); + + std::string ttml; + + { + GstMapInfo inmap, outmap; + if (gst_buffer_map(inbuf, &inmap, (GstMapFlags)GST_MAP_READ) && gst_buffer_map(outbuf, &outmap, (GstMapFlags)GST_MAP_WRITE)) + { + ttml.assign(reinterpret_cast(inmap.data), inmap.size); + memcpy(outmap.data, inmap.data, inmap.size); + gst_buffer_unmap(outbuf, &outmap); + gst_buffer_unmap(inbuf, &inmap); + } + else + { + GST_ERROR_OBJECT (vipertransform, "Could not map buffers"); + return GST_FLOW_ERROR; + } + } + + + if (is_harmonic_uhd(vipertransform, ttml)) + { + std::vector ttml_vec; + + GST_DEBUG_OBJECT (vipertransform, "Harmonic transform"); + + //Split inbuf into a vector of xml docs + ttml_vec = split_buffer(ttml); + + if (ttml_vec.empty()) + { + GST_DEBUG_OBJECT (vipertransform, "vector empty - passing data straight through"); + return GST_FLOW_OK; + } + + int segment_duration; + GstClockTime pts = GST_BUFFER_PTS(inbuf); + + //Useful for debugging via filesrc (filesrc sets pts/dts/duration to -1) + if (static_cast(GST_BUFFER_DURATION(inbuf)) == -1) + { + segment_duration = ttml_vec.size() * GST_SECOND; + pts = 0; + } + else + segment_duration = GST_BUFFER_DURATION(inbuf) / ttml_vec.size(); + + auto segment_count = 0; + + //Split single, concatenated buffer into separate buffers + //Add an offset to the buffer object for use by the sink + //Then push them as new, separate buffers to the pipeline + for (const auto &item : ttml_vec) + { + GstBuffer *buf; + GstMemory *mem; + + guint64 pts_ms = pts / GST_MSECOND; + guint64 duration_ms = segment_duration / GST_MSECOND; + + gchar *data = (gchar *)g_malloc(item.size()); + memcpy(data, item.c_str(), item.size()); + + buf = gst_buffer_new_wrapped(data, item.size()); + //Retimestamp new buffer if required + GST_BUFFER_PTS(buf) = pts + ((segment_count * segment_duration)); + GST_BUFFER_DTS(buf) = pts + ((segment_count * segment_duration)); + GST_BUFFER_DURATION(buf) = segment_duration; + + //Incoming Harmonic content might be either + // a. TTML timestamped relative to the start of the fragment or + // b. MediaKind-style with TTML timestamp being absolute from some fairly arbitrary zero point + if (vipertransform->m_linear_begin_offset != 0) + GST_BUFFER_OFFSET(buf) = vipertransform->m_linear_begin_offset; + else + GST_BUFFER_OFFSET(buf) = 0 - (pts_ms + (duration_ms * segment_count)); + + GST_DEBUG_OBJECT (vipertransform, "pts_ms: %" G_GUINT64_FORMAT " duration_ms %" G_GUINT64_FORMAT " offset ms %" G_GUINT64_FORMAT " segment_count %d", pts_ms, duration_ms, GST_BUFFER_OFFSET(buf), segment_count); + + //Push fragment as a separate GstBuffer + gst_pad_push(gst_element_get_static_pad(&trans->element, "src"), buf); + segment_count++; + } + + return GST_BASE_TRANSFORM_FLOW_DROPPED; + } + else if (vipertransform->m_linear_begin_offset != 0) + { + GST_BUFFER_OFFSET(outbuf) = vipertransform->m_linear_begin_offset; + } + + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "vipertransform", GST_RANK_PRIMARY + 1, + GST_TYPE_VIPERTRANSFORM); +} + +#ifndef VERSION +#define VERSION "0.1.0" +#endif +#ifndef PACKAGE +#define PACKAGE "MWGstreamerPlugins" +#endif +#ifndef PACKAGE_NAME +#define PACKAGE_NAME "MWGstreamerPlugins" +#endif +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "http://comcast.com/" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + vipertransform, + "Applies any necessary transforms for TTML content", + plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/middleware/gst-plugins/gst_subtec/gstvipertransform.h b/middleware/gst-plugins/gst_subtec/gstvipertransform.h new file mode 100644 index 000000000..e2e8f4f01 --- /dev/null +++ b/middleware/gst-plugins/gst_subtec/gstvipertransform.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 RDK Management + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GST_VIPERTRANSFORM_H_ +#define _GST_VIPERTRANSFORM_H_ + +#include + +#include + +// Declared static here because this string exists in player and gstplugin .so +// library files This string needs to match the start +// of the gsteamer plugin name as created by the macros. +static const char* GstPluginNameViperTransform = "vipertransform"; + +G_BEGIN_DECLS + +#define GST_TYPE_VIPERTRANSFORM (gst_vipertransform_get_type()) +#define GST_VIPERTRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VIPERTRANSFORM,GstViperTransform)) +#define GST_VIPERTRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VIPERTRANSFORM,GstViperTransformClass)) +#define GST_IS_VIPERTRANSFORM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VIPERTRANSFORM)) +#define GST_IS_VIPERTRANSFORM_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VIPERTRANSFORM)) + +typedef struct _GstViperTransform GstViperTransform; +typedef struct _GstViperTransformClass GstViperTransformClass; + +enum class ViperContentType +{ + UNKNOWN, + PASSTHROUGH, + LINEAR_OFFSET, + LINEAR_OFFSET_PRELIM, + HARMONIC_UHD +}; + +struct _GstViperTransform +{ + GstBaseTransform base_vipertransform; + ViperContentType m_content_type {ViperContentType::UNKNOWN}; + gint64 m_linear_begin_offset {0}; + gboolean m_search_first_begin {true}; +}; + +struct _GstViperTransformClass +{ + GstBaseTransformClass base_vipertransform_class; +}; + +GType gst_vipertransform_get_type (void); + +G_END_DECLS + +#endif diff --git a/middleware/gst-plugins/gstinit.cpp b/middleware/gst-plugins/gstinit.cpp new file mode 100755 index 000000000..381548254 --- /dev/null +++ b/middleware/gst-plugins/gstinit.cpp @@ -0,0 +1,106 @@ +/* +* Copyright 2018 RDK Management +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation, version 2.1 +* of the license. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the +* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +* Boston, MA 02110-1301, USA. +*/ + +/** + * @file gstinit.cpp + * @brief gstreamer plugin initialization + */ + +#include +#include +#ifdef DRM_BUILD_PROFILE +#include "gstplayreadydecryptor.h" +#include "gstwidevinedecryptor.h" +#include "gstclearkeydecryptor.h" +#include "gstverimatrixdecryptor.h" +#endif + + +/** + * @brief plugin_init , invoked by gstreamer core on load. Registers player gstreamer elements. + * @param plugin GstPlugin to which elements should be registered + * @retval status of operation + */ +static gboolean plugin_init(GstPlugin * plugin) +{ + gboolean ret = false; +#ifdef DRM_BUILD_PROFILE + ret = gst_element_register(plugin, GstPluginNamePR, + GST_RANK_PRIMARY, GST_TYPE_PLAYREADYDECRYPTOR ); + if(ret) + { + printf("player plugin_init registered %s element\n", GstPluginNamePR); + } + else + { + printf("player plugin_init FAILED to register %s element\n", GstPluginNamePR); + } + ret = gst_element_register(plugin, GstPluginNameWV, + GST_RANK_PRIMARY, GST_TYPE_WIDEVINEDECRYPTOR ); + if(ret) + { + printf("player plugin_init registered %s element\n", GstPluginNameWV); + } + else + { + printf("player plugin_init FAILED to register %s element\n", GstPluginNameWV); + } + ret = gst_element_register(plugin, GstPluginNameCK, + GST_RANK_PRIMARY, GST_TYPE_CLEARKEYDECRYPTOR ); + if(ret) + { + printf("player plugin_init registered %s element\n", GstPluginNameCK); + } + else + { + printf("player plugin_init FAILED to register %s element\n", GstPluginNameCK); + } + ret = gst_element_register(plugin, GstPluginNameVMX, + GST_RANK_PRIMARY, GST_TYPE_VERIMATRIXDECRYPTOR ); + if(ret) + { + printf("player plugin_init registered %s element\n", GstPluginNameVMX); + } + else + { + printf("player plugin_init FAILED to register %s element\n", GstPluginNameVMX); + } +#else +#endif + return ret; +} + +#ifndef VERSION +#define VERSION "0.0.1" +#endif +#ifndef PACKAGE +#define PACKAGE "RDK" +#endif +#ifndef PACKAGE_NAME +#define PACKAGE_NAME "aamp" +#endif +#ifndef GST_PACKAGE_ORIGIN +#define GST_PACKAGE_ORIGIN "https://rdkcentral.com/" +#endif + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + aamp, + "Interface Player", + plugin_init, VERSION, "LGPL", PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/middleware/gstplayertaskpool.cpp b/middleware/gstplayertaskpool.cpp new file mode 100644 index 000000000..1cb30cd30 --- /dev/null +++ b/middleware/gstplayertaskpool.cpp @@ -0,0 +1,120 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file gstplayertaskpool.cpp + * @brief PLAYER Gstreamer Task pool code + */ + +#include +#include "gstplayertaskpool.h" +#include + + +/** + * @brief PlayerGstTaskId to store the threadId + */ +typedef struct +{ + pthread_t thread; +} PlayerGstTaskId; + +/** class initialization */ +#define gst_player_taskpool_parent_class parent_class +G_DEFINE_TYPE(GstPlayerTaskpool, gst_player_taskpool, GST_TYPE_TASK_POOL); + +GST_DEBUG_CATEGORY(gst_player_taskpool_debug_category); +#define GST_CAT_DEFAULT gst_player_taskpool_debug_category + +/** + * @brief Override for gst_task_pool_push + * Start the execution of a new thread from pool. + * In this case create and assign a thread for gst task + * + * Not setting any specific policies or priorities for now + * as the pthread implementation takes thread attributes automatically + * from the calling thread. + * + * For now we just need same priority for PLAYER gst threads, and if enabled + * RT priority is set on PLAYER thread which setup the pipeline thus the gst + * threads also get same priority + * + * @param pool GstTaskPool + * @param func the function to call + * @param data data to pass to func + * @param error stores error + * @return gpointer a pointer that should be used for the gst_task_pool_join function + */ +static gpointer gst_player_taskpool_push (GstTaskPool * pool, GstTaskPoolFunction func, gpointer data, + GError ** error) +{ + PlayerGstTaskId *tid; + gint res; + + tid = g_new0 (PlayerGstTaskId, 1); + res = pthread_create (&tid->thread, NULL, (void *(*)(void *)) func, data); + + if (res != 0) { + g_set_error (error, G_THREAD_ERROR, G_THREAD_ERROR_AGAIN, + "Error creating thread: %s", g_strerror (res)); + g_free (tid); + tid = NULL; + } + return tid; +} + +/** + * @brief Override for gst_task_pool_join + * Used to join the created thread + * @param pool GstTaskPool + * @param id TaskId containing the thread info to join + */ +static void gst_player_taskpool_join (GstTaskPool * pool, gpointer id) +{ + PlayerGstTaskId *tid = (PlayerGstTaskId *) id; + + pthread_join (tid->thread, NULL); + + g_free (tid); +} + + +/** + * @brief class_init function for gst_player_taskpool + * + * @param klass GstPlayerTaskpoolClass defined in header file + */ +static void gst_player_taskpool_class_init (GstPlayerTaskpoolClass * klass) +{ + GstTaskPoolClass *gsttaskpool_class; + + gsttaskpool_class = (GstTaskPoolClass *) klass; + gsttaskpool_class->push = gst_player_taskpool_push; + gsttaskpool_class->join = gst_player_taskpool_join; +} + +/** + * @brief gst_player_taskpool_init + * + * @param pool + */ +static void gst_player_taskpool_init (GstPlayerTaskpool * pool) +{ +} diff --git a/middleware/gstplayertaskpool.h b/middleware/gstplayertaskpool.h new file mode 100644 index 000000000..8c7bc98f8 --- /dev/null +++ b/middleware/gstplayertaskpool.h @@ -0,0 +1,60 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + + +/** + * @file gstplayertaskpool.h + * @brief PLAYER Gstreamer Task pool if needed + */ + + +#ifndef _GST_PLAYER_TASKPOOL_H_ +#define _GST_PLAYER_TASKPOOL_H_ + +#include + +G_BEGIN_DECLS + + + +#define GST_TYPE_PLAYER_TASKPOOL (gst_player_taskpool_get_type ()) +#define GST_PLAYER_TASKPOOL(pool) (G_TYPE_CHECK_INSTANCE_CAST ((pool), GST_TYPE_PLAYER_TASKPOOL, GstPlayerTaskpool)) +#define GST_IS_PLAYER_TASKPOOL(pool) (G_TYPE_CHECK_INSTANCE_TYPE ((pool), GST_TYPE_PLAYER_TASKPOOL)) +#define GST_PLAYER_TASKPOOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_CAST ((pclass), GST_TYPE_PLAYER_TASKPOOL, GstPlayerTaskpoolClass)) +#define GST_IS_PLAYER_TASKPOOL_CLASS(pclass) (G_TYPE_CHECK_CLASS_TYPE ((pclass), GST_TYPE_PLAYER_TASKPOOL)) +#define GST_PLAYER_TASKPOOL_GET_CLASS(pool) (G_TYPE_INSTANCE_GET_CLASS ((pool), GST_TYPE_PLAYER_TASKPOOL, GstPlayerTaskpoolClass)) +#define GST_PLAYER_TASKPOOL_CAST(pool) ((GstPlayerTaskpool*)(pool)) + +typedef struct _GstPlayerTaskpool GstPlayerTaskpool; +typedef struct _GstPlayerTaskpoolClass GstPlayerTaskpoolClass; + +struct _GstPlayerTaskpool { + GstTaskPool object; +}; + +struct _GstPlayerTaskpoolClass { + GstTaskPoolClass parent_class; +}; + +GType gst_player_taskpool_get_type (void); + + +G_END_DECLS + +#endif diff --git a/middleware/install-middleware.sh b/middleware/install-middleware.sh new file mode 100755 index 000000000..dd6b57e20 --- /dev/null +++ b/middleware/install-middleware.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +#set -x + +if [[ -z "${MAKEFLAGS}" ]]; then + export MAKEFLAGS=-j$(nproc) +fi + +# Set the CMAKE_POLICY_VERSION_MINIMUM to 3.5 +# Mostly required for OSX builds +export CMAKE_POLICY_VERSION_MINIMUM=3.5 + +# Fail the script should any step fail. To override this behavior use "|| true" on those statements +set -eo pipefail + +# All include files should be done here +# +# tools and OS specifics +source scripts/tools.sh +# do_clone, et al +source scripts/install_clone.sh +# install_options_fn +source scripts/install_options.sh +# pre-built dependencies install +source scripts/install_dependencies.sh +# gtest install and build +source scripts/install_gtest.sh +# glib install and build +source scripts/install_glib.sh +# libdash install and build +source scripts/install_libdash.sh +# gstreamer install +source scripts/install_gstreamer.sh +# subtec install and build +source scripts/install_subtec.sh +# rialto install and build +source scripts/install_rialto.sh +# + + +# VARIABLES + +# Elapsed time +SECONDS=0 + +declare ARCH="" +# Collect summary to be printed at the end of execution +declare -a INSTALL_STATUS_ARR +# +# See scripts/install_options.sh for other variables + +# directory where we build dependencies from source +declare LOCAL_DEPS_BUILD_DIR + +PLAYER_DIR=$PWD + +# MAIN execution starts + +# Get and process install options +install_options_fn "$@" +INSTALL_STATUS_ARR+=("install_options_fn check passed.") + +tools_banner_fn + +echo "Checking for installed compilation tools" +tools_install_fn +echo "" +INSTALL_STATUS_ARR+=("tools_install_fn check passed.") + + +ARCH=$(tools_arch_fn) +echo "Building ${OPTION_PLAYER_BRANCH} on ${OSTYPE} ${ARCH} starting $(date)" +echo "" +INSTALL_STATUS_ARR+=("tools_arch_fn check passed.") + +LOCAL_DEPS_BUILD_DIR="${PLAYER_DIR}/.libs" +echo "" +echo "Building dependencies in ${LOCAL_DEPS_BUILD_DIR}" +if [ ! -d ${LOCAL_DEPS_BUILD_DIR} ]; then + mkdir ${LOCAL_DEPS_BUILD_DIR} +fi + + +# Install prebuilt dependencies +# +if [ ${OPTION_QUICK} = false ] ; then + echo "" + echo "*** Check/Install dependency packages" + install_pkgs_fn + INSTALL_STATUS_ARR+=("install_pkgs_fn check passed.") +else + INSTALL_STATUS_ARR+=("install_pkgs_fn check SKIPPED.") +fi + + +# Install built dependencies +echo "" +echo "*** Check/Install source packages" + +# Install gstreamer +# +install_gstreamer_fn +INSTALL_STATUS_ARR+=("install_gstreamer_fn check passed.") + +# Build gst-plugins-good. install_gstreamer_fn must have been called first +install_gstpluginsgoodfn $OPTION_CLEAN +INSTALL_STATUS_ARR+=("install_gstplugingood_fn check passed.") + +# Build googletest +# +install_build_googletest_fn "${OPTION_CLEAN}" +INSTALL_STATUS_ARR+=("install_build_googletest check passed.") + +# Build glib +# +install_build_glib_fn "${OPTION_CLEAN}" +INSTALL_STATUS_ARR+=("install_build_glib check passed.") + +# Build subtec +# +CLEAN=false +if [ ${OPTION_CLEAN} = true ] ; then + CLEAN=true +fi +if [ ${OPTION_SUBTEC_CLEAN} = true ] ; then + CLEAN=true +fi + +if [ ${OPTION_SUBTEC_SKIP} = false ] ; then + subtec_install_build_fn "${CLEAN}" + INSTALL_STATUS_ARR+=("subtec_install_build check passed.") +else + INSTALL_STATUS_ARR+=("subtec_install_build check SKIPPED.") +fi + +# Build rialto +# +rialto_install_build_fn "${OPTION_CLEAN}" +INSTALL_STATUS_ARR+=("rialto_install_build_fn check passed.") + +tools_print_summary_fn + +# DONE diff --git a/middleware/playerJsonObject/CMakeLists.txt b/middleware/playerJsonObject/CMakeLists.txt new file mode 100644 index 000000000..f47079219 --- /dev/null +++ b/middleware/playerJsonObject/CMakeLists.txt @@ -0,0 +1,50 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.5) + +project(playerjsonobject) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FindPkgConfig) +pkg_check_modules(LIBCJSON REQUIRED libcjson) +link_directories(${LIBCJSON_LIBRARY_DIRS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../baseConversion) +set(PlayerJsonObject_SRC PlayerJsonObject.cpp) + +add_library(playerjsonobject SHARED ${PlayerJsonObject_SRC}) + +target_include_directories(playerjsonobject PUBLIC + ${LIBCJSON_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../baseConversion +) + +target_link_libraries(playerjsonobject PUBLIC + ${LIBCJSON_LINK_LIBRARIES} + baseconversion +) + +set_target_properties(playerjsonobject PROPERTIES + PUBLIC_HEADER "PlayerJsonObject.h" +) + +install(TARGETS playerjsonobject + DESTINATION lib + PUBLIC_HEADER DESTINATION include +) + diff --git a/middleware/playerJsonObject/PlayerJsonObject.cpp b/middleware/playerJsonObject/PlayerJsonObject.cpp new file mode 100644 index 000000000..acbcdd481 --- /dev/null +++ b/middleware/playerJsonObject/PlayerJsonObject.cpp @@ -0,0 +1,737 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerJsonObject.cpp + * @brief File to handle Json format object + */ + +#include + +#include "PlayerJsonObject.h" +#include "_base64.h" + +//unsigned char *player_Base64_URL_Decode(const char *src, size_t *len, size_t srcLen); +char *player_Base64_URL_Encode(const unsigned char *src, size_t len); + +PlayerJsonObject::PlayerJsonObject() : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_CreateObject(); +} + +PlayerJsonObject::PlayerJsonObject(const std::string& jsonStr) : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_Parse(jsonStr.c_str()); + + if (!mJsonObj) + { + throw PlayerJsonParseException(); + } +} + +PlayerJsonObject::PlayerJsonObject(const char* jsonStr) : mParent(NULL), mJsonObj() +{ + mJsonObj = cJSON_Parse(jsonStr); + + if (!mJsonObj) + { + throw PlayerJsonParseException(); + } +} + +PlayerJsonObject::~PlayerJsonObject() +{ + if (!mParent) + { + cJSON_Delete(mJsonObj); + } +} + +/** + * @brief Add a string value + */ +bool PlayerJsonObject::add(const std::string& name, const std::string& value, const ENCODING encoding) +{ + bool res = false; + + if (encoding == ENCODING_STRING) + { + res = add(name, cJSON_CreateString(value.c_str())); + } + else + { + res = add(name, std::vector(value.begin(), value.end()), encoding); + } + + return res; +} + +/** + * @brief Add a string value + */ +bool PlayerJsonObject::add(const std::string& name, const char *value, const ENCODING encoding) +{ + return add(name, std::string(value), encoding); +} + +/** + * @brief Add a vector of string values as a JSON array + */ +bool PlayerJsonObject::add(const std::string& name, const std::vector& values) +{ + bool res = false; + cJSON* arr = cJSON_CreateArray(); + + if (!arr) + { + // Failed to create array + } + else + { + res = true; + for (auto& value : values) + { + cJSON* string_item = cJSON_CreateString(value.c_str()); + + if (!string_item) + { + // Create string failed + res = false; + } + else if (!cJSON_AddItemToArray(arr, string_item)) + { + cJSON_Delete(string_item); + res = false; + } + else + { + // String added successfully + } + + if (!res) + { + // Adding an item failed + break; + } + } + + if (!res) + { + // Something failed + } + else if (!add(name, arr)) + { + // Adding the array to the json object failed + res = false; + } + else + { + // Array added successfully + } + + if (!res) + { + // Something failed, so delete whole array + cJSON_Delete(arr); + } + } + + return res; +} + +/** + * @brief Add the provided bytes after encoding in the specified encoding + */ +bool PlayerJsonObject::add(const std::string& name, const std::vector& values, const ENCODING encoding) +{ + bool res = false; + + switch (encoding) + { + case ENCODING_STRING: + { + std::string strValue(values.begin(), values.end()); + res = add(name, cJSON_CreateString(strValue.c_str())); + } + break; + + case ENCODING_BASE64: + { + const char *encodedResponse = base64_Encode( reinterpret_cast(values.data()), values.size()); + if (encodedResponse != NULL) + { + res = add(name, cJSON_CreateString(encodedResponse)); + free((void*)encodedResponse); + } + } + break; + + case ENCODING_BASE64_URL: + { + const char *encodedResponse = player_Base64_URL_Encode( reinterpret_cast(values.data()), values.size()); + if (encodedResponse != NULL) + { + res = add(name, cJSON_CreateString(encodedResponse)); + free((void*)encodedResponse); + } + } + break; + + default: + /* Unsupported encoding format */ + break; + } + + return res; +} + +/** + * @brief Add a #PlayerJsonObject value + */ +bool PlayerJsonObject::add(const std::string& name, PlayerJsonObject& value) +{ + bool res = false; + + if (cJSON_AddItemToObject(mJsonObj, name.c_str(), value.mJsonObj)) + { + value.mParent = this; + res = true; + } + + return res; +} + +/** + * @brief Add a vector of #PlayerJsonObject values as a JSON array + */ +bool PlayerJsonObject::add(const std::string& name, std::vector& values) +{ + bool res = false; + cJSON *arr = cJSON_CreateArray(); + + if (!arr) + { + // Failed to create array + } + else + { + res = true; + for (auto& obj : values) + { + if (!cJSON_AddItemToArray(arr, obj->mJsonObj)) + { + // Failed to add item to array + res = false; + break; + } + else + { + obj->mParent = this; + } + } + + if (!res) + { + // Something failed + } + else if (!add(name, arr)) + { + // Adding the array to the json object failed + res = false; + } + else + { + // Array added successfully + } + + if (!res) + { + // Something failed, so delete whole array + cJSON_Delete(arr); + } + } + + return res; +} + +/** + * @brief Add a cJSON value + */ +bool PlayerJsonObject::add(const std::string& name, cJSON *value) +{ + bool res = false; + + if (NULL == value) + { + // NULL parameter passed + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), value)) + { + // cJSON_AddItemToObject failed + } + else + { + res = true; + } + + return res; +} + + +/** + * @brief Add a bool value + */ +bool PlayerJsonObject::add(const std::string& name, bool value) +{ + bool res = false; + cJSON *bool_item = cJSON_CreateBool(value); + + if (!bool_item) + { + // Failed to create a bool + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), bool_item)) + { + cJSON_Delete(bool_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add an int value + */ +bool PlayerJsonObject::add(const std::string& name, int value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add a double value + */ +bool PlayerJsonObject::add(const std::string& name, double value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Add a long value + */ +bool PlayerJsonObject::add(const std::string& name, long value) +{ + bool res = false; + cJSON *number_item = cJSON_CreateNumber(value); + + if (!number_item) + { + // Failed to create a number + } + else if (!cJSON_AddItemToObject(mJsonObj, name.c_str(), number_item)) + { + cJSON_Delete(number_item); + } + else + { + res = true; + } + + return res; +} + +/** + * @brief Set cJSON value + */ +bool PlayerJsonObject::set(PlayerJsonObject *parent, cJSON *object) +{ + this->mParent = parent; + this->mJsonObj = object; + /**< return true always to match the template */ + return true; +} + +/** + * @brief Get the PlayerJson object from json data within the Json data + */ +bool PlayerJsonObject::get(const std::string& name, PlayerJsonObject &value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj) + { + retValue = value.set(this, strObj); + } + return retValue; +} + +/** + * @brief Get an array of objects from JSON data + * @param name name for the property + * @param[out] value JSON object array + * @return true if successfully retrieved, false otherwise + */ +bool PlayerJsonObject::get(const std::string& name, std::vector &values) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + cJSON *object = NULL; + bool retVal = true; + int idx = 0; + + values.clear(); + cJSON_ArrayForEach(object, strObj) + { + values.emplace_back(); + values[idx++].set(this, object); + } + return retVal; +} + +/** + * @brief Get a string value + */ +bool PlayerJsonObject::get(const std::string& name, std::string& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + + if (strObj) + { + char *strValue = cJSON_GetStringValue(strObj); + if (strValue) + { + value = strValue; + return true; + } + } + return false; +} + +/** + * @brief Get a int value from a JSON data + */ +bool PlayerJsonObject::get(const std::string& name, int& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj) + { + // TODO: replace with cJSON_GetNumberValue(strObj); + value = (int)strObj->valuedouble; + retValue = true; + } + return retValue; +} + +/** + * @brief Get a double value from JSON data + * @param name name for the property + * @param[out] value double value + * @return true if successfully retrieved, false otherwise + */ +bool PlayerJsonObject::get(const std::string& name, double& value) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retValue = false; + if (strObj && cJSON_IsNumber(strObj)) + { + value = strObj->valuedouble; + retValue = true; + } + return retValue; +} + +/** + * @brief Get a string value + */ +bool PlayerJsonObject::get(const std::string& name, std::vector& values) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + cJSON *object = NULL; + bool retVal = false; + cJSON_ArrayForEach(object, strObj) + { + char *strValue = cJSON_GetStringValue(object); + if (strValue) + { + values.push_back(std::string(strValue)); + retVal = true; + } + } + return retVal; +} + +/** + * @brief convert blob of binary data to ascii base64-URL-encoded equivalent + * @retval pointer to malloc'd cstring containing base64 URL encoded version of string + * @retval NULL if insufficient memory to allocate base64-URL-encoded copy + * @note caller responsible for freeing returned cstring + */ +char *player_Base64_URL_Encode(const unsigned char *src, size_t len) +{ + char *rc = base64_Encode(src,len); + if( rc ) + { + char *dst = rc; + while( *dst ) + { + switch( *dst ) + { + case '+': + *dst = '-'; + break; + case '/': + *dst = '_'; + break; + case '=': + *dst = '\0'; + break; + default: + break; + } + dst++; + } + } + return rc; +} + +/** + * @brief decode base64 URL encoded data to binary equivalent + * @retval pointer to malloc'd memory containing decoded binary data + * @retval NULL if insufficient memory to allocate decoded data + * @note caller responsible for freeing returned data + */ +unsigned char *player_Base64_URL_Decode(const char *src, size_t *len, size_t srcLen) +{ + unsigned char * rc = NULL; + char *temp = (char *)malloc(srcLen+3); + if( temp ) + { + temp[srcLen+2] = '\0'; + temp[srcLen+1] = '='; + temp[srcLen+0] = '='; + for( int iter = 0; iter < srcLen; iter++ ) + { + char c = src[iter]; + switch( c ) + { + case '_': + c = '/'; + break; + case '-': + c = '+'; + break; + default: + break; + } + temp[iter] = c; + } + rc = base64_Decode(temp, len ); + free(temp); + } + else + { + *len = 0; + } + return rc; +} + + +/** + * @brief Get a string value as a vector of bytes + */ +bool PlayerJsonObject::get(const std::string& name, std::vector& values, const ENCODING encoding) +{ + bool res = false; + std::string strValue; + + if (get(name, strValue)) + { + values.clear(); + + switch (encoding) + { + case ENCODING_STRING: + { + values.insert(values.begin(), strValue.begin(), strValue.end()); + } + break; + + case ENCODING_BASE64: + { + size_t decodedSize = 0; + const unsigned char *decodedResponse = base64_Decode(strValue.c_str(), &decodedSize, strValue.length()); + if (decodedResponse != NULL) + { + values.insert(values.begin(), decodedResponse, decodedResponse + decodedSize); + res = true; + free((void *)decodedResponse); + } + } + break; + + case ENCODING_BASE64_URL: + { + size_t decodedSize = 0; + const unsigned char *decodedResponse = player_Base64_URL_Decode(strValue.c_str(), &decodedSize, strValue.length()); + if (decodedResponse != NULL) + { + values.insert(values.begin(), decodedResponse, decodedResponse + decodedSize); + res = true; + free((void *)decodedResponse); + } + } + break; + + default: + /* Unsupported encoding format */ + break; + } + } + return res; +} + +/** + * @brief Print the constructed JSON to a string + */ +std::string PlayerJsonObject::print() +{ + char *jsonString = cJSON_Print(mJsonObj); + if (NULL != jsonString) + { + std::string retStr(jsonString); + cJSON_free(jsonString); + return retStr; + } + return ""; +} + +/** + * @brief Print the constructed JSON to a string + */ +std::string PlayerJsonObject::print_UnFormatted() +{ + char *jsonString = cJSON_PrintUnformatted(mJsonObj); + if (NULL != jsonString) + { + std::string retStr(jsonString); + cJSON_free(jsonString); + return retStr; + } + return ""; +} + +/** + * @brief Print the constructed JSON into the provided vector + */ +void PlayerJsonObject::print(std::vector& data) +{ + std::string jsonOutputStr = print(); + (void)data.insert(data.begin(), jsonOutputStr.begin(), jsonOutputStr.end()); +} + +/** + * @brief Check whether the value is Array or not + */ +bool PlayerJsonObject::isArray(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsArray(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is String or not + */ +bool PlayerJsonObject::isString(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsString(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is Number or not + */ +bool PlayerJsonObject::isNumber(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsNumber(strObj); + } + return retVal; +} + +/** + * @brief Check whether the value is Object or not + */ +bool PlayerJsonObject::isObject(const std::string& name) +{ + cJSON *strObj = cJSON_GetObjectItem(mJsonObj, name.c_str()); + bool retVal = false; + if (strObj) + { + retVal = cJSON_IsObject(strObj); + } + return retVal; +} + diff --git a/middleware/playerJsonObject/PlayerJsonObject.h b/middleware/playerJsonObject/PlayerJsonObject.h new file mode 100644 index 000000000..6facf0049 --- /dev/null +++ b/middleware/playerJsonObject/PlayerJsonObject.h @@ -0,0 +1,280 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +#ifndef _PLAYER_JSON_OBJECT_H +#define _PLAYER_JSON_OBJECT_H + +/** + * @file PlayerJsonObject.h + * @brief File to handle Json format + */ + + +#include +#include +#include // for uint8_t + +#include + +/** + * @class PlayerJsonObject + * @brief Utility class to construct a JSON string + */ +class PlayerJsonObject { +public: + PlayerJsonObject(); + PlayerJsonObject(const std::string& jsonStr); + PlayerJsonObject(const char* jsonStr); + ~PlayerJsonObject(); + PlayerJsonObject(const PlayerJsonObject&) = delete; + PlayerJsonObject& operator=(const PlayerJsonObject&) = delete; + PlayerJsonObject(PlayerJsonObject &&) = default; + + enum ENCODING + { + ENCODING_STRING, /**< Bytes encoded as a string as-is */ + ENCODING_BASE64, /**< Bytes base64 encoded to a string */ + ENCODING_BASE64_URL /**< Bytes base64url encoded to a string */ + }; + + /** + * @fn add + * + * @param name name for the value + * @param value string value to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::string& value, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the value + * @param value string value to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const char *value, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the array + * @param values vector of strings to add as an array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::vector& values); + + /** + * @fn add + * + * @param name name for the value + * @param values vector of bytes to add in the specified encoding + * @param encoding how to encode the byte array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, const std::vector& values, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn add + * + * @param name name for the array + * @param values vector of #PlayerJsonObject to add as an array + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, std::vector& values); + + /** + * @fn get + * + * @param name name for the array + * @param[out] values String Array + * @return true if successfully added, false otherwise + */ + bool get(const std::string& name, std::vector& values); + + /** + * @fn get + * + * @param name name for the property + * @param[out] values int value + * @return true if successfully added, false otherwise + */ + bool get(const std::string& name, int& value); + + /** + * @brief Get a double value from JSON data + * @param name name for the property + * @param[out] value double value + * @return true if successfully retrieved, false otherwise + */ + bool get(const std::string& name, double& value); + + /** + * @fn get + * + * @param name name of the property to retrieve + * @param value string to populate with the retrieved value + * @return true if successfully retrieved value, false otherwise + */ + bool get(const std::string& name, std::string& value); + + /** + * @fn fn + * + * @param name name of the property to retrieve + * @param values vector to populate with the retrieved value + * @param encoding the encoding of the string, used to determine how to decode the content into the vector + * @return true if successfully retrieved and decoded value, false otherwise + */ + bool get(const std::string& name, std::vector& values, const ENCODING encoding = ENCODING_STRING); + + /** + * @fn get + * + * @param name Name of the internal json data + * @param value[out] reference Object which return as json object inside json data. + * @return true if successfully retrieved and decoded value, false otherwise + */ + bool get(const std::string& name, PlayerJsonObject &value); + + /** + * @brief Get an array of objects from JSON data + * @param name name for the property + * @param[out] value JSON object array + * @return true if successfully retrieved, false otherwise + */ + bool get(const std::string& name, std::vector &values); + + /** + * @fn print + * + * @return JSON string + */ + std::string print(); + + + /** + * @fn print_UnFormatted + * + * @return JSON string + */ + std::string print_UnFormatted(); + + /** + * @fn print + * + * @param[out] data - vector to output printed JSON to + * @return void. + */ + void print(std::vector& data); + /** + * @fn add + * + * @param name name for the value + * @param value bool to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, bool value); + /** + * @fn add + * + * @param name name for the value + * @param value int to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, int value); + + /** + * @fn add + * + * @param name name for the value + * @param value double to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, double value); + + /** + * @fn add + * + * @param name name for the value + * @param value long to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, long value); + + bool add(const std::string& name, PlayerJsonObject& value); + + /** + * @fn add + * + * @param name name for the value + * @param value long to add + * @return true if successfully added, false otherwise + */ + bool add(const std::string& name, cJSON *value); + + /** + * @fn isArray + * + * @return true if it is Array or false + */ + bool isArray(const std::string& name); + + /** + * @fn isString + * + * @return true if it is String or false + */ + bool isString(const std::string& name); + + /** + * @fn isNumber + * + * @return true if it is Number or false + */ + bool isNumber(const std::string& name); + + /** + * @fn isObject + * + * @return true if it is Object or false + */ + bool isObject(const std::string& name); + +private: + bool set(PlayerJsonObject *parent, cJSON *object); + bool add(const std::string& name); + cJSON *mJsonObj; + PlayerJsonObject *mParent; +}; + +/** + * @class PlayerJsonParseException + * @brief Handles the exception for JSON parser + */ +class PlayerJsonParseException : public std::exception +{ +public: + virtual const char* what() const throw() override + { + return "Failed to parse JSON string"; + } +}; + +#endif //_PLAYER_JSON_OBJECT_H + diff --git a/middleware/playerLogManager/CMakeLists.txt b/middleware/playerLogManager/CMakeLists.txt new file mode 100644 index 000000000..998458600 --- /dev/null +++ b/middleware/playerLogManager/CMakeLists.txt @@ -0,0 +1,58 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.5) + +project(playerlogmanager) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include_directories(..) + +if(CMAKE_SYSTEMD_JOURNAL) + message("CMAKE_SYSTEMD_JOURNAL set") + set(LIBPLAYERLOGMANAGER_DEPENDS "${LIBPLAYERLOGMANAGER_DEPENDS} -lsystemd") + set(LIBPLAYERLOGMANAGER_DEFINES "${LIBPLAYERLOGMANAGER_DEFINES} -DUSE_SYSTEMD_JOURNAL_PRINT=1") +endif() + +if(CMAKE_USE_ETHAN_LOG) + message("DCMAKE_USE_ETHAN_LOG set") +# Find the ethanlog library for container logger + find_package( EthanLog REQUIRED ) +# Add the include directories for EthanLog + include_directories(${ETHANLOG_INCLUDE_DIRS}) + + set(LIBPLAYERLOGMANAGER_DEPENDS "${LIBPLAYERLOGMANAGER_DEPENDS} -lethanlog") + set(LIBPLAYERLOGMANAGER_DEFINES "${LIBPLAYERLOGMANAGER_DEFINES} -DUSE_ETHAN_LOG=1") +endif() + +set(PlayerLogManager_SRC PlayerLogManager.cpp) + +add_library(playerlogmanager SHARED ${PlayerLogManager_SRC}) + +set_target_properties(playerlogmanager PROPERTIES PUBLIC_HEADER "PlayerLogManager.h") + +set_target_properties(playerlogmanager PROPERTIES COMPILE_FLAGS "${LIBPLAYERLOGMANAGER_DEFINES}") + +string(STRIP "${LIBPLAYERLOGMANAGER_DEPENDS}" LIBPLAYERLOGMANAGER_DEPENDS) + +target_link_libraries(playerlogmanager ${LIBPLAYERLOGMANAGER_DEPENDS}) + +# Install the library and its headers +install(TARGETS playerlogmanager + DESTINATION lib + PUBLIC_HEADER DESTINATION include) diff --git a/middleware/playerLogManager/PlayerLogManager.cpp b/middleware/playerLogManager/PlayerLogManager.cpp new file mode 100644 index 000000000..1508ac127 --- /dev/null +++ b/middleware/playerLogManager/PlayerLogManager.cpp @@ -0,0 +1,203 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file PlayerLogManager.cpp + * @brief Interface Player logging mechanism source file + */ + +#include +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" +#include "PlayerUtils.h" + +#ifdef USE_ETHAN_LOG +#include +#else +// stubs for use if USE_ETHAN_LOG not defined +static void vethanlog(int level, const char *filename, const char *function, int line, const char *format, va_list ap){} +#define ETHAN_LOG_INFO 0 +#define ETHAN_LOG_DEBUG 1 +#define ETHAN_LOG_WARNING 2 +#define ETHAN_LOG_ERROR 3 +#define ETHAN_LOG_FATAL 4 +#define ETHAN_LOG_MILESTONE 5 +#endif + +#ifdef USE_SYSTEMD_JOURNAL_PRINT +#include +#else +// stub for OSX, where sd_journal_print not available +#define LOG_NOTICE 0 +static void sd_journal_printv(int priority, const char *format, va_list arg ){ + size_t fmt_len = strlen(format); + char *fmt_with_newline = (char *)malloc(fmt_len+2); + if( fmt_with_newline ) + { + memcpy( fmt_with_newline, format, fmt_len ); + fmt_with_newline[fmt_len++] = '\n'; + fmt_with_newline[fmt_len++] = 0x00; + vprintf(fmt_with_newline,arg); + free( fmt_with_newline ); + } +} +#endif + +using namespace std; + +static const char *mLogLevelStr[mLOGLEVEL_ERROR+1] = +{ + "TRACE", // mLOGLEVEL_TRACE + "DEBUG", // mLOGLEVEL_DEBUG + "INFO", // mLOGLEVEL_INFO + "WARN", // mLOGLEVEL_WARN + "MIL", // mLOGLEVEL_MIL + "ERROR", // mLOGLEVEL_ERROR +}; + +MW_LogLevel PlayerLogManager::mwLoglevel = mLOGLEVEL_WARN; +bool PlayerLogManager::locked = false; +bool PlayerLogManager::disableLogRedirection = false; +bool PlayerLogManager::enableEthanLogRedirection = false; + +static std::hash std_thread_hasher; +std::size_t GetPlayerPrintableThreadID( void ) +{ + return std_thread_hasher( std::this_thread::get_id() ); +} +/** + * @brief Print logs to console / log file + */ +void logprintf(MW_LogLevel logLevelIndex, const char* file, int line, const char *format, ...) +{ + char timestamp[MW_CLI_TIMESTAMP_PREFIX_MAX_CHARS]; + timestamp[0] = 0x00; + if( PlayerLogManager::disableLogRedirection ) + { // add timestamp if not using sd_journal_print + struct timeval t; + gettimeofday(&t, NULL); + snprintf(timestamp, sizeof(timestamp), MW_CLI_TIMESTAMP_PREFIX_FORMAT, (unsigned int)t.tv_sec, (unsigned int)t.tv_usec / 1000 ); + } + char *format_ptr = NULL; + int format_bytes = 0; + for( int pass=0; pass<2; pass++ ) + { + format_bytes = snprintf(format_ptr, format_bytes, + "%s[PLAYER_IF][%s][%zx][%s][%d]%s\n", + timestamp, + mLogLevelStr[logLevelIndex], + GetPlayerPrintableThreadID(), + file, line, + format ); + if( format_bytes<=0 ) + { // should never happen! + break; + } + if( pass==0 ) + { + format_bytes++; // include nul terminator + format_ptr = (char *)alloca(format_bytes); // allocate on stack + } + else + { + va_list args; + va_start(args, format); + if( PlayerLogManager::disableLogRedirection ) + { // cli + vprintf( format_ptr, args ); + } + else if ( PlayerLogManager::enableEthanLogRedirection ) + { // remap MW log levels to Ethan log levels + int ethanLogLevel; + // Important: in production builds, Ethan logger filters out everything + // except ETHAN_LOG_MILESTONE and ETHAN_LOG_FATAL + switch (logLevelIndex) + { + case mLOGLEVEL_TRACE: + case mLOGLEVEL_DEBUG: + ethanLogLevel = ETHAN_LOG_DEBUG; + break; + + case mLOGLEVEL_ERROR: + ethanLogLevel = ETHAN_LOG_FATAL; + break; + + case mLOGLEVEL_INFO: // note: we rely on eLOGLEVEL_INFO at tune time for triage + case mLOGLEVEL_WARN: + case mLOGLEVEL_MIL: + default: + ethanLogLevel = ETHAN_LOG_MILESTONE; + break; + } + vethanlog(ethanLogLevel,NULL,NULL,-1,format_ptr, args); + } + else + { + format_ptr[format_bytes-1] = 0x00; // strip not-needed newline (good for Ethan Logger, too?) + sd_journal_printv(LOG_NOTICE,format_ptr,args); // note: truncates to 2040 characters + } + va_end(args); + } + } +} + +/** + * @brief Compactly log blobs of binary data + * + */ +void DumpBinaryBlob(const unsigned char *ptr, size_t len) +{ +#define FIT_CHARS 64 + char buf[FIT_CHARS + 1]; // pad for NUL + char *dst = buf; + const unsigned char *fin = ptr+len; + int fit = FIT_CHARS; + while (ptr < fin) + { + unsigned char c = *ptr++; + if (c >= ' ' && c < 128) + { // printable ascii + *dst++ = c; + fit--; + } + else if( fit>=4 ) + { + *dst++ = '['; + WRITE_HASCII( dst, c ); + *dst++ = ']'; + fit -= 4; + } + else + { + fit = 0; + } + if (fit==0 || ptr==fin ) + { + *dst++ = 0x00; + + MW_LOG_WARN("%s", buf); + dst = buf; + fit = FIT_CHARS; + } + } +} diff --git a/middleware/playerLogManager/PlayerLogManager.h b/middleware/playerLogManager/PlayerLogManager.h new file mode 100644 index 000000000..1a906f1da --- /dev/null +++ b/middleware/playerLogManager/PlayerLogManager.h @@ -0,0 +1,167 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef PLAYER_LOG_MANAGER_H +#define PLAYER_LOG_MANAGER_H + +/** + * @file PlayerLogManager.h + * @brief Log manager for Player Interface + */ + + +#include +#include +#include +#include +#include +#include +/** + * @brief Log level's of Middleware + */ +enum MW_LogLevel +{ + mLOGLEVEL_TRACE, /**< Trace level */ + mLOGLEVEL_DEBUG, /**< Debug level */ + mLOGLEVEL_INFO, /**< Info level */ + mLOGLEVEL_WARN, /**< Warn level */ + mLOGLEVEL_MIL, /**< Milestone level */ + mLOGLEVEL_ERROR, /**< Error level */ +}; + +/** + * @class PlayerLogManager + * @brief PlayerLogManager Class + */ +class PlayerLogManager +{ +public : + static MW_LogLevel mwLoglevel; + static bool locked; + static bool disableLogRedirection; /**< disables log re-direction to journal or ethan log apis and uses vprintf - used by simulators */ + static bool enableEthanLogRedirection; /**< Enables Ethan log redirection which uses Ethan lib for logging */ + + /** + * @brief Sets the externals logger information. + * + * @param[in] logRedirectStatus Status of log redirection. + * @param[in] ethanLogStatus Status of Ethan logging. + * @param[in] level Log level. + * @param[in] lock Lock status. + */ + static void SetLoggerInfo(bool logRedirectStatus, bool ethanLogStatus, int level, bool lock) + { + PlayerLogManager::disableLogRedirection = logRedirectStatus; + PlayerLogManager::enableEthanLogRedirection = ethanLogStatus; + PlayerLogManager::setLogLevel(MW_LogLevel(level)); + PlayerLogManager::lockLogLevel(lock); + } + + /** + * @fn isLogLevelAllowed + * + * @param[in] chkLevel - log level + * @retval true if the log level allowed for print mechanism + */ + static bool isLogLevelAllowed(MW_LogLevel chkLevel) + { + return (chkLevel>=mwLoglevel); + } + /** + * @fn setLogLevel + * + * @param[in] newLevel - log level new value + * @return void + */ + static void setLogLevel(MW_LogLevel newLevel) + { + if( !locked ) + { + mwLoglevel = newLevel; + } + } + /** + * @brief lock or unlock log level. This allows (for example) logging to be locked to info or trace, so that "more verbose while tuning, less verbose after tune complete" behavior doesn't override desired log level used for debugging. This is also used as part of aampcli "noisy" and "quiet" command handling. + * + * @param lock if true, subsequent calls to setLogLevel will be ignored + */ + static void lockLogLevel( bool lock ) + { + locked = lock; + } + /** + * @fn getHexDebugStr + */ + static std::string getHexDebugStr(const std::vector& data) + { + std::ostringstream hexSs; + hexSs << "0x"; + hexSs << std::hex << std::uppercase << std::setfill('0'); + std::for_each(data.cbegin(), data.cend(), [&](int c) { hexSs << std::setw(2) << c; }); + return hexSs.str(); + } + +}; +/** + * @fn DumpBinaryBlob + * + * @param[in] ptr to the buffer + * @param[in] len length of buffer + * + * @return void + */ +void DumpBinaryBlob(const unsigned char *ptr, size_t len); +/** + * @fn logprintf + * @param[in] format - printf style string + * @return void + */ +extern void logprintf(MW_LogLevel logLevelIndex, const char* file, int line, const char *format, ...) __attribute__ ((format (printf, 4, 5))); + +#define MW_CLI_TIMESTAMP_PREFIX_MAX_CHARS 20 +#define MW_CLI_TIMESTAMP_PREFIX_FORMAT "%u.%03u: " + +#define MW_LOG( LEVEL, FORMAT, ... ) \ +do{\ +if( (LEVEL) >= PlayerLogManager::mwLoglevel ) \ +{ \ + logprintf( LEVEL, __FUNCTION__, __LINE__, FORMAT, ##__VA_ARGS__); \ +}\ +}while(0) + +#ifdef MW_LOG_TRACE +// avoid compile time macro redefinition warnings due to defines in SubtecChannel.cpp +#undef MW_LOG_TRACE +#undef MW_LOG_DEBUG +#undef MW_LOG_INFO +#undef MW_LOG_WARN +#undef MW_LOG_MIL +#undef MW_LOG_ERR +#endif + +/** + * @brief Middleware logging defines, this can be enabled through setLogLevel() as per the need + */ +#define MW_LOG_TRACE(FORMAT, ...) MW_LOG(mLOGLEVEL_TRACE, FORMAT, ##__VA_ARGS__) +#define MW_LOG_DEBUG(FORMAT, ...) MW_LOG(mLOGLEVEL_DEBUG, FORMAT, ##__VA_ARGS__) +#define MW_LOG_INFO(FORMAT, ...) MW_LOG(mLOGLEVEL_INFO, FORMAT, ##__VA_ARGS__) +#define MW_LOG_WARN(FORMAT, ...) MW_LOG(mLOGLEVEL_WARN, FORMAT, ##__VA_ARGS__) +#define MW_LOG_MIL(FORMAT, ...) MW_LOG(mLOGLEVEL_MIL, FORMAT, ##__VA_ARGS__) +#define MW_LOG_ERR(FORMAT, ...) MW_LOG(mLOGLEVEL_ERROR, FORMAT, ##__VA_ARGS__) + +#endif /* PLAYER_LOG_MANAGER_H */ diff --git a/middleware/playerisobmff/playerisobmffbox.cpp b/middleware/playerisobmff/playerisobmffbox.cpp new file mode 100644 index 000000000..6a62d3b4d --- /dev/null +++ b/middleware/playerisobmff/playerisobmffbox.cpp @@ -0,0 +1,1515 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file isobmffbox.cpp +* @brief Source file for ISO Base Media File Format Boxes +*/ + +#include "playerisobmffbox.h" +#include +#include + +#define READ_BMDT64(buf) \ + ReadUint64FromBuffer(buf); buf+=8; + +#define READ_64(buf) \ + ReadUint64FromBuffer(buf); buf+=8; + +#define IS_TYPE(value, type) \ + (value[0]==type[0] && value[1]==type[1] && value[2]==type[2] && value[3]==type[3]) + +const uint32_t TRUN_FLAG_DATA_OFFSET_PRESENT = 0x0001; +const uint32_t TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT = 0x0004; +const uint32_t TRUN_FLAG_SAMPLE_DURATION_PRESENT = 0x0100; +const uint32_t TRUN_FLAG_SAMPLE_SIZE_PRESENT = 0x0200; +const uint32_t TRUN_FLAG_SAMPLE_FLAGS_PRESENT = 0x0400; +const uint32_t TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT = 0x0800; + +const uint32_t TFHD_FLAG_BASE_DATA_OFFSET_PRESENT = 0x00001; +const uint32_t TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT = 0x00002; +const uint32_t TFHD_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT = 0x00008; +const uint32_t TFHD_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT = 0x00010; +// const uint32_t TFHD_FLAG_DEFAULT_SAMPLE_FLAGS_PRESENT = 0x00020; +// const uint32_t TFHD_FLAG_DURATION_IS_EMPTY = 0x10000; +// const uint32_t TFHD_FLAG_DEFAULT_BASE_IS_MOOF = 0x20000; + +const uint32_t SAIZ_FLAG_AUX_INFO_TYPE_PRESENT = 0x0001; + +/** + * @brief Read a string from buffer and return it + */ +int ReadCStringLenFromBuffer(const uint8_t* buffer, uint32_t bufferLen) +{ + int retLen = -1; + if(buffer && bufferLen > 0) + { + for(int i=0; i < bufferLen; i++) + { + if(buffer[i] == '\0') + { + retLen = i+1; + break; + } + } + } + return retLen; +} + +/** + * @brief Utility function to read 8 bytes from a buffer + */ +uint64_t ReadUint64FromBuffer(uint8_t *buf) +{ + uint64_t val = PLAYER_READ_U32(buf); + val = (val<<32) | (uint32_t)PLAYER_READ_U32(buf); + return val; +} + +/** + * @brief Utility function to write 8 bytes to a buffer + */ +void WriteUint64ToBuffer(uint8_t *dst, uint64_t val) +{ + uint32_t msw = (uint32_t)(val>>32); + PLAYER_WRITE_U32(dst, msw); dst+=4; + PLAYER_WRITE_U32(dst, val); +} + +namespace player_isobmff { +/** + * @brief Box constructor + */ +IsoBmffBox::IsoBmffBox(uint32_t sz, const char btype[4]) : offset(0), size(sz), type{}, base(nullptr) +{ + memcpy(type,btype,4); +} + +/** + * @brief Set box's offset from the beginning of the buffer + */ +void IsoBmffBox::setOffset(uint32_t os) +{ + offset = os; +} + +/** + * @brief Get box offset + */ +uint32_t IsoBmffBox::getOffset() const +{ + return offset; +} + +/** + * @brief To check if box has any child boxes + */ +bool IsoBmffBox::hasChildren() const +{ + return false; +} + +/** + * @brief Get children of this box + */ +const std::vector *IsoBmffBox::getChildren() const +{ + return NULL; +} + +/** + * @brief Get box size + */ +uint32_t IsoBmffBox::getSize() const +{ + return size; +} + +/** + * @brief Get box type + */ +const char *IsoBmffBox::getType() const +{ + return type; +} + +/** + * @fn constructBox + * @brief Static function to construct a Box object + * @param[in] hdr - pointer to box + * @param[in] maxSz - box size + * @param[in] correctBoxSize - flag to check if box size needs to be corrected + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed Box object + */ +IsoBmffBox* IsoBmffBox::constructBox(uint8_t *hdr, uint32_t maxSz, bool correctBoxSize, int newTrackId) +{ + L_RESTART: + uint8_t *hdr_start = hdr; + + uint32_t size = 0; + uint8_t type[5]; + if(maxSz < 4) + { + MW_LOG_TRACE("Box data < 4 bytes. Can't determine Size & Type"); + return new IsoBmffBox(maxSz, (const char *)"UKWN"); + } + else if(maxSz >= 4 && maxSz < 8) + { + MW_LOG_TRACE("Box Size between >4 but <8 bytes. Can't determine Type"); + //size = PLAYER_READ_U32(hdr); + return new IsoBmffBox(maxSz, (const char *)"UKWN"); + } + else + { + size = PLAYER_READ_U32(hdr); + PLAYER_READ_U8(type, hdr, 4); + type[4] = '\0'; + } + + if (size > maxSz) + { + if(correctBoxSize) + { + //Fix box size to handle cases like receiving whole file for HTTP range requests + MW_LOG_WARN("Box[%s] fixing size error:size[%u] > maxSz[%u]", type, size, maxSz); + hdr = hdr_start; + PLAYER_WRITE_U32(hdr,maxSz); + goto L_RESTART; + } + else + { +#ifdef PLAYER_DEBUG_BOX_CONSTRUCT + MW_LOG_WARN("Box[%s] Size error:size[%u] > maxSz[%u]",type, size, maxSz); +#endif + } + } + else if (IS_TYPE(type, MOOV)) + { + return GenericContainerIsoBmffBox::constructContainer(size, MOOV, hdr, newTrackId); + } + else if (IS_TYPE(type, TRAK)) + { + return TrakIsoBmffBox::constructTrakBox(size,hdr, newTrackId); + } + else if (IS_TYPE(type, MDIA)) + { + return GenericContainerIsoBmffBox::constructContainer(size, MDIA, hdr, newTrackId); + } + else if (IS_TYPE(type, MOOF)) + { + return GenericContainerIsoBmffBox::constructContainer(size, MOOF, hdr, newTrackId); + } + else if (IS_TYPE(type, TRAF)) + { + return GenericContainerIsoBmffBox::constructContainer(size, TRAF, hdr, newTrackId); + } + else if (IS_TYPE(type, TFHD)) + { + return TfhdIsoBmffBox::constructTfhdBox(size, hdr); + } + else if (IS_TYPE(type, TFDT)) + { + return TfdtIsoBmffBox::constructTfdtBox(size, hdr); + } + else if (IS_TYPE(type, TRUN)) + { + return TrunIsoBmffBox::constructTrunBox(size, hdr); + } + else if (IS_TYPE(type, MVHD)) + { + return MvhdIsoBmffBox::constructMvhdBox(size, hdr); + } + else if (IS_TYPE(type, MDHD)) + { + return MdhdIsoBmffBox::constructMdhdBox(size, hdr); + } + else if (IS_TYPE(type, EMSG)) + { + return EmsgIsoBmffBox::constructEmsgBox(size, hdr); + } + else if (IS_TYPE(type, PRFT)) + { + return PrftIsoBmffBox::constructPrftBox(size, hdr); + } + else if( IS_TYPE(type, SIDX) ) + { + return SidxIsoBmffBox::constructSidxBox(size, hdr); + } + else if (IS_TYPE(type, MDAT)) + { + return MdatIsoBmffBox::constructMdatBox(size, hdr); + } + else if (IS_TYPE(type, SENC)) + { + return SencIsoBmffBox::constructSencBox(size, hdr); + } + else if (IS_TYPE(type, SAIZ)) + { + return SaizIsoBmffBox::constructSaizBox(size, hdr); + } + + return new IsoBmffBox(size, (const char *)type); +} + +/** + * @brief rewriteAsSkipBox - overwrite buffer data and rewrte box type as skip + * @params none + * @return none + */ +void IsoBmffBox::rewriteAsSkipBox(void) +{ + // buffer + memcpy(base + 4, IsoBmffBox::SKIP, 4); + // internal type + memcpy(type, IsoBmffBox::SKIP, 5); +} + +/** + * @brief GenericContainerBox constructor + */ +GenericContainerIsoBmffBox::GenericContainerIsoBmffBox(uint32_t sz, const char btype[4]) : IsoBmffBox(sz, btype), children() +{ + +} + +/** + * @brief GenericContainerBox destructor + */ +GenericContainerIsoBmffBox::~GenericContainerIsoBmffBox() +{ + for (unsigned int i = (unsigned int)children.size(); i>0;) + { + --i; + SAFE_DELETE(children.at(i)); + children.pop_back(); + } + children.clear(); +} + +/** + * @brief Add a box as a child box + */ +void GenericContainerIsoBmffBox::addChildren(IsoBmffBox *box) +{ + children.push_back(box); +} + +/** + * @brief To check if box has any child boxes + */ +bool GenericContainerIsoBmffBox::hasChildren() const +{ + return true; +} + +/** + * @brief Get children of this box + */ +const std::vector *GenericContainerIsoBmffBox::getChildren() const +{ + return &children; +} + + +/** + * @fn constructContainer + * @brief Static function to construct a GenericContainerBox object + * @param[in] sz - box size + * @param[in] btype - box type + * @param[in] ptr - pointer to box + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed GenericContainerBox object + */ +GenericContainerIsoBmffBox* GenericContainerIsoBmffBox::constructContainer(uint32_t sz, const char btype[4], uint8_t *ptr, int newTrackId) +{ + GenericContainerIsoBmffBox *cbox = new GenericContainerIsoBmffBox(sz, btype); + uint32_t curOffset = sizeof(uint32_t) + sizeof(uint32_t); //Sizes of size & type fields + while (curOffset < sz) + { + IsoBmffBox *box = IsoBmffBox::constructBox(ptr, sz-curOffset, false, newTrackId ); + box->setOffset(curOffset); + cbox->addChildren(box); + curOffset += box->getSize(); + ptr += box->getSize(); + } + return cbox; +} + +/** + * @brief FullBox constructor + */ +FullIsoBmffBox::FullIsoBmffBox(uint32_t sz, const char btype[4], uint8_t ver, uint32_t f) : IsoBmffBox(sz, btype), version(ver), flags(f) +{ + +} + +/** + * @brief MvhdBox constructor + */ +MvhdIsoBmffBox::MvhdIsoBmffBox(uint32_t sz, uint32_t tScale, uint8_t* tScale_loc) : FullIsoBmffBox(sz, IsoBmffBox::MVHD, 0, 0), timeScale(tScale), timeScale_loc(tScale_loc) +{ + +} + +/** + * @brief MvhdBox constructor + */ +MvhdIsoBmffBox::MvhdIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint8_t* tScale_loc) : FullIsoBmffBox(fbox), timeScale(tScale), timeScale_loc(tScale_loc) +{ + +} + +/** + * @brief Set TimeScale value + */ +void MvhdIsoBmffBox::setTimeScale(uint32_t tScale) +{ + timeScale = tScale; + if (nullptr != timeScale_loc) + { + PLAYER_WRITE_U32(timeScale_loc, tScale); + } +} + +/** + * @brief Get TimeScale value + */ +uint32_t MvhdIsoBmffBox::getTimeScale() +{ + return timeScale; +} + +/** + * @brief Static function to construct a MvhdIsoBmffBox object + */ +MvhdIsoBmffBox* MvhdIsoBmffBox::constructMvhdBox(uint32_t sz, uint8_t *ptr) +{ + auto start{ptr}; + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t flags = PLAYER_READ_FLAGS(ptr); + uint32_t tScale; + + uint32_t skip = sizeof(uint32_t)*2; + if (1 == version) + { + //Skipping creation_time &modification_time + skip = sizeof(uint64_t)*2; + } + ptr += skip; + + uint8_t* tScale_loc{ptr}; + tScale = PLAYER_READ_U32(ptr); + + FullIsoBmffBox fbox(sz, IsoBmffBox::MVHD, version, flags); + fbox.setBase(start); + return new MvhdIsoBmffBox(fbox, tScale, tScale_loc); +} + +/** + * @brief MdhdBox constructor + */ +MdhdIsoBmffBox::MdhdIsoBmffBox(uint32_t sz, uint32_t tScale, uint8_t* tScale_loc) : FullIsoBmffBox(sz, IsoBmffBox::MDHD, 0, 0), timeScale(tScale), timeScale_loc(tScale_loc) +{ + +} + +/** + * @brief MdhdIsoBmffBox constructor + */ +MdhdIsoBmffBox::MdhdIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint8_t* tScale_loc) : FullIsoBmffBox(fbox), timeScale(tScale), timeScale_loc(tScale_loc) +{ + +} + +/** + * @brief Set TimeScale value + */ +void MdhdIsoBmffBox::setTimeScale(uint32_t tScale) +{ + timeScale = tScale; + if (nullptr != timeScale_loc) + { + PLAYER_WRITE_U32(timeScale_loc, tScale); + } +} + +/** + * @brief Get TimeScale value + */ +uint32_t MdhdIsoBmffBox::getTimeScale() +{ + return timeScale; +} + +/** + * @brief Static function to construct a MdhdIsoBmffBox object + */ +MdhdIsoBmffBox* MdhdIsoBmffBox::constructMdhdBox(uint32_t sz, uint8_t *ptr) +{ + auto start{ptr}; + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t flags = PLAYER_READ_FLAGS(ptr); + uint32_t tScale; + + uint32_t skip = sizeof(uint32_t)*2; + if (1 == version) + { + //Skipping creation_time &modification_time + skip = sizeof(uint64_t)*2; + } + ptr += skip; + + uint8_t* tScale_loc{ptr}; + tScale = PLAYER_READ_U32(ptr); + + FullIsoBmffBox fbox(sz, IsoBmffBox::MDHD, version, flags); + fbox.setBase(start); + return new MdhdIsoBmffBox(fbox, tScale, tScale_loc); +} + +/** + * @brief TfdtIsoBmffBox constructor + */ +TfdtIsoBmffBox::TfdtIsoBmffBox(uint32_t sz, uint64_t mdt, uint8_t* mdt_loc) : FullIsoBmffBox(sz, IsoBmffBox::TFDT, 0, 0), baseMDT(mdt), baseMDT_loc(mdt_loc) +{ + +} + +/** + * @brief TfdtIsoBmffBox constructor + */ +TfdtIsoBmffBox::TfdtIsoBmffBox(FullIsoBmffBox &fbox, uint64_t mdt, uint8_t* mdt_loc) : FullIsoBmffBox(fbox), baseMDT(mdt), baseMDT_loc(mdt_loc) +{ + +} + +/** + * @brief Set BaseMediaDecodeTime value + */ +void TfdtIsoBmffBox::setBaseMDT(uint64_t mdt) +{ + baseMDT = mdt; + if (nullptr != baseMDT_loc) + { + if (1 == version) + { + WriteUint64ToBuffer(baseMDT_loc, mdt); + } + else + { + PLAYER_WRITE_U32(baseMDT_loc, static_cast(mdt)); + } + } +} + +/** + * @brief Get BaseMediaDecodeTime value + */ +uint64_t TfdtIsoBmffBox::getBaseMDT() +{ + return baseMDT; +} + +/** + * @brief Static function to construct a TfdtIsoBmffBox object + */ +TfdtIsoBmffBox* TfdtIsoBmffBox::constructTfdtBox(uint32_t sz, uint8_t *ptr) +{ + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t flags = PLAYER_READ_FLAGS(ptr); + uint8_t* mdt_loc{ptr}; + uint64_t mdt; + + if (1 == version) + { + mdt = READ_BMDT64(ptr); + } + else + { + mdt = (uint32_t)PLAYER_READ_U32(ptr); + } + FullIsoBmffBox fbox(sz, IsoBmffBox::TFDT, version, flags); + return new TfdtIsoBmffBox(fbox, mdt, mdt_loc); +} + +/** + * @brief EmsgIsoBmffBox constructor + */ +EmsgIsoBmffBox::EmsgIsoBmffBox(uint32_t sz, uint32_t tScale, uint32_t evtDur, uint32_t _id) : FullIsoBmffBox(sz, IsoBmffBox::EMSG, 0, 0) + , timeScale(tScale), eventDuration(evtDur), id(_id) + , presentationTimeDelta(0), presentationTime(0) + , schemeIdUri(nullptr), value(nullptr), messageData(nullptr), messageLen(0) +{ + +} + +/** + * @brief EmsgIsoBmffBox constructor + */ +EmsgIsoBmffBox::EmsgIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint32_t evtDur, uint32_t _id, uint64_t presTime, uint32_t presTimeDelta) : FullIsoBmffBox(fbox) + , timeScale(tScale), eventDuration(evtDur), id(_id) + , presentationTimeDelta(presTimeDelta), presentationTime(presTime) + , schemeIdUri(nullptr), value(nullptr), messageData(nullptr), messageLen(0) +{ + +} + +/** + * @brief EmsgIsoBmffBox dtor + */ +EmsgIsoBmffBox::~EmsgIsoBmffBox() +{ + if (messageData) + { + free(messageData); + } + + if (schemeIdUri) + { + free(schemeIdUri); + } + + if (value) + { + free(value); + } +} + +/** + * @brief Set TimeScale value + */ +void EmsgIsoBmffBox::setTimeScale(uint32_t tScale) +{ + timeScale = tScale; +} + +/** + * @brief Get schemeIdUri + */ +uint32_t EmsgIsoBmffBox::getTimeScale() +{ + return timeScale; +} + +/** + * @brief Set eventDuration value + */ +void EmsgIsoBmffBox::setEventDuration(uint32_t evtDur) +{ + eventDuration = evtDur; +} + +/** + * @brief Get eventDuration + */ +uint32_t EmsgIsoBmffBox::getEventDuration() +{ + return eventDuration; +} + +/** + * @brief Set id + */ +void EmsgIsoBmffBox::setId(uint32_t _id) +{ + id = _id; +} + +/** + * @brief Get id + */ +uint32_t EmsgIsoBmffBox::getId() +{ + return id; +} + +/** + * @brief Set presentationTimeDelta + */ +void EmsgIsoBmffBox::setPresentationTimeDelta(uint32_t presTimeDelta) +{ + presentationTimeDelta = presTimeDelta; +} + +/** + * @brief Get presentationTimeDelta + */ +uint32_t EmsgIsoBmffBox::getPresentationTimeDelta() +{ + return presentationTimeDelta; +} + +/** + * @brief Set presentationTime + */ +void EmsgIsoBmffBox::setPresentationTime(uint64_t presTime) +{ + presentationTime = presTime; +} + +/** + * @brief Get presentationTime + */ +uint64_t EmsgIsoBmffBox::getPresentationTime() +{ + return presentationTime; +} + +/** + * @brief Set schemeIdUri + */ +void EmsgIsoBmffBox::setSchemeIdUri(char * scheme_uri) +{ + schemeIdUri = scheme_uri; +} + +/** + * @brief Get schemeIdUri + */ +char * EmsgIsoBmffBox::getSchemeIdUri() const +{ + return schemeIdUri; +} + +/** + * @brief Set value + */ +void EmsgIsoBmffBox::setValue(uint8_t* schemeIdValue) +{ + value = schemeIdValue; +} + +/** + * @brief Get value + */ +uint8_t* EmsgIsoBmffBox::getValue() +{ + return value; +} + +/** + * @brief Set Message + */ +void EmsgIsoBmffBox::setMessage(uint8_t* message, uint32_t len) +{ + messageData = message; + messageLen = len; +} + +/** + * @brief Get Message + */ +uint8_t* EmsgIsoBmffBox::getMessage() +{ + return messageData; +} + +/** + * @brief Get Message length + */ +uint32_t EmsgIsoBmffBox::getMessageLen() +{ + return messageLen; +} + +/** + * @brief Static function to construct a EmsgIsoBmffBox object + */ +EmsgIsoBmffBox* EmsgIsoBmffBox::constructEmsgBox(uint32_t sz, uint8_t *ptr) +{ + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t flags = PLAYER_READ_FLAGS(ptr); + // Calculating remaining size, + // flags(3bytes)+ version(1byte)+ box_header(type and size)(8bytes) + uint32_t remainingSize = sz - ((sizeof(uint32_t))+(sizeof(uint64_t))); + + uint64_t presTime = 0; + uint32_t presTimeDelta = 0; + uint32_t tScale; + uint32_t evtDur; + uint32_t boxId; + + char * schemeId = nullptr; + uint8_t* schemeIdValue = nullptr; + + uint8_t* message = nullptr; + FullIsoBmffBox fbox(sz, IsoBmffBox::EMSG, version, flags); + + /* + * Extraction is done as per https://aomediacodec.github.io/id3-emsg/ + */ + if (1 == version) + { + tScale = PLAYER_READ_U32(ptr); + // Read 64 bit value + presTime = READ_64(ptr); + evtDur = PLAYER_READ_U32(ptr); + boxId = PLAYER_READ_U32(ptr); + remainingSize -= ((sizeof(uint32_t)*3) + sizeof(uint64_t)); + int schemeIdLen = ReadCStringLenFromBuffer(ptr, remainingSize); + if(schemeIdLen > 0) + { + schemeId = (char*) malloc(sizeof(char)*schemeIdLen); + PLAYER_READ_U8(schemeId, ptr, schemeIdLen); + remainingSize -= (sizeof(uint8_t) * schemeIdLen); + int schemeIdValueLen = ReadCStringLenFromBuffer(ptr, remainingSize); + if (schemeIdValueLen > 0) + { + schemeIdValue = (uint8_t*) malloc(sizeof(uint8_t)*schemeIdValueLen); + PLAYER_READ_U8(schemeIdValue, ptr, schemeIdValueLen); + remainingSize -= (sizeof(uint8_t) * schemeIdValueLen); + } + } + } + else if(0 == version) + { + int schemeIdLen = ReadCStringLenFromBuffer(ptr, remainingSize); + if(schemeIdLen > 0) + { + schemeId = (char*) malloc(sizeof(char)*schemeIdLen); + PLAYER_READ_U8(schemeId, ptr, schemeIdLen); + remainingSize -= (sizeof(uint8_t) * schemeIdLen); + int schemeIdValueLen = ReadCStringLenFromBuffer(ptr, remainingSize); + if (schemeIdValueLen > 0) + { + schemeIdValue = (uint8_t*) malloc(sizeof(uint8_t)*schemeIdValueLen); + PLAYER_READ_U8(schemeIdValue, ptr, schemeIdValueLen); + remainingSize -= (sizeof(uint8_t) * schemeIdValueLen); + } + } + tScale = PLAYER_READ_U32(ptr); + presTimeDelta = PLAYER_READ_U32(ptr); + evtDur = PLAYER_READ_U32(ptr); + boxId = PLAYER_READ_U32(ptr); + remainingSize -= (sizeof(uint32_t)*4); + } + else + { + MW_LOG_WARN("Unsupported emsg box version"); + return new EmsgIsoBmffBox(fbox, 0, 0, 0, 0, 0); + } + + EmsgIsoBmffBox* retBox = new EmsgIsoBmffBox(fbox, tScale, evtDur, boxId, presTime, presTimeDelta); + if(retBox) + { + // Extract remaining message + if(remainingSize > 0) + { + message = (uint8_t*) malloc(sizeof(uint8_t)*remainingSize); + PLAYER_READ_U8(message, ptr, remainingSize); + if(message) + { + retBox->setMessage(message, remainingSize); + } + } + + // Save schemeIdUri and value if present + if (schemeId) + { + retBox->setSchemeIdUri(schemeId); + if(schemeIdValue) + { + retBox->setValue(schemeIdValue); + } + } + else + { + if(schemeIdValue) + { + free(schemeIdValue); + } + } + } + else + { + if (schemeId) + { + free(schemeId); + } + if(schemeIdValue) + { + free(schemeIdValue); + } + } + return retBox; +} + +/** + * @brief TrunIsoBmffBox constructor + */ +TrunIsoBmffBox::TrunIsoBmffBox(uint32_t sz, uint64_t sampleDuration,uint32_t sampleCount, uint8_t *sampleCountLoc, uint8_t* firstSampleDurationLoc, uint32_t firstSampleSize, uint32_t flags) + : FullIsoBmffBox(sz, IsoBmffBox::TRUN, 0, 0), + duration(sampleDuration), + sample_count(sampleCount), + sample_count_loc(sampleCountLoc), + first_sample_duration_loc(firstSampleDurationLoc), + mFirstSampleSize(firstSampleSize), + mFlags(flags) +{ +} + +/** + * @brief TrunIsoBmffBox constructor + */ +TrunIsoBmffBox::TrunIsoBmffBox(FullIsoBmffBox &fbox, uint64_t sampleDuration,uint32_t sampleCount, uint8_t *sampleCountLoc, uint8_t* firstSampleDurationLoc, uint32_t firstSampleSize, uint32_t flags) + : FullIsoBmffBox(fbox), + duration(sampleDuration), + sample_count(sampleCount), + sample_count_loc(sampleCountLoc), + first_sample_duration_loc(firstSampleDurationLoc), + mFirstSampleSize(firstSampleSize), + mFlags(flags) +{ +} + +/** + * @brief Set SampleDuration value + */ +void TrunIsoBmffBox::setFirstSampleDuration(uint64_t sampleDuration) +{ + duration = sampleDuration; + if (nullptr != first_sample_duration_loc) + { + PLAYER_WRITE_U32(first_sample_duration_loc, sampleDuration); + } +} + +/** + * @brief Get sampleDuration value + */ +uint64_t TrunIsoBmffBox::getSampleDuration() +{ + return duration; +} + +/** + * @brief Get SampleCount value + */ +uint32_t TrunIsoBmffBox::getSampleCount() +{ + return sample_count; +} + +/** + * @brief Static function to construct a TrunIsoBmffBox object + */ +TrunIsoBmffBox* TrunIsoBmffBox::constructTrunBox(uint32_t sz, uint8_t *ptr) +{ + // The size and tag have been read, so the pointer has advanced after that value + auto start = ptr; + + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t flags = PLAYER_READ_FLAGS(ptr); + uint32_t sample_count = 0; + uint32_t sample_duration = 0; + uint32_t sample_size = 0; + uint64_t totalSampleDuration = 0; + + uint32_t record_fields_count = 0; + + uint32_t firstSampleSize{}; + + // count the number of bits set to 1 in the second byte of the flags + for (unsigned int i=0; i<8; i++) + { + if (flags & (1<<(i+8))) ++record_fields_count; + } + + uint8_t *sampleCountLoc = ptr; + sample_count = PLAYER_READ_U32(ptr); + if(flags & TRUN_FLAG_DATA_OFFSET_PRESENT) + { + ptr += sizeof(uint32_t); // skip data offset + } + if(flags & TRUN_FLAG_FIRST_SAMPLE_FLAGS_PRESENT) + { + ptr += sizeof(uint32_t); // skip first sample flags + // bit(4) reserved=0; + // unsigned int(2) is_leading; + // unsigned int(2) sample_depends_on; + // unsigned int(2) sample_is_depended_on; + // unsigned int(2) sample_has_redundancy; + // bit(3) sample_padding_value; + // bit(1) sample_is_non_sync_sample; + // unsigned int(16) sample_degradation_priority; + } + + // Used by truncate operation + //uint32_t bytesPerSample = ((flags & TRUN_FLAG_SAMPLE_DURATION_PRESENT)? 4 : 0) + + // ((flags & TRUN_FLAG_SAMPLE_SIZE_PRESENT)? 4 : 0) + + // ((flags & TRUN_FLAG_SAMPLE_FLAGS_PRESENT)? 4 : 0) + + // ((flags & TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT)? 4 : 0); + uint8_t *firstSampleDurationLoc = nullptr; + + for (unsigned int i=0; i 1) + { + uint32_t bytesPerSample = ((flags & TRUN_FLAG_SAMPLE_DURATION_PRESENT)? 4 : 0) + + ((flags & TRUN_FLAG_SAMPLE_SIZE_PRESENT)? 4 : 0) + + ((flags & TRUN_FLAG_SAMPLE_FLAGS_PRESENT)? 4 : 0) + + ((flags & TRUN_FLAG_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT)? 4 : 0); + uint32_t tableSize{sample_count * bytesPerSample}; + // Calculate new trun size and write + auto oldTrunSize = getSize(); + auto newTrunSize = getSize() - tableSize + bytesPerSample; + sample_count = 1; + PLAYER_WRITE_U32(sample_count_loc, sample_count); + + // If there is room to insert a skip box + if ((oldTrunSize - newTrunSize) >= PLAYER_SIZEOF_SIZE_AND_TAG) + { + setSize(newTrunSize); + SkipIsoBmffBox skip{tableSize - bytesPerSample, getBase() + getSize()}; + } + else + { + MW_LOG_INFO("No room for a skip box"); + } + } + // Else no need to truncate +} + +/** + * @fn getFirstSampleSize + * + * @return The size of the first sample + */ +uint32_t TrunIsoBmffBox::getFirstSampleSize() +{ + return mFirstSampleSize; +} + +/** + * @fn getFirstSampleSize + * + * @return true if SAMPLE_DURATION_PRESENT is enabled, false otherwise + */ +bool TrunIsoBmffBox::sampleDurationPresent() +{ + return ((flags & TRUN_FLAG_SAMPLE_DURATION_PRESENT) != 0); +} + +/** + * @brief TfhdIsoBmffBox constructor + */ +TfhdIsoBmffBox::TfhdIsoBmffBox(uint32_t sz, uint64_t default_duration, uint8_t* default_duration_location, uint32_t default_sample_size, uint32_t flags) + : FullIsoBmffBox(sz, IsoBmffBox::TFHD, 0, 0), + mDefaultSampleDuration(default_duration), + default_sample_duration_location(default_duration_location), + mDefaultSampleSize(default_sample_size), + mFlags(flags) +{ + +} + +/** + * @brief TfhdIsoBmffBox constructor + */ +TfhdIsoBmffBox::TfhdIsoBmffBox(FullIsoBmffBox &fbox, uint64_t default_duration, uint8_t* default_duration_location, uint32_t default_sample_size, uint32_t flags) + : FullIsoBmffBox(fbox), + mDefaultSampleDuration(default_duration), + default_sample_duration_location(default_duration_location), + mDefaultSampleSize(default_sample_size), + mFlags(flags) +{ + +} + +bool TfhdIsoBmffBox::defaultSampleDurationPresent(void) +{ + return (mFlags & TFHD_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT) != 0; +} + +uint64_t TfhdIsoBmffBox::getDefaultSampleDuration() +{ + return mDefaultSampleDuration; +} + +void TfhdIsoBmffBox::setDefaultSampleDuration(uint64_t default_duration) +{ + mDefaultSampleDuration = default_duration; + if (nullptr != default_sample_duration_location) + { + PLAYER_WRITE_U32(default_sample_duration_location, default_duration); + } +} + +uint32_t TfhdIsoBmffBox::getDefaultSampleSize(void) +{ + return mDefaultSampleSize; +} + +/** + * @brief Static function to construct a TfdtIsoBmffBox object + */ +TfhdIsoBmffBox* TfhdIsoBmffBox::constructTfhdBox(uint32_t sz, uint8_t *ptr) +{ + auto start = ptr; + uint8_t version = PLAYER_READ_VERSION(ptr); // 8 + uint32_t flags = PLAYER_READ_FLAGS(ptr); //24 + + uint32_t DefaultSampleDuration{0}; + uint32_t DefaultSampleSize{0}; + uint8_t* DefaultSampleDuration_loc{nullptr}; + + ptr += sizeof(uint32_t); // skip track id + + if (flags & TFHD_FLAG_BASE_DATA_OFFSET_PRESENT) + { + ptr += sizeof(uint64_t); // skip base data offset + } + + if (flags & TFHD_FLAG_SAMPLE_DESCRIPTION_INDEX_PRESENT) + { + ptr += sizeof(uint32_t); // skip sample description index + } + + if (flags & TFHD_FLAG_DEFAULT_SAMPLE_DURATION_PRESENT) + { + DefaultSampleDuration_loc = ptr; + DefaultSampleDuration = PLAYER_READ_U32(ptr); + } + + if (flags & TFHD_FLAG_DEFAULT_SAMPLE_SIZE_PRESENT) + { + DefaultSampleSize = PLAYER_READ_U32(ptr); + } + + FullIsoBmffBox fbox(sz, IsoBmffBox::TFHD, version, flags); + fbox.setBase(start); + + return new TfhdIsoBmffBox(fbox, DefaultSampleDuration, DefaultSampleDuration_loc, DefaultSampleSize, flags); +} + +/** + * @brief PrftIsoBmffBox constructor + */ +PrftIsoBmffBox::PrftIsoBmffBox(uint32_t sz, uint32_t trackId, uint64_t ntpTs, uint64_t mediaTime) : FullIsoBmffBox(sz, IsoBmffBox::PRFT, 0, 0), track_id(trackId), ntp_ts(ntpTs), media_time(mediaTime) +{ + +} + +/** + * @brief PrftIsoBmffBox constructor + */ +PrftIsoBmffBox::PrftIsoBmffBox(FullIsoBmffBox &fbox, uint32_t trackId, uint64_t ntpTs, uint64_t mediaTime) : FullIsoBmffBox(fbox), track_id(trackId), ntp_ts(ntpTs), media_time(mediaTime) +{ + +} + +/** + * @brief Set Track Id value + */ +void PrftIsoBmffBox::setTrackId(uint32_t trackId) +{ + track_id = trackId; +} + +/** + * @brief Get Track id value + */ +uint32_t PrftIsoBmffBox::getTrackId() +{ + return track_id; +} + +/** + * @brief Set NTP Ts value + */ +void PrftIsoBmffBox::setNtpTs(uint64_t ntpTs) +{ + ntp_ts = ntpTs; +} + +/** + * @brief Get ntp Timestamp value + */ +uint64_t PrftIsoBmffBox::getNtpTs() +{ + return ntp_ts; +} + +/** + * @brief Set Sample Duration value + */ +void PrftIsoBmffBox::setMediaTime(uint64_t mediaTime) +{ + media_time = mediaTime; +} + +/** + * @brief Get SampleDuration value + */ +uint64_t PrftIsoBmffBox::getMediaTime() +{ + return media_time; +} + +/** + * @brief Static function to construct a PrftIsoBmffBox object + */ +PrftIsoBmffBox* PrftIsoBmffBox::constructPrftBox(uint32_t sz, uint8_t *ptr) +{ + uint8_t version = PLAYER_READ_VERSION(ptr); //8 + uint32_t flags = PLAYER_READ_FLAGS(ptr); //24 + + uint32_t track_id = 0; // reference track ID + track_id = PLAYER_READ_U32(ptr); + uint64_t ntp_ts = 0; // NTP time stamp + ntp_ts = READ_64(ptr); + uint64_t pts = 0; //media time + pts = READ_64(ptr); + + FullIsoBmffBox fbox(sz, IsoBmffBox::PRFT, version, flags); + + return new PrftIsoBmffBox(fbox, track_id, ntp_ts, pts); +} + +/** + * @fn constructTrakBox + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed trak object + */ +TrakIsoBmffBox* TrakIsoBmffBox::constructTrakBox(uint32_t sz, uint8_t *ptr, int newTrackId) +{ + TrakIsoBmffBox *cbox = new TrakIsoBmffBox(sz); + uint32_t curOffset = sizeof(uint32_t) + sizeof(uint32_t); //Sizes of size & type fields + while (curOffset < sz) + { + IsoBmffBox *box = IsoBmffBox::constructBox(ptr, sz-curOffset); + box->setOffset(curOffset); + + if (IS_TYPE(box->getType(),TKHD)) + { + uint8_t *tkhd_start = ptr; + ptr += curOffset; + uint8_t version = PLAYER_READ_VERSION(ptr); + uint32_t skip = 3; // size of flags + if (1 == version) + { + skip += sizeof(uint64_t) * 2; //CreationTime + ModificationTime + } + else + { + skip += sizeof(uint32_t) * 2; //CreationTime + ModificationTime + } + ptr += skip; + if(-1 != newTrackId) + { + PLAYER_WRITE_U32(ptr, newTrackId); + } + cbox->track_id = PLAYER_READ_U32(ptr); + ptr = tkhd_start; + } + cbox->addChildren(box); + curOffset += box->getSize(); + ptr += box->getSize(); + } + return cbox; +} + +/** + * @brief SidxIsoBmffBox constructor + */ +SidxIsoBmffBox::SidxIsoBmffBox(FullIsoBmffBox &fbox, uint32_t currTimeScale, uint64_t sidxDuration) : FullIsoBmffBox(fbox), timeScale(currTimeScale), duration(sidxDuration) +{ + +} + +/** + * @brief SidxIsoBmffBox constructor + */ +SidxIsoBmffBox::SidxIsoBmffBox(uint32_t sz, uint32_t tScale, uint64_t sidxDuration) : FullIsoBmffBox(sz, IsoBmffBox::SIDX, 0, 0), timeScale(tScale), duration(sidxDuration) +{ + +} + +/** + * @brief Get TimeScale value + */ +uint32_t SidxIsoBmffBox::getTimeScale() +{ + return timeScale; +} + +/** + * @brief Set TimeScale value + */ +void SidxIsoBmffBox::setTimeScale(uint32_t tScale) +{ + timeScale = tScale; +} + +/** + * @brief Get sampleDuration value + */ +uint64_t SidxIsoBmffBox::getSampleDuration() +{ + return duration; +} + +/** + * @brief Static function to construct a SidxBox object + */ +SidxIsoBmffBox* SidxIsoBmffBox::constructSidxBox(uint32_t sz, uint8_t *ptr) +{ + uint8_t version = PLAYER_READ_VERSION(ptr); //8 + uint32_t flags = PLAYER_READ_FLAGS(ptr); //24 + uint32_t reference_ID = PLAYER_READ_U32(ptr); (void) reference_ID; // 32 + uint32_t currTimeScale = PLAYER_READ_U32(ptr); //32 + uint32_t duration = 0x00; + if ( version == 0) + { + PLAYER_READ_U32(ptr); // earliest_presentation_time; + PLAYER_READ_U32(ptr); // first_offset + } + else + { + READ_64(ptr); // earliest_presentation_time; + READ_64(ptr); // first_offset; + } + PLAYER_READ_U16(ptr); //unused + uint16_t refCount = PLAYER_READ_U16(ptr); + for(uint16_t i = 0; i < refCount; i++) + { + PLAYER_READ_U32(ptr); // refType, size + duration += PLAYER_READ_U32(ptr); + PLAYER_READ_U32(ptr); // startsWithSAP, sapType, sapDeltaTime + } + FullIsoBmffBox fbox(sz, IsoBmffBox::SIDX, version, flags); + return new SidxIsoBmffBox(fbox, currTimeScale,duration); +} + + +/** + * @fn constructMdatIsoBmffBox + * @param[in] sz - box size + * @param[in] ptr - pointer to box + + * @return newly constructed mdat object + */ +MdatIsoBmffBox* MdatIsoBmffBox::constructMdatBox(uint32_t sz, uint8_t *ptr) +{ + MdatIsoBmffBox *cbox = new MdatIsoBmffBox(sz, ptr); + // The size and tag have been read, so the pointer has advanced after that value + cbox->setBase(ptr); + + return cbox; +} + +/** + * @fn MdatBox::truncate + * @param[in] newSize - new mdat box size + * @return None +*/ + +void MdatIsoBmffBox::truncate(uint32_t newSize) +{ + setSize(newSize); +} + +/** + * @fn SencIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] sampleCountLoc - sample count location + * @param[in] numSamples - number of samples + */ +SencIsoBmffBox::SencIsoBmffBox(FullIsoBmffBox &fbox, uint8_t *sampleCountLoc, uint32_t numSamples): sampleCountLoc(sampleCountLoc), numSamples(numSamples), FullIsoBmffBox(fbox) +{ +} + +/** + * @fn constructSencIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed SencBox object + */ +SencIsoBmffBox* SencIsoBmffBox::constructSencBox(uint32_t sz, uint8_t *ptr) +{ + auto start = ptr; + + uint8_t version = PLAYER_READ_VERSION(ptr); // 8 bits + uint32_t flags = PLAYER_READ_FLAGS(ptr); // 24 bits + + auto sample_count_loc{ptr}; + uint32_t sample_count = PLAYER_READ_U32(ptr); + + FullIsoBmffBox fbox(sz, IsoBmffBox::SENC, version, flags); + fbox.setBase(start); + + return new SencIsoBmffBox(fbox, sample_count_loc, sample_count); +} + +/** + * @fn sencIsoBmffBox::truncate + * @brief Reduce the number of samples to 1, write a new skip box over the remainder of the table + * + * @param[in] firstSampleSize - Size of the first sample +*/ +void SencIsoBmffBox::truncate(uint32_t firstSampleSize) +{ + if (numSamples > 1) + { + if (firstSampleSize) + { + auto newEnd{sampleCountLoc + sizeof(uint32_t) + firstSampleSize}; + auto oldSize{getSize()}; + auto newSize{static_cast(newEnd - getBase())}; + + if ((oldSize - newSize) >= PLAYER_SIZEOF_SIZE_AND_TAG) + { + PLAYER_WRITE_U32(getBase(), newSize); + SkipIsoBmffBox skip{oldSize - newSize, newEnd}; + } + else + { + MW_LOG_INFO("No room for a skip box"); + } + } + + numSamples = 1; + PLAYER_WRITE_U32(sampleCountLoc, numSamples); + } +} + +/** + * @fn SaizIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] sampleCountLoc - location of the sample count + * @param[in] numSamples - number of samples + * @param[in] firstSampleInfoSize - Size of the first sample + */ +SaizIsoBmffBox::SaizIsoBmffBox(FullIsoBmffBox &fbox, uint8_t *sampleCountLoc, uint32_t numSamples, uint32_t firstSampleInfoSize) + : sampleCountLoc(sampleCountLoc), + numSamples(numSamples), + firstSampleInfoSize(firstSampleInfoSize), + FullIsoBmffBox(fbox) +{ +} + +/** + * @fn constructSaizIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed SaizBox object + */ +SaizIsoBmffBox* SaizIsoBmffBox::constructSaizBox(uint32_t sz, uint8_t *ptr) +{ + auto start = ptr; + uint8_t version = PLAYER_READ_VERSION(ptr); // 8 + uint32_t flags = PLAYER_READ_FLAGS(ptr); // 24 + + if (flags & SAIZ_FLAG_AUX_INFO_TYPE_PRESENT) + { + ptr += sizeof(uint32_t); // skip aux_info_type + ptr += sizeof(uint32_t); // skip aux_info_type_parameter + } + + uint8_t default_sample_info_size{*ptr++}; + + uint8_t *sample_count_loc{ptr}; + uint32_t sample_count = PLAYER_READ_U32(ptr); + + uint8_t sample_info_size = (0 == default_sample_info_size) ? *ptr : default_sample_info_size; + + FullIsoBmffBox fbox(sz, IsoBmffBox::SAIZ, version, flags); + fbox.setBase(start); + + return new SaizIsoBmffBox(fbox, sample_count_loc, sample_count, sample_info_size); +} + +/** + * @fn SaizIsoBmffBox::getFirstSampleInfoSize +*/ +uint32_t SaizIsoBmffBox::getFirstSampleInfoSize(void) +{ + return firstSampleInfoSize; +} + + +/** + * @fn SaizIsoBmffBox::truncate +*/ +void SaizIsoBmffBox::truncate(void) +{ + auto oldSize{getSize()}; + auto newSize{oldSize - (numSamples - 1)}; + + // Need min 8 bytes to insert a skip box + if ((oldSize - newSize) >= PLAYER_SIZEOF_SIZE_AND_TAG) + { + PLAYER_WRITE_U32(getBase(), newSize); + SkipIsoBmffBox skip{oldSize - newSize, getBase() + newSize}; + } + else + { + MW_LOG_INFO("No room for a skip box"); + // Not truncating the table, just setting the sample count to 1 + } + + numSamples = 1; + PLAYER_WRITE_U32(sampleCountLoc, 1); +} +} diff --git a/middleware/playerisobmff/playerisobmffbox.h b/middleware/playerisobmff/playerisobmffbox.h new file mode 100644 index 000000000..9c52e5e30 --- /dev/null +++ b/middleware/playerisobmff/playerisobmffbox.h @@ -0,0 +1,1134 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file isobmffbox.h +* @brief Header file for ISO Base Media File Format Boxes +*/ + +#ifndef __PLAYERISOBMFFBOX_H__ +#define __PLAYERISOBMFFBOX_H__ + +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" + +// Size of the size and tag fields in IsoBmff +#define PLAYER_SIZEOF_SIZE_AND_TAG (8) + +#define PLAYER_READ_U16(buf) \ + (buf[0] << 8) | buf[1]; buf+=2; + +#define PLAYER_READ_U32(buf) \ + ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | buf[3]; buf+=4; + +#define PLAYER_WRITE_U64(buf, val) \ + buf[0]= val>>56; buf[1]= val>>48; buf[2]= val>>40; buf[3]= val>>32; buf[4]= val>>24; buf[5]= val>>16; buf[6]= val>>8; buf[7]= val; + +#define PLAYER_WRITE_U32(buf, val) \ + buf[0]= val>>24; buf[1]= val>>16; buf[2]= val>>8; buf[3]= val; + +#define PLAYER_READ_U8(dst, src, sz) \ + memcpy(dst, src, sz); src+=sz; + +#define PLAYER_READ_VERSION(buf) \ + buf[0]; buf++; + +#define PLAYER_READ_FLAGS(buf) \ + (buf[0] << 16) | (buf[1] << 8) | buf[2]; buf+=3; + +//Delete non-array object +#define SAFE_DELETE(ptr) { delete(ptr); ptr = NULL; } +//Delete Array object +#define SAFE_DELETE_ARRAY(ptr) { delete [] ptr; ptr = NULL; } + +/** + * @fn ReadUint64 + * + * @param[in] buf - buffer pointer + * @return bytes read from buffer + */ +uint64_t ReadUint64FromBuffer(uint8_t *buf); + +/** + * @fn WriteUint64 + * + * @param[in] dst - buffer pointer + * @param[in] val - value to write + * @return void + */ +void WriteUint64ToBuffer(uint8_t *dst, uint64_t val); + +/** + * @fn ReadCStringLen + * @param[in] buffer Buffer to read + * @param[in] bufferLen String length + */ +int ReadCStringLenFromBuffer(const uint8_t* buffer, uint32_t bufferLen); + +namespace player_isobmff { +/** + * @class Box + * @brief Base Class for ISO BMFF Box + */ +class IsoBmffBox +{ +private: + uint8_t *base; /**< Ptr to start of IsoBmffBox */ + uint32_t offset; /**< Offset from the beginning of the segment */ + uint32_t size; /**< IsoBmffBox Size */ + char type[5]; /**< IsoBmffBox Type Including \0 */ + +/*TODO: Handle special cases separately */ +public: + static constexpr const char *FTYP = "ftyp"; + static constexpr const char *MOOV = "moov"; + static constexpr const char *MVHD = "mvhd"; + static constexpr const char *TRAK = "trak"; + static constexpr const char *MDIA = "mdia"; + static constexpr const char *MDHD = "mdhd"; + static constexpr const char *EMSG = "emsg"; + + static constexpr const char *MOOF = "moof"; + static constexpr const char *TFHD = "tfhd"; + static constexpr const char *TRAF = "traf"; + static constexpr const char *TFDT = "tfdt"; + static constexpr const char *TRUN = "trun"; + static constexpr const char *MDAT = "mdat"; + static constexpr const char *TKHD = "tkhd"; + + static constexpr const char *STYP = "styp"; + static constexpr const char *SIDX = "sidx"; + static constexpr const char *PRFT = "prft"; + static constexpr const char *SKIP = "skip"; + static constexpr const char *SENC = "senc"; + static constexpr const char *SAIZ = "saiz"; + + /** + * @fn IsoBmffBox + * + * @param[in] sz - box size + * @param[in] btype - box type + */ + IsoBmffBox(uint32_t sz, const char btype[4]); + + /** + * @brief IsoBmffBox destructor + */ + virtual ~IsoBmffBox() + { + + } + + /** + * @fn setOffset + * + * @param[in] os - offset + * @return void + */ + void setOffset(uint32_t os); + + /** + * @fn getOffset + * + * @return offset of box + */ + uint32_t getOffset() const; + + /** + * @fn hasChildren + * @return true if this box has other boxes as children + */ + virtual bool hasChildren() const; + + /** + * @fn getChildren + * + * @return array of child boxes + */ + virtual const std::vector *getChildren() const; + + /** + * @fn truncate + */ + virtual void truncate(void) {} + virtual void truncate(uint32_t param) {} + + /** + * @fn getSize + * + * @return box size + */ + uint32_t getSize() const; + + void setSize(uint32_t newSize) { PLAYER_WRITE_U32(base, newSize); + size = newSize; + } + + /** + * @fn getType + * + * @return box type + */ + const char *getType() const; + + /** + * @fn constructBox + * + * @param[in] hdr - pointer to box + * @param[in] maxSz - box size + * @param[in] correctBoxSize - flag to check if box size needs to be corrected + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed Box object + */ + static IsoBmffBox* constructBox(uint8_t *hdr, uint32_t maxSz, bool correctBoxSize = false, int newTrackId = -1); + + uint8_t *getBase(void) const { return base; } + + /** + * @fn setBase + * @brief Store a pointer to the base of the box + * + * @param[in] start - pointer to the box after reading the size and box type + */ + void setBase(uint8_t *start) { base = start - PLAYER_SIZEOF_SIZE_AND_TAG; } + + /** + * @fn rewriteAsSkipBox + * @brief Rewrite the box buffer data as a skip box + * NB This will not affect the object, it will only change the buffer data + * Once this has been called on a box, the box object should not be used + */ + void rewriteAsSkipBox(void); + +}; + +/** + * @class GenericContainerBox + * @brief Class for ISO BMFF Box container + * Eg: MOOV, MOOF, TRAK, MDIA + */ +class GenericContainerIsoBmffBox : public IsoBmffBox +{ +private: + std::vector children; // array of child boxes + +public: + /** + * @fn GenericContainerIsoBmffBox + * + * @param[in] sz - box size + * @param[in] btype - box type + */ + GenericContainerIsoBmffBox(uint32_t sz, const char btype[4]); + + /** + * @fn ~GenericContainerIsoBmffBox + */ + virtual ~GenericContainerIsoBmffBox(); + + /** + * @fn addChildren + * + * @param[in] box - child box object + * @return void + */ + void addChildren(IsoBmffBox *box); + + /** + * @fn hasChildren + * + * @return true if this box has other boxes as children + */ + bool hasChildren() const override; + + /** + * @fn getChildren + * + * @return array of child boxes + */ + const std::vector *getChildren() const override; + + /** + * @fn constructContainer + * + * @param[in] sz - box size + * @param[in] btype - box type + * @param[in] ptr - pointer to box + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed GenericContainerBox object + */ + static GenericContainerIsoBmffBox* constructContainer(uint32_t sz, const char btype[4], uint8_t *ptr, int newTrackId = -1); +}; + +/** + * @class TrakBox + * @brief Class for ISO BMFF TRAK container + */ +class TrakIsoBmffBox : public GenericContainerIsoBmffBox +{ +private: + uint32_t track_id; +public: + /** + * @brief Trak constructor + * + * @param[in] sz - box size + */ + TrakIsoBmffBox(uint32_t sz) : GenericContainerIsoBmffBox(sz, TRAK), track_id(0) + { + } + /** + * @fn constructTrakBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return newly constructed trak object + */ + static TrakIsoBmffBox* constructTrakBox(uint32_t sz, uint8_t *ptr, int newTrackId = -1); + + /** + * @brief track_id getter + * + * @return trak_id + */ + uint32_t getTrack_Id() { return track_id; } +}; +/** + * @class FullIsoBmffBox + * @brief Class for single ISO BMFF Box + * Eg: FTYP, MDHD, MVHD, TFDT + */ +class FullIsoBmffBox : public IsoBmffBox +{ +protected: + uint8_t version; + uint32_t flags; + +public: + /** + * @fn FullBox + * + * @param[in] sz - box size + * @param[in] btype - box type + * @param[in] ver - version value + * @param[in] f - flag value + */ + FullIsoBmffBox(uint32_t sz, const char btype[4], uint8_t ver, uint32_t f); +}; + +/** + * @class MdatBox + * @brief create an mdat box +*/ +class MdatIsoBmffBox: public IsoBmffBox +{ +public: + + /** + * @fn MdatBox + * @brief construct an mdat box header with the specified size at the specified location + * @param[in] sz - box size + * @param[in] locn - location + */ + MdatIsoBmffBox(uint32_t sz, uint8_t *locn) : IsoBmffBox(sz, IsoBmffBox::MDAT){} + + void truncate(uint32_t newSize) override; + + static MdatIsoBmffBox* constructMdatBox(uint32_t sz, uint8_t *ptr); +}; + +/** + * @class skipIsoBmffBox + * @brief Create a skip box +*/ +class SkipIsoBmffBox: public IsoBmffBox +{ +public: + /** + * @fn skipBox + * @brief construct a skip box header with the specified size at the specified location + * @param[in] sz - box size + * @param[in] locn - location + */ + SkipIsoBmffBox(uint32_t sz, uint8_t *locn) : IsoBmffBox(sz, IsoBmffBox::SKIP) + { + PLAYER_WRITE_U32(locn, sz); + memcpy(locn + sizeof(uint32_t), IsoBmffBox::SKIP, std::strlen(IsoBmffBox::SKIP)); + } +}; + +/** + * @class MvhdIsoBmffBox + * @brief Class for ISO BMFF MVHD Box + */ +class MvhdIsoBmffBox : public FullIsoBmffBox +{ +private: + uint32_t timeScale; + uint8_t* const timeScale_loc; + +public: + /** + * @fn MvhdIsoBmffBox + * + * @param[in] sz - box size + * @param[in] tScale - TimeScale value + * @param[in] tScale_loc - pointer with the location of the TimeScale in the buffer + */ + MvhdIsoBmffBox(uint32_t sz, uint32_t tScale, uint8_t* tScale_loc); + + /** + * @fn MvhdIsoBmffBox + * @param[in] fbox - box object + * @param[in] tScale - TimeScale value + * @param[in] tScale_loc - pointer with the location of the TimeScale in the buffer + */ + MvhdIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint8_t* tScale_loc); + + /** + * @fn setTimeScale + * + * @param[in] tScale - TimeScale value + * @return void + */ + void setTimeScale(uint32_t tScale); + + /** + * @fn getTimeScale + * + * @return TimeScale value + */ + uint32_t getTimeScale(); + + /** + * @fn constructMvhdBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed MvhdBox object + */ + static MvhdIsoBmffBox* constructMvhdBox(uint32_t sz, uint8_t *ptr); +}; + + +/** + * @class MdhdBox + * @brief Class for ISO BMFF MDHD Box + */ +class MdhdIsoBmffBox : public FullIsoBmffBox +{ +private: + uint32_t timeScale; + uint8_t* const timeScale_loc; + +public: + /** + * @fn MdhdIsoBmffBox + * + * @param[in] sz - box size + * @param[in] tScale - TimeScale value + * @param[in] tScale_loc - pointer with the location of the TimeScale in the buffer + */ + MdhdIsoBmffBox(uint32_t sz, uint32_t tScale, uint8_t* tScale_loc); + + /** + * @fn MdhdBox + * + * @param[in] fbox - box object + * @param[in] tScale - TimeScale value + * @param[in] tScale_loc - pointer with the location of the TimeScale in the buffer + */ + MdhdIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint8_t* tScale_loc); + + /** + * @fn setTimeScale + * + * @param[in] tScale - TimeScale value + * @return void + */ + void setTimeScale(uint32_t tScale); + + /** + * @fn getTimeScale + * + * @return TimeScale value + */ + uint32_t getTimeScale(); + + /** + * @fn constructMdhdBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed MdhdBox object + */ + static MdhdIsoBmffBox* constructMdhdBox(uint32_t sz, uint8_t *ptr); +}; + + +/** + * @class TfdtBox + * @brief Class for ISO BMFF TFDT Box + */ +class TfdtIsoBmffBox : public FullIsoBmffBox +{ +private: + uint64_t baseMDT; // BaseMediaDecodeTime value + uint8_t* const baseMDT_loc; // BaseMediaDecodeTime location + +public: + /** + * @fn TfdtIsoBmffBox + * + * @param[in] sz - box size + * @param[in] mdt - BaseMediaDecodeTime value + * @param[in] mdt_loc - BaseMediaDecodeTime location + */ + TfdtIsoBmffBox(uint32_t sz, uint64_t mdt, uint8_t* mdt_loc); + + /** + * @fn TfdtIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] mdt - BaseMediaDecodeTime value + * @param[in] mdt_loc - BaseMediaDecodeTime location + */ + TfdtIsoBmffBox(FullIsoBmffBox &fbox, uint64_t mdt, uint8_t* mdt_loc); + + /** + * @fn setBaseMDT + * + * @param[in] mdt - BaseMediaDecodeTime value + * @return void + */ + void setBaseMDT(uint64_t mdt); + + /** + * @fn getBaseMDT + * + * @return BaseMediaDecodeTime value + */ + uint64_t getBaseMDT(); + + /** + * @fn constructTfdtBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed TfdtBox object + */ + static TfdtIsoBmffBox* constructTfdtBox(uint32_t sz, uint8_t *ptr); +}; + +/** + * @class EmsgBox + * @brief Class for ISO BMFF EMSG Box + */ +class EmsgIsoBmffBox : public FullIsoBmffBox +{ +private: + uint32_t timeScale; + uint32_t eventDuration; + uint32_t id; + uint32_t presentationTimeDelta; // This is added in emsg box v1 + uint64_t presentationTime; // This is included in emsg box v0 + char* schemeIdUri; + uint8_t* value; + // Message data + uint8_t* messageData; + uint32_t messageLen; + +public: + /** + * @fn EmsgIsoBmffBox + * + * @param[in] sz - box size + * @param[in] tScale - TimeScale value + * @param[in] evtDur - eventDuration value + * @param[in] _id - id value + */ + EmsgIsoBmffBox(uint32_t sz, uint32_t tScale, uint32_t evtDur, uint32_t _id); + + /** + * @fn EmsgBox + * + * @param[in] fbox - box object + * @param[in] tScale - TimeScale value + * @param[in] evtDur - eventDuration value + * @param[in] _id - id value + * @param[in] presTime - presentationTime value + * @param[in] presTimeDelta - presentationTimeDelta value + */ + EmsgIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint32_t evtDur, uint32_t _id, uint64_t presTime, uint32_t presTimeDelta); + + /** + * @fn ~EmsgIsoBmffBox + */ + ~EmsgIsoBmffBox(); + + /** + * @brief EmsgIsoBmffBox copy constructor + */ + EmsgIsoBmffBox(const EmsgIsoBmffBox&) = delete; + + /** + * @brief EmsgIsoBmffBox =operator overloading + */ + EmsgIsoBmffBox& operator=(const EmsgIsoBmffBox&) = delete; + + /** + * @fn setTimeScale + * + * @param[in] tScale - TimeScale value + * @return void + */ + void setTimeScale(uint32_t tScale); + + /** + * @fn getTimeScale + * + * @return TimeScale value + */ + uint32_t getTimeScale(); + + /** + * @fn setEventDuration + * + * @param[in] evtDur - eventDuration value + * @return void + */ + void setEventDuration(uint32_t evtDur); + + /** + * @fn getEventDuration + * + * @return eventDuration value + */ + uint32_t getEventDuration(); + + /** + * @fn setId + * + * @param[in] _id - id + * @return void + */ + void setId(uint32_t _id); + + /** + * @fn getId + * + * @return id value + */ + uint32_t getId(); + + /** + * @fn setPresentationTimeDelta + * + * @param[in] presTimeDelta - presTimeDelta + * @return void + */ + void setPresentationTimeDelta(uint32_t presTimeDelta); + + /** + * @fn getPresentationTimeDelta + * + * @return presentationTimeDelta value + */ + uint32_t getPresentationTimeDelta(); + + /** + * @fn setPresentationTime + * + * @param[in] presTime - presTime + * @return void + */ + void setPresentationTime(uint64_t presTime); + + /** + * @fn getPresentationTime + * + * @return presentationTime value + */ + uint64_t getPresentationTime(); + + /** + * @fn setSchemeIdUri + * + * @param[in] schemeIdUri - schemeIdUri pointer + * @return void + */ + void setSchemeIdUri(char* schemeIdURI); + + /** + * @fn getSchemeIdUri + * + * @return schemeIdUri value + */ + char* getSchemeIdUri() const; + + /** + * @fn setValue + * + * @param[in] value - value pointer + * @return void + */ + void setValue(uint8_t* schemeIdValue); + + /** + * @fn getValue + * + * @return schemeIdUri value + */ + uint8_t* getValue(); + + /** + * @fn setMessage + * + * @param[in] message - Message pointer + * @param[in] len - Message length + * @return void + */ + void setMessage(uint8_t* message, uint32_t len); + + /** + * @fn getMessage + * + * @return messageData + */ + uint8_t* getMessage(); + + /** + * @fn getMessageLen + * + * @return messageLen + */ + uint32_t getMessageLen(); + + /** + * @fn constructEmsgIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed EmsgBox object + */ + static EmsgIsoBmffBox* constructEmsgBox(uint32_t sz, uint8_t *ptr); +}; + +/** + * @class TrunIsoBmffBox + * @brief Class for ISO BMFF TRUN Box + */ +class TrunIsoBmffBox : public FullIsoBmffBox +{ +private: + uint64_t duration; //Sample Duration value + uint32_t sample_count; + uint8_t *sample_count_loc; + uint8_t *first_sample_duration_loc; + uint32_t mFirstSampleSize; + uint32_t mFlags; + +public: + struct Entry { + Entry() : sample_duration(0), sample_size(0), sample_flags(0), sample_composition_time_offset(0) {} + uint32_t sample_duration; + uint32_t sample_size; + uint32_t sample_flags; + uint32_t sample_composition_time_offset; + }; + + /** + * @fn TrunIsoBmffBox + * + * @param[in] sz - box size + * @param[in] sampleDuration - sample duration value + * @param[in] sampleCount - sample count value + * @param[in] sampleCountLoc - sample count location + * @param[in] sampleDurationLoc - sample duration location + * @param[in] firstSampleSize - size of the first sample + * @param[in] flags - flags set on this box + */ + TrunIsoBmffBox(uint32_t sz, uint64_t sampleDuration, uint32_t sampleCount, uint8_t *sampleCountLoc, uint8_t *sampleDurationLoc, uint32_t firstSampleSize, uint32_t flags); + + /** + * @fn TrunIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] sampleDuration - sample duration value + * @param[in] sampleCount - sample count value + * @param[in] sampleCountLoc - sample count location + * @param[in] sampleDurationLoc - sample duration location + * @param[in] firstSampleSize - size of the first sample + * @param[in] flags - flags set on this box + */ + TrunIsoBmffBox(FullIsoBmffBox &fbox, uint64_t sampleDuration, uint32_t sampleCount, uint8_t *sampleCountLoc, uint8_t *sampleDurationLoc, uint32_t firstSampleSize, uint32_t flags); + + /** + * @fn setFirstSampleDuration + * + * @brief The sample duration of the first sample is updated with the value provided. + * The sample duration of the remaining samples is not modified. + * + * @param[in] sampleDuration - Sample Duration value + * @return void + */ + void setFirstSampleDuration(uint64_t sampleDuration); + + /** + * @fn getSampleDuration + * + * @return sampleDuration value + */ + uint64_t getSampleDuration(); + + /** + * @fn getSampleCount + * + * @return SampleCount value + */ + uint32_t getSampleCount(); + + /** + * @fn constructTrunBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed TrunBox object + */ + static TrunIsoBmffBox* constructTrunBox(uint32_t sz, uint8_t *ptr); + + /** + * @fn truncate + */ + void truncate(void) override; + + /** + * @fn getFirstSampleSize + * + * @return The size of the first sample + */ + uint32_t getFirstSampleSize(void); + + /** + * @fn sampleDurationPresent + * + * @return true if SAMPLE_DURATION_PRESENT is enabled, false otherwise + */ + bool sampleDurationPresent(void); +}; + +/** + * @class TfhdBox + * @brief Class for ISO BMFF TFHD Box + */ +class TfhdIsoBmffBox : public FullIsoBmffBox +{ +private: + uint64_t mDefaultSampleDuration; + uint8_t *default_sample_duration_location; + uint32_t mDefaultSampleSize; + uint32_t mFlags; + +public: + /** + * @fn TfhdIsoBmffBox + * + * @param[in] sz - box size + * @param[in] sample_duration - Sample Duration value + * @param[in] first_sample_duration_loc - Default sample duration location + * @param[in] default_sample_size - Default sample size + * @param[in] flags - flags set on this box + */ + TfhdIsoBmffBox(uint32_t sz, uint64_t sample_duration, uint8_t* first_sample_duration_loc, uint32_t default_sample_size, uint32_t flags); + + /** + * @fn TfhdBox + * + * @param[in] fbox - box object + * @param[in] sample_duration - Sample Duration value + * @param[in] first_sample_duration_loc - Default sample duration location + * @param[in] default_sample_size - Default sample size + * @param[in] flags - flags set on this box + */ + TfhdIsoBmffBox(FullIsoBmffBox &fbox, uint64_t default_duration, uint8_t * default_duration_location, uint32_t default_sample_size, uint32_t flags); + + /** + * @fn defaultSampleDurationPresent + * + * @return True if default sample duration is present in the box; false otherwise + */ + bool defaultSampleDurationPresent(void); + + /** + * @fn getDefaultSampleDuration + * Use defaultSampleDurationPresent() beforehand to check for presence. + * + * @return Default sample duration, 0 if not present + * NOTE: It's possible that 0 may also be a valid value. + */ + uint64_t getDefaultSampleDuration(); + + /** + * @fn setDefaultSampleDuration + * Use defaultSampleDurationPresent() beforehand to check for presence. + * + * @param[in] sample_duration - Default sample duration value to set + */ + void setDefaultSampleDuration(uint64_t sample_duration); + + /** + * @fn getDefaultSampleSize + * + * @return The default sample size, 0 if not present + */ + uint32_t getDefaultSampleSize(void); + + /** + * @fn constructTfhdIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed TfhdBox object + */ + static TfhdIsoBmffBox* constructTfhdBox(uint32_t sz, uint8_t *ptr); +}; + +/** + * @class PrftIsoBmffBox + * @brief Class for ISO BMFF TFHD Box + */ +class PrftIsoBmffBox : public FullIsoBmffBox +{ +private: + uint32_t track_id; + uint64_t ntp_ts; + uint64_t media_time; + +public: + /** + * @fn PrftIsoBmffBox + * + * @param[in] sz - box size + * @param[in] trackId - media time + * @param[in] ntpTs - media time + * @param[in] mediaTime - media time + */ + PrftIsoBmffBox(uint32_t sz, uint32_t trackId, uint64_t ntpTs, uint64_t mediaTime); + + /** + * @fn PrftIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] trackId - media time + * @param[in] ntpTs - media time + * @param[in] mediaTime - media time + */ + PrftIsoBmffBox(FullIsoBmffBox &fbox, uint32_t trackId, uint64_t ntpTs, uint64_t mediaTime); + + /** + * @fn setTrackId + * + * @param[in] trackId - Track Id value + * @return void + */ + void setTrackId(uint32_t trackId); + + /** + * @fn getTrackId + * + * @return track_id value + */ + uint32_t getTrackId(); + + /** + * @fn setNtpTs + * + * @param[in] ntpTs - ntp timestamp value + * @return void + */ + void setNtpTs(uint64_t ntpTs); + + /** + * @fn getNtpTs + * + * @return ntp_ts value + */ + uint64_t getNtpTs(); + + /** + * @fn setMediaTime + * + * @param[in] mediaTime - media time value + * @return void + */ + void setMediaTime(uint64_t mediaTime); + + /** + * @fn getMediaTime + * + * @return media_time value + */ + uint64_t getMediaTime(); + + /** + * @fn constructPrftBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed PrftBox object + */ + static PrftIsoBmffBox* constructPrftBox(uint32_t sz, uint8_t *ptr); +}; + +class SencIsoBmffBox: public FullIsoBmffBox +{ +private: + uint8_t *sampleCountLoc; + uint32_t numSamples; + +public: + /** + * @fn SencIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] sampleCountLoc - sample count location + * @param[in] numSamples - number of samples + */ + SencIsoBmffBox(FullIsoBmffBox &fbox, uint8_t *sampleCountLoc, uint32_t numSamples); + + /** + * @fn constructSencIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed SencBox object + */ + static SencIsoBmffBox* constructSencBox(uint32_t sz, uint8_t *ptr); + + /** + * @fn truncate + * + * @param[in] firstSampleSize - Size of the first sample + */ + void truncate(uint32_t firstSampleSize) override; +}; + +class SaizIsoBmffBox : public FullIsoBmffBox +{ +private: + uint8_t *sampleCountLoc; + uint32_t numSamples; + uint32_t firstSampleInfoSize; + +public: + /** + * @fn SaizIsoBmffBox + * + * @param[in] fbox - box object + * @param[in] sampleCountLoc - location of the sample count + * @param[in] numSamples - number of samples + * @param[in] sample_info_size - Size for the first auxiliary sample information entry + */ + SaizIsoBmffBox(FullIsoBmffBox &fbox, uint8_t *sampleCountLoc, uint32_t numSamples, uint32_t sample_info_size); + + /** + * @fn constructSaizIsoBmffBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed SaizBox object + */ + static SaizIsoBmffBox* constructSaizBox(uint32_t sz, uint8_t *ptr); + + /* + * @fn getFirstSampleInfoSize + * + * @return The first sample size signalled in the saiz + */ + uint32_t getFirstSampleInfoSize(void); + + void truncate(void) override; +}; + +/** + * @class SidxBox + * @brief Class for ISO BMFF SIDX Box + */ +class SidxIsoBmffBox : public FullIsoBmffBox +{ +private: + uint32_t timeScale; + uint64_t duration; +public: + + /** + * @fn SidxIsoBmffBox + * @param[in] fbox - box object + * @param[in] tScale - TimeScale value + * @param[in] sidxDuration - duration from sidx box + */ + SidxIsoBmffBox(FullIsoBmffBox &fbox, uint32_t tScale, uint64_t sidxDuration); + + /** + * @fn SidxBox + * + * @param[in] sz - box size + * @param[in] tScale - TimeScale value + * @param[in] sidxDuration - duration from sidx box + */ + SidxIsoBmffBox(uint32_t sz, uint32_t tScale, uint64_t sidxDuration); + + /* @fn constructSidxBox + * + * @param[in] sz - box size + * @param[in] ptr - pointer to box + * @return newly constructed SidxBox object + */ + static SidxIsoBmffBox* constructSidxBox(uint32_t sz, uint8_t *ptr); + + /** + * @fn getTimeScale + * + * @return TimeScale value + */ + uint32_t getTimeScale(); + + /** + * @fn setTimeScale + * + * @param[in] tScale - TimeScale value + * @return void + */ + void setTimeScale(uint32_t tScale); + + /** + * @fn getSampleDuration + * + * @return sampleDuration value + */ + uint64_t getSampleDuration(); +}; +} + +#endif /* __PLAYERISOBMFFBOX_H__ */ diff --git a/middleware/playerisobmff/playerisobmffbuffer.cpp b/middleware/playerisobmff/playerisobmffbuffer.cpp new file mode 100644 index 000000000..2cad761e3 --- /dev/null +++ b/middleware/playerisobmff/playerisobmffbuffer.cpp @@ -0,0 +1,209 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file isobmffbuffer.cpp +* @brief Source file for ISO Base Media File Format Buffer +*/ + +#include "playerisobmffbuffer.h" +#include + +#define READ_BMDT64(buf) \ + ReadUint64(buf); buf+=8; + +#define READ_64(buf) \ + ReadUint64(buf); buf+=8; + +#define IS_TYPE(value, type) \ + (value[0]==type[0] && value[1]==type[1] && value[2]==type[2] && value[3]==type[3]) + +/** + * @brief IsoBmffBuffer destructor + */ +PlayerIsoBmffBuffer::~PlayerIsoBmffBuffer() +{ + for (unsigned int i=(unsigned int)boxes.size(); i>0;) + { + --i; + SAFE_DELETE(boxes[i]); + boxes.pop_back(); + } + boxes.clear(); +} + +/** + * @brief Set buffer + */ +void PlayerIsoBmffBuffer::setBuffer(uint8_t *buf, size_t sz) +{ + buffer = buf; + bufSize = sz; +} + +/** + * @fn parseBuffer + * @param[in] correctBoxSize - flag to correct the box size + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @brief Parse ISOBMFF boxes from buffer + */ +bool PlayerIsoBmffBuffer::parseBuffer(bool correctBoxSize, int newTrackId) +{ + size_t curOffset = 0; + while (curOffset < bufSize) + { + player_isobmff::IsoBmffBox *box = player_isobmff::IsoBmffBox::constructBox(buffer+curOffset, (uint32_t)(bufSize - curOffset), correctBoxSize, newTrackId); + if( ((bufSize - curOffset) < 4) || ( (bufSize - curOffset) < box->getSize()) ) + { + chunkedBox = box; + } + box->setOffset((uint32_t)curOffset); + boxes.push_back(box); + curOffset += box->getSize(); + } + return !!(boxes.size()); +} + +/** + * @brief Get list of box handles in a parsed buffer + */ +player_isobmff::IsoBmffBox* PlayerIsoBmffBuffer::getChunkedfBox() const +{ + return this->chunkedBox; +} + +/** + * @brief Print ISOBMFF boxes + */ +void PlayerIsoBmffBuffer::printBoxesInternal(const std::vector *boxes) +{ + for (size_t i = 0; i < boxes->size(); i++) + { + player_isobmff::IsoBmffBox *box = boxes->at(i); + MW_LOG_WARN("Offset[%u] Type[%s] Size[%u]", box->getOffset(), box->getType(), box->getSize()); + if (IS_TYPE(box->getType(), player_isobmff::IsoBmffBox::TFDT)) + { + player_isobmff::TfdtIsoBmffBox *tfdtBox = dynamic_cast(box); + if(tfdtBox) { + MW_LOG_WARN("****Base Media Decode Time: %" PRIu64, tfdtBox->getBaseMDT()); + } + } + else if (IS_TYPE(box->getType(), player_isobmff::IsoBmffBox::MVHD)) + { + player_isobmff::MvhdIsoBmffBox *mvhdBox = dynamic_cast(box); + if(mvhdBox) { + MW_LOG_WARN("**** TimeScale from MVHD: %u", mvhdBox->getTimeScale()); + } + } + else if (IS_TYPE(box->getType(), player_isobmff::IsoBmffBox::MDHD)) + { + player_isobmff::MdhdIsoBmffBox *mdhdBox = dynamic_cast(box); + if(mdhdBox) { + MW_LOG_WARN("**** TimeScale from MDHD: %u", mdhdBox->getTimeScale()); + } + } + + if (box->hasChildren()) + { + printBoxesInternal(box->getChildren()); + } + } +} + + +/** + * @brief Print ISOBMFF boxes + */ +void PlayerIsoBmffBuffer::printBoxes() +{ + printBoxesInternal(&boxes); +} + +/** + * @brief Check if buffer is an initialization segment + */ +bool PlayerIsoBmffBuffer::isInitSegment() +{ + bool foundFtypBox = false; + for (size_t i = 0; i < boxes.size(); i++) + { + player_isobmff::IsoBmffBox *box = boxes.at(i); + if (IS_TYPE(box->getType(), player_isobmff::IsoBmffBox::FTYP)) + { + foundFtypBox = true; + break; + } + } + return foundFtypBox; +} + +/** + * @brief Get mdat buffer size + */ +bool PlayerIsoBmffBuffer::getMdatBoxSize(size_t &size) +{ + return getBoxSizeInternal(&boxes, player_isobmff::IsoBmffBox::MDAT, size); +} + +/** + * @brief get ISOBMFF box size of a type + */ +bool PlayerIsoBmffBuffer::getBoxSizeInternal(const std::vector *boxes, const char *name, size_t &size) +{ + for (size_t i = 0; i < boxes->size(); i++) + { + player_isobmff::IsoBmffBox *box = boxes->at(i); + if (IS_TYPE(box->getType(), name)) + { + size = box->getSize(); + return true; + } + } + return false; +} + +/** + * @brief Get mdat buffer handle and size from parsed buffer + */ +bool PlayerIsoBmffBuffer::parseMdatBox(uint8_t *buf, size_t &size) +{ + return parseBoxInternal(&boxes, player_isobmff::IsoBmffBox::MDAT, buf, size); +} + +#define BOX_HEADER_SIZE 8 + +/** + * @brief parse ISOBMFF boxes of a type in a parsed buffer + */ +bool PlayerIsoBmffBuffer::parseBoxInternal(const std::vector *boxes, const char *name, uint8_t *buf, size_t &size) +{ + for (size_t i = 0; i < boxes->size(); i++) + { + player_isobmff::IsoBmffBox *box = boxes->at(i); + MW_LOG_TRACE("Offset[%u] Type[%s] Size[%u]", box->getOffset(), box->getType(), box->getSize()); + if (IS_TYPE(box->getType(), name)) + { + size_t offset = box->getOffset() + BOX_HEADER_SIZE; + size = box->getSize() - BOX_HEADER_SIZE; + memcpy(buf, buffer + offset, size); + return true; + } + } + return false; +} diff --git a/middleware/playerisobmff/playerisobmffbuffer.h b/middleware/playerisobmff/playerisobmffbuffer.h new file mode 100644 index 000000000..dc64c723d --- /dev/null +++ b/middleware/playerisobmff/playerisobmffbuffer.h @@ -0,0 +1,159 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** +* @file playerisobmffbuffer.h +* @brief Header file for ISO Base Media File Format Buffer +*/ + +#ifndef __PLAYERISOBMFFBUFFER_H__ +#define __PLAYERISOBMFFBUFFER_H__ + +#include "playerisobmffbox.h" +#include +#include +#include +#include +#include + +using namespace player_isobmff; + +/** + * @class PlayerIsoBmffBuffer + * @brief Class for ISO BMFF Buffer + */ +class PlayerIsoBmffBuffer +{ +private: + std::vector boxes; //ISOBMFF boxes of associated buffer + uint8_t *buffer; + size_t bufSize; + player_isobmff::IsoBmffBox* chunkedBox; //will hold one element only + size_t mdatCount; + + /** + * @fn printBoxesInternal + * + * @param[in] boxes - ISOBMFF boxes + * @return void + */ + void printBoxesInternal(const std::vector *boxes); + + /** + * @fn parseBoxInternal + * + * @param[in] boxes - ISOBMFF boxes + * @param[in] name - box name to get + * @param[out] buf - mdat buffer pointer + * @param[out] size - size of mdat buffer + * @return bool + */ + bool parseBoxInternal(const std::vector *boxes, const char *name, uint8_t *buf, size_t &size); + + /** + * @fn getBoxSizeInternal + * + * @param[in] boxes - ISOBMFF boxes + * @param[in] name - box name to get + * @param[out] size - size of mdat buffer + * @return bool + */ + bool getBoxSizeInternal(const std::vector *boxes, const char *name, size_t &size); + +public: + /** + * @brief PlayerIsoBmffBuffer constructor + */ + PlayerIsoBmffBuffer(): boxes(), buffer(NULL), bufSize(0), chunkedBox(NULL), mdatCount(0) + { + } + + /** + * @fn ~PlayerIsoBmffBuffer + */ + ~PlayerIsoBmffBuffer(); + + PlayerIsoBmffBuffer(const PlayerIsoBmffBuffer&) = delete; + PlayerIsoBmffBuffer& operator=(const PlayerIsoBmffBuffer&) = delete; + + /** + * @fn getChunkedfBox + * + * @return Box handle if Chunk box found in a parsed buffer. NULL otherwise + */ + player_isobmff::IsoBmffBox* getChunkedfBox() const; + + /** + * @fn UpdateBufferData + * @return true if parsed or false + */ + int UpdateBufferData(size_t parsedBoxCount, char* &unParsedBuffer, size_t &unParsedBufferSize, size_t & parsedBufferSize); + + /** + * @fn setBuffer + * + * @param[in] buf - buffer pointer + * @param[in] sz - buffer size + * @return void + */ + void setBuffer(uint8_t *buf, size_t sz); + + /** + * @fn parseBuffer + * + * @brief Parse the ISO BMFF buffer and create a vector of boxes with the parsed information. + * The method destroyBoxes needs to be called before parseBuffer can be called a second time. + * + * @param[in] correctBoxSize - flag to indicate if box size needs to be corrected + * @param[in] newTrackId - new track id to overwrite the existing track id, when value is -1, it will not override + * @return true if parse was successful. false otherwise + */ + bool parseBuffer(bool correctBoxSize = false, int newTrackId = -1); + + /** + * @fn printBoxes + * + * @return void + */ + void printBoxes(); + + /** + * @fn isInitSegment + * + * @return true if buffer is an initialization segment. false otherwise + */ + bool isInitSegment(); + + /** + * @fn parseMdatBox + * @param[out] buf - mdat buffer pointer + * @param[out] size - size of mdat buffer + * @return true if mdat buffer is available. false otherwise + */ + bool parseMdatBox(uint8_t *buf, size_t &size); + + /** + * @fn getMdatBoxSize + * @param[out] size - size of mdat buffer + * @return true if buffer size available. false otherwise + */ + bool getMdatBoxSize(size_t &size); +}; + +#endif /* __PLAYERISOBMFFBUFFER_H__ */ diff --git a/middleware/scripts/install_clone.sh b/middleware/scripts/install_clone.sh new file mode 100644 index 000000000..e374930dd --- /dev/null +++ b/middleware/scripts/install_clone.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# do_clone +# Pass all params to 'git clone' +# If the clone fails then exit the script +function do_clone_fn() +{ + ARGLIST="" + while [ "$1" ]; do + ARGLIST+=" $1" + shift + done + + echo && echo "Executing: 'git clone $ARGLIST'" + git clone $ARGLIST + + if [ $? != 0 ]; then + echo "'git clone $ARGLIST' FAILED" + return 1 + fi +} + +# do_clone_rdk_repo +# Clone a generic RDK repo +# If the destination (repo) dir already exists then skip the clone +function do_clone_rdk_repo_fn() { + if [ -d $2 ]; then + echo "Repo '$2' already exists" + pushd $2 + git fetch + git checkout $1 + git pull + popd + else + do_clone_fn -b $1 https://code.rdkcentral.com/r/rdk/components/generic/$2 + fi +} + +# do_clone_github_repo [...] +# Clone a repo from github into a directory +# If the destination already exists then skip the clone +function do_clone_github_repo_fn() { + if [ -d $2 ]; then + echo "Repo in '$2' already exists" + return 1 + else + do_clone_fn "$@" + fi +} diff --git a/middleware/scripts/install_dependencies.sh b/middleware/scripts/install_dependencies.sh new file mode 100644 index 000000000..67e56df5d --- /dev/null +++ b/middleware/scripts/install_dependencies.sh @@ -0,0 +1,253 @@ +#!/usr/bin/env bash +# +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +declare DEFAULT_OPENSSL_VERSION="openssl@3" + +function install_pkgs_pkgconfig_darwin_fn() +{ + echo "`brew --prefix ${1}`/lib/pkgconfig" +} + +function install_pkgs_darwin_fn() +{ + # Check if brew package $1 is installed + # http://stackoverflow.com/a/20802425/1573477 + for PKG in "$@"; + do + if brew ls --versions $PKG > /dev/null; then + echo "${PKG} is already installed." + INSTALL_STATUS_ARR+=("${PKG} is already installed.") + else + echo "Installing ${PKG}" + brew install $PKG + #update summery + if brew ls --versions $PKG > /dev/null; then + #The package is successfully installed + INSTALL_STATUS_ARR+=("The package was ${PKG} was successfully installed.") + + else + #The package is failed to be installed + INSTALL_STATUS_ARR+=("The package ${PKG} FAILED to be installed.") + fi + fi + #if pkg is openssl and its successfully installed every time ensure to symlink to the latest version + if [ $PKG = "${DEFAULT_OPENSSL_VERSION}" ]; then + OPENSSL_PATH=$(brew --prefix ${DEFAULT_OPENSSL_VERSION}) + # link may not exist so don't fail + OPENSSL_CUR_PATH=`readlink /usr/local/ssl` || true + if [ "$OPENSSL_CUR_PATH" != "{$OPENSSL_PATH}" ] ; then + sudo rm -f /usr/local/ssl || true + sudo ln -s $OPENSSL_PATH /usr/local/ssl + fi + fi + PKGDIR="`brew --prefix ${PKG}`/lib/pkgconfig:" + INSTALLED_PKGCONFIG=$PKGDIR$INSTALLED_PKGCONFIG + + # Add the path to the pkgconfig directory to the PKG_CONFIG_PATH for openldap and krb5 + if [ $PKG = "openldap" ] || [ $PKG = "krb5" ]; then + brew link $PKG --force + if [ "$(uname -m)" = "arm64" ]; then + export PKG_CONFIG_PATH="/opt/homebrew/opt/krb5/lib/pkgconfig:/opt/homebrew/opt/openldap/lib/pkgconfig:$PKG_CONFIG_PATH" + else + export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig:/usr/local/opt/openldap/lib/pkgconfig:$PKG_CONFIG_PATH" + fi + + fi + done + echo "${INSTALLED_PKGCONFIG}" +} + +function package_exists_lin_fn() { + dpkg -s "$1" &> /dev/null + return $? +} + +function install_package_fn() { + if ! package_exists_lin_fn $1 ; then + echo "Installing $1" + sudo apt install $1 -y + if [ $? == 0 ] ; then + INSTALL_STATUS_ARR+=("$1 was successfully installed.") + else + INSTALL_STATUS_ARR+=("The package $1 FAILED to be installed.") + fi + else + echo "$1 is already installed." + INSTALL_STATUS_ARR+=("$1 is already installed.") + fi +} + +function pip_package_exists_lin_fn() { + pip3 show "$1" &> /dev/null + return $? +} + +function pip_install_package_fn() +{ + if ! pip_package_exists_lin_fn $1 ; then + echo "installing $1" + sudo pip3 install $1 + fi +} + + +function install_pkgs_linux_fn() +{ + sudo apt update + install_package_fn git + install_package_fn cmake + install_package_fn gcc + install_package_fn g++ + install_package_fn libcurl4-openssl-dev + install_package_fn libgstreamer1.0-dev + install_package_fn libgstreamer-plugins-bad1.0-dev + install_package_fn libssl-dev + install_package_fn libxml2-dev + install_package_fn pkg-config + install_package_fn zlib1g-dev + install_package_fn libreadline-dev + install_package_fn libgstreamer-plugins-base1.0-dev + install_package_fn gstreamer1.0-libav + install_package_fn lcov + install_package_fn gcovr + install_package_fn libcjson-dev + install_package_fn curl + install_package_fn xz-utils + install_package_fn freeglut3-dev + install_package_fn build-essential + install_package_fn libglew-dev + install_package_fn libboost-all-dev + install_package_fn ninja-build + install_package_fn libwebsocketpp-dev + install_package_fn libjansson-dev + install_package_fn libwayland-dev + install_package_fn libxkbcommon-dev + install_package_fn libfontconfig-dev + install_package_fn libharfbuzz-dev + install_package_fn snapd + install_package_fn libcppunit-dev + install_package_fn wayland-protocols + install_package_fn libjsoncpp-dev + install_package_fn libasio-dev + install_package_fn libsystemd-dev + install_package_fn jq + install_package_fn libtinyxml2-dev + + VER=$(grep -oP 'VERSION_ID="\K[\d.]+' /etc/os-release) + + if [ ${VER:0:2} -ge 22 ]; then + install_package_fn libjavascriptcoregtk-4.1-dev + # Install and verify the version of meson + install_package_fn python3-pip + pip_install_package_fn meson + + MESON_VERSION=$(meson --version) + if $(dpkg --compare-versions "${MESON_VERSION}" lt "1.2.3"); then + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + echo "Meson version ${MESON_VERSION} is not supported" + echo "Please uninstall and use version 1.2.3 or later" + echo " sudo apt remove meson" + echo " sudo pip3 install meson" + echo " hash -r" + echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + return 1 + fi + elif [ ${VER:0:2} -eq 20 ]; then + install_package_fn libjavascriptcoregtk-4.0-dev + install_package_fn python3-pip + pip_install_package_fn meson + elif [ ${VER:0:2} -eq 18 ]; then + install_package_fn libjavascriptcoregtk-4.0-dev + install_package_fn python3-pip + pip_install_package_fn meson + else + echo "Please upgrade your Ubuntu version to at least 20:04 LTS. OS version is $VER" + return 1 + fi +} + +function install_pkgs_fn() +{ + if [[ "$OSTYPE" == "darwin"* ]]; then + + #Check/Install brew + which -s brew + if [[ $? != 0 ]] ; then + echo "Installing homebrew" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + # TODO: Need to add to path or have user do it and fail this script + else + echo "Updating homebrew" + brew update + fi + + install_pkgs_darwin_fn git json-glib cmake "openssl@3" libxml2 ossp-uuid cjson gnu-sed jpeg-turbo taglib speex mpg123 meson ninja pkg-config flac asio jsoncpp lcov gcovr jq curl + install_pkgs_darwin_fn coreutils websocketpp "boost@1.85" jansson libxkbcommon cppunit gnu-sed fontconfig doxygen graphviz tinyxml2 openldap krb5 + + # ORC causes compile errors on x86_64 Mac, but not on ARM64 + if [[ $ARCH == "x86_64" ]]; then + + # Workaround for making boost compatible with websocketpp (used for subtec) + + export BOOST_ROOT="/usr/local/opt/boost@1.85" + export CMAKE_PREFIX_PATH="/usr/local/opt/boost@1.85" + export LDFLAGS="-L/usr/local/opt/boost@1.85/lib -L/usr/local/lib -lwavpack" + export CPPFLAGS="-I/usr/local/opt/boost@1.85/include" + + echo "Checking/removing ORC package which cause compile errors with gst-plugins-good" + + # "|| true" prevents the script from exiting if orc is not found, that is not an error + ORC_FOUND=`brew list | grep -i orc | wc -l` || true + if [ "${ORC_FOUND}" -gt 0 ]; then + read -p "Found ORC, remove ORC package (Y/N)" remove_orc + case $remove_orc in + [Yy]* ) brew remove -f --ignore-dependencies orc + ;; + * ) echo "Exiting without removal ..." + return 1 + ;; + esac + fi + elif [[ $ARCH == "arm64" ]]; then + + # Workaround for making boost compatible with websocketpp (used for subtec) + export BOOST_ROOT="/opt/homebrew/opt/boost@1.85" + export CMAKE_PREFIX_PATH="/opt/homebrew/opt/boost@1.85" + export LDFLAGS="-L/opt/homebrew/opt/boost@1.85/lib -L/opt/homebrew/lib -lwavpack" + export CPPFLAGS="-I/opt/homebrew/opt/boost@1.85/include" + + install_pkgs_darwin_fn orc + fi + + #L1 dependency, we don't build this so treat like an installed pkg + cd ${LOCAL_DEPS_BUILD_DIR} + if [ ! -d ./systemd ]; then + echo "Installing systemd (required for L1 tests)" + do_clone_fn https://github.com/systemd/systemd.git + else + echo "systemd is already installed." + + fi + + + elif [[ "$OSTYPE" == "linux"* ]]; then + install_pkgs_linux_fn + fi +} diff --git a/middleware/scripts/install_glib.sh b/middleware/scripts/install_glib.sh new file mode 100644 index 000000000..40956e716 --- /dev/null +++ b/middleware/scripts/install_glib.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +function install_build_glib_fn() +{ + cd $LOCAL_DEPS_BUILD_DIR + + # $OPTION_CLEAN == true + if [ $1 = true ] ; then + echo "glib clean" + if [ -d glib ] ; then + rm -rf glib + # uninstall? + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgmock.a + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgmock_main.a + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgtest.a + fi + fi + + if [ -d "glib" ]; then + echo "glib is already installed" + INSTALL_STATUS_ARR+=("glib was already installed.") + else + # TODO: create a python virtual env so we don't have to globally install this pkg and avoid "error: externally-managed-environment" + PIP_BREAK_SYSTEM_PACKAGES=1 pip3 install setuptools + + echo "Installing glib..." + do_clone_fn https://github.com/GNOME/glib.git -b 2.78.0 + pushd glib + meson build && cd build + meson compile + INSTALL_STATUS_ARR+=("glib was successfully installed.") + popd + fi +} diff --git a/middleware/scripts/install_gstreamer.sh b/middleware/scripts/install_gstreamer.sh new file mode 100644 index 000000000..fd7d5f1da --- /dev/null +++ b/middleware/scripts/install_gstreamer.sh @@ -0,0 +1,131 @@ +#!/usr/bin/env bash + +declare DEFAULT_GSTVERSION="1.24.9" + + +function install_gstreamer_fn() +{ + # There is no CLEAN function + + if [[ "$OSTYPE" == "darwin"* ]]; then + # homebrew versions are not equivalent to the install package so we don't use it. In the future that may change. + + if [[ $ARCH == "x86_64" ]]; then + DEFAULT_GSTVERSION="1.18.6" + elif [[ $ARCH == "arm64" ]]; then + DEFAULT_GSTVERSION="1.24.9" + else + echo "Architecture $ARCH is unsupported" + return 1 + fi + + # Install Gstreamer and plugins if not installed + if [ -f /Library/Frameworks/GStreamer.framework/Versions/1.0/bin/gst-launch-1.0 ];then + if [ $(/Library/Frameworks/GStreamer.framework/Versions/1.0/bin/gst-launch-1.0 --version | head -n1 |cut -d " " -f 3) == $DEFAULT_GSTVERSION ] ; then + echo "gstreamer $DEFAULT_GSTVERSION is already installed" + return + fi + fi + echo "Installing GStreamer packages..." + + if [[ $ARCH == "x86_64" ]]; then + curl -o gstreamer-1.0-$DEFAULT_GSTVERSION-x86_64.pkg https://gstreamer.freedesktop.org/data/pkg/osx/$DEFAULT_GSTVERSION/gstreamer-1.0-$DEFAULT_GSTVERSION-x86_64.pkg + sudo installer -pkg gstreamer-1.0-$DEFAULT_GSTVERSION-x86_64.pkg -target / + rm gstreamer-1.0-$DEFAULT_GSTVERSION-x86_64.pkg + curl -o gstreamer-1.0-devel-$DEFAULT_GSTVERSION-x86_64.pkg https://gstreamer.freedesktop.org/data/pkg/osx/$DEFAULT_GSTVERSION/gstreamer-1.0-devel-$DEFAULT_GSTVERSION-x86_64.pkg + sudo installer -pkg gstreamer-1.0-devel-$DEFAULT_GSTVERSION-x86_64.pkg -target / + rm gstreamer-1.0-devel-$DEFAULT_GSTVERSION-x86_64.pkg + + elif [[ $ARCH == "arm64" ]]; then + curl -o gstreamer-1.0-$DEFAULT_GSTVERSION-universal.pkg https://gstreamer.freedesktop.org/data/pkg/osx/$DEFAULT_GSTVERSION/gstreamer-1.0-$DEFAULT_GSTVERSION-universal.pkg + sudo installer -pkg gstreamer-1.0-$DEFAULT_GSTVERSION-universal.pkg -target / + rm gstreamer-1.0-$DEFAULT_GSTVERSION-universal.pkg + curl -o gstreamer-1.0-devel-$DEFAULT_GSTVERSION-universal.pkg https://gstreamer.freedesktop.org/data/pkg/osx/$DEFAULT_GSTVERSION/gstreamer-1.0-devel-$DEFAULT_GSTVERSION-universal.pkg + sudo installer -pkg gstreamer-1.0-devel-$DEFAULT_GSTVERSION-universal.pkg -target / + rm gstreamer-1.0-devel-$DEFAULT_GSTVERSION-universal.pkg + # Gstreamer has a broken .pc prefix that can't be worked around with meson + grep non_existent_on_purpose /Library/Frameworks/GStreamer.framework/Libraries/pkgconfig/*.pc + retVal=$? + if [ $retVal -eq 0 ]; then + echo "Fixing Gstreamer.framework .pc files." + sudo sed -i '.bak' 's#prefix=.*#prefix=/Library/Frameworks/GStreamer.framework/Versions/1.0#' /Library/Frameworks/GStreamer.framework/Libraries/pkgconfig/* + fi + + fi + INSTALL_STATUS_ARR+=("gstreamer $DEFAULT_GSTVERSION was successfully installed.") + + elif [[ "$OSTYPE" == "linux"* ]]; then + echo "gstreamer is installed via apt on Linux targets." + fi +} + + + +function install_gstpluginsgoodfn() +{ + cd $LOCAL_DEPS_BUILD_DIR + + if [[ "$OSTYPE" == "darwin"* ]]; then + # homebrew versions are not equivalent to the install package so we don't use it. In the future that may change. + + # $OPTION_CLEAN == true + if [ $1 = true ] ; then + echo "gst-plugins-good clean" + if [ -d gst-plugins-good-$DEFAULT_GSTVERSION ] ; then + rm -rf gst-plugins-good-$DEFAULT_GSTVERSION + fi + fi + + if [ -d "gst-plugins-good-$DEFAULT_GSTVERSION" ]; then + echo "gst-plugins-good-$DEFAULT_GSTVERSION is already installed" + INSTALL_STATUS_ARR+=("gst-plugins-good-$DEFAULT_GSTVERSION was already installed.") + else + + curl -o gst-plugins-good-$DEFAULT_GSTVERSION.tar.xz https://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-$DEFAULT_GSTVERSION.tar.xz + tar -xvzf gst-plugins-good-$DEFAULT_GSTVERSION.tar.xz + cd gst-plugins-good-$DEFAULT_GSTVERSION + + patch -p1 < ../../OSx/patches/0009-qtdemux-tm_gst-1.16.patch + patch -p1 < ../../OSx/patches/0013-qtdemux-remove-override-segment-event_gst-1.16.patch + patch -p1 < ../../OSx/patches/0014-qtdemux-clear-crypto-info-on-trak-switch_gst-1.16.patch + patch -p1 < ../../OSx/patches/0021-qtdemux-tm-multiperiod_gst-1.16.patch + sed -in 's/gstglproto_dep\x27], required: true/gstglproto_dep\x27], required: false/g' meson.build + + + # + # NOTE: Don't casually change the order of PKG_CONFIG assignment otherwise the build will fail, or it will build OK, but fail at runtime + # with a gstreamer error. Is this a problem with pkg-config or meson, don't know. + # + # These particular packages are not found with the default search path for some reason, have to look them up. + PKG_CONFIG="$(install_pkgs_pkgconfig_darwin_fn orc)" + PKG_CONFIG+=":$(install_pkgs_pkgconfig_darwin_fn flac)" + PKG_CONFIG+=":$(install_pkgs_pkgconfig_darwin_fn mpg123)" + PKG_CONFIG+=":$(install_pkgs_pkgconfig_darwin_fn speex)" + PKG_CONFIG+=":$(install_pkgs_pkgconfig_darwin_fn taglib)" + PKG_CONFIG+=":$(install_pkgs_pkgconfig_darwin_fn jpeg-turbo)" + + PKG_CONFIG+=":/Library/Frameworks/GStreamer.framework/Versions/1.0/lib/pkgconfig" + if [[ $ARCH == "x86_64" ]]; then + PKG_CONFIG+=":/usr/local/lib/pkgconfig" + elif [[ $ARCH == "arm64" ]]; then + PKG_CONFIG+=":/opt/homebrew/lib/pkgconfig" + fi + + echo "Building gst-plugins-good with --pkg-config path $PKG_CONFIG..." + meson --pkg-config-path="${PKG_CONFIG}" build + ninja -C build + sudo ninja -C build install + + # ARM vs x86 have different installation directories + if [ -d /usr/local/lib/gstreamer-1.0 ]; then + sudo cp /usr/local/lib/gstreamer-1.0/libgstisomp4.dylib /Library/Frameworks/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/libgstisomp4.dylib + else + sudo cp /opt/homebrew/lib/gstreamer-1.0/libgstisomp4.dylib /Library/Frameworks/GStreamer.framework/Versions/1.0/lib/gstreamer-1.0/libgstisomp4.dylib + fi + INSTALL_STATUS_ARR+=("gst-plugins-good-$DEFAULT_GSTVERSION was successfully installed.") + fi + + elif [[ "$OSTYPE" == "linux"* ]]; then + echo "gst-plugins-good-$DEFAULT_GSTVERSION is installed via apt on Linux targets." + fi +} diff --git a/middleware/scripts/install_gtest.sh b/middleware/scripts/install_gtest.sh new file mode 100644 index 000000000..89c723689 --- /dev/null +++ b/middleware/scripts/install_gtest.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +declare GOOGLETEST_REFERENCE="tags/release-1.11.0" + +function install_build_googletest_fn() +{ + + cd $LOCAL_DEPS_BUILD_DIR + + # $OPTION_CLEAN == true + if [ $1 = true ] ; then + echo "googletest clean" + if [ -d googletest ] ; then + rm -rf googletest + # uninstall? + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgmock.a + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgmock_main.a + #rm $LOCAL_DEPS_BUILD_DIR/lib/libgtest.a + fi + fi + + if [ -d "googletest" ]; then + echo "googletest is already installed" + INSTALL_STATUS_ARR+=("googletest was already installed.") + else + echo "Installing googletest..." + do_clone_fn https://github.com/google/googletest + pushd googletest + echo "Checkout googletest '$GOOGLETEST_REFERENCE'" + git checkout $GOOGLETEST_REFERENCE + + ###Build gtest + echo "Building googletest" + mkdir -p build + cd build + if [[ "$OSTYPE" == "darwin"* ]]; then + env PKG_CONFIG_PATH=${LOCAL_DEPS_BUILD_DIR}/lib/pkgconfig cmake .. -DCMAKE_INSTALL_PREFIX=${LOCAL_DEPS_BUILD_DIR} + elif [[ "$OSTYPE" == "linux"* ]]; then + env PKG_CONFIG_PATH=${LOCAL_DEPS_BUILD_DIR}/lib/pkgconfig cmake .. -DCMAKE_PLATFORM_UBUNTU=1 -DCMAKE_INSTALL_PREFIX=${LOCAL_DEPS_BUILD_DIR} + fi + make + make install + INSTALL_STATUS_ARR+=("googletest was successfully installed.") + popd + fi +} diff --git a/middleware/scripts/install_libdash.sh b/middleware/scripts/install_libdash.sh new file mode 100755 index 000000000..78612f437 --- /dev/null +++ b/middleware/scripts/install_libdash.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + + +function install_build_libdash_fn() +{ + cd $LOCAL_DEPS_BUILD_DIR + + # $OPTION_CLEAN == true + if [ $1 = true ] ; then + echo "libdash clean" + if [ -d libdash ] ; then + rm -rf libdash + # uninstall? + rm -rf $LOCAL_DEPS_BUILD_DIR/include/libdash + fi + fi + + if [ -d "libdash" ]; then + echo "libdash is already installed" + INSTALL_STATUS_ARR+=("libdash was already installed.") + else + echo "Installing libdash..." + do_clone_fn https://github.com/bitmovin/libdash.git + + cd libdash/libdash + git checkout stable_3_0 + do_clone_fn -b rdk-next "https://code.rdkcentral.com/r/rdk/components/generic/rdk-oe/meta-rdk-ext" + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0001-libdash-build.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0002-libdash-starttime-uint64.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0003-libdash-presentationTimeOffset-uint64.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0004-Support-of-EventStream.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0005-DELIA-39460-libdash-memleak.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0006-RDK-32003-LLD-Support.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0007-DELIA-51645-Event-Stream-RawAttributes-Support.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0008-DELIA-53263-Use-Label-TAG.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0009-RDK-35134-Support-for-FailoverContent.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0010-RDKAAMP-121-Failover-Tag-on-SegmentTemplate.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0011-RDKAAMP-61-AAMP-low-latency-dash-stream-evaluation.patch + patch -p1 < meta-rdk-ext/recipes-multimedia/libdash/libdash/0012-To-retrieves-the-text-content-of-CDATA-section.patch + mkdir -p build + cd build + cmake .. -DCMAKE_INSTALL_PREFIX=${LOCAL_DEPS_BUILD_DIR} -DCMAKE_MACOSX_RPATH=TRUE + make + make install + + # why doesn't make install do this for us + cd .. + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/xml + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/mpd + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/network + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/portable + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/helpers + mkdir -p $LOCAL_DEPS_BUILD_DIR/include/libdash/metrics + cp -p libdash/include/* $LOCAL_DEPS_BUILD_DIR/include/libdash + cp -p libdash/source/xml/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/xml + cp -p libdash/source/mpd/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/mpd + cp -p libdash/source/network/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/network + cp -p libdash/source/portable/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/portable + cp -p libdash/source/helpers/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/helpers + cp -p libdash/source/metrics/*.h $LOCAL_DEPS_BUILD_DIR/include/libdash/metrics + echo -e 'prefix='$LOCAL_DEPS_BUILD_DIR'/lib \nexec_prefix='$LOCAL_DEPS_BUILD_DIR' \nlibdir='$LOCAL_DEPS_BUILD_DIR'/lib \nincludedir='$LOCAL_DEPS_BUILD_DIR'/include/libdash \n \nName: libdash \nDescription: ISO/IEC MPEG-DASH library \nVersion: 3.0 \nRequires: libxml-2.0 \nLibs: -L${libdir} -ldash \nLibs.private: -lxml2 \nCflags: -I${includedir}' > $LOCAL_DEPS_BUILD_DIR/lib/pkgconfig/libdash.pc + + INSTALL_STATUS_ARR+=("libdash was successfully installed.") + fi +} diff --git a/middleware/scripts/install_options.sh b/middleware/scripts/install_options.sh new file mode 100755 index 000000000..6d85b158a --- /dev/null +++ b/middleware/scripts/install_options.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +# default values +OPTION_PLAYER_BRANCH="dev_sprint_25_1" +OPTION_BUILD_DIR="" +OPTION_BUILD_ARGS="" +OPTION_CLEAN=false +OPTION_COVERAGE=false +OPTION_PROTOBUF_REFERENCE="3.11.x" +OPTION_QUICK=false +OPTION_RIALTO_REFERENCE="v0.2.2" +OPTION_RIALTO_BUILD=false +OPTION_SUBTEC_SKIP=false +OPTION_SUBTEC_BUILD=true +OPTION_SUBTEC_CLEAN=false +OPTION_GOOGLETEST_REFERENCE="tags/release-1.11.0" + + + +function install_options_fn() +{ + # Parse optional command line parameters + while getopts ":d:b:cf:p:r:g:qs" OPT; do + case ${OPT} in + d ) # process option d install base directory name + OPTION_BUILD_DIR=${OPTARG} + echo "${OPTARG}" + ;; + b ) # process option b code branch name + OPTION_PLAYER_BRANCH=${OPTARG} + ;; + c ) # process option c coverage + OPTION_COVERAGE=ON + echo coverage "${OPTION_COVERAGE}" + ;; + f )# process option f to get compiler flags + # add flags for cmake build by splitting buildargs with separator ',' + OPTION_BUILD_ARGS=${OPTARG} + echo "Additional build flags specified '${OPTARG}'" + ;; + g )# process option f to get googletest revision to build + OPTION_GOOGLETEST_REFERENCE=${OPTARG} + echo "${OPTARG}" + ;; + q )# quick option, skips installed (not built) dependency checks + OPTION_QUICK=true + echo "Skip : ${QUICK}" + ;; + r ) + OPTION_RIALTO_REFERENCE=${OPTARG} + echo "rialto tag : ${RIALTO_REFERENCE}" + ;; + s ) + OPTION_SUBTEC_SKIP=true + # overrides any subtec or subtec clean setting + echo "Skip subtec: ${OPTION_SKIP_SUBTEC}" + ;; + p ) + OPTION_PROTOBUF_REFERENCE=${OPTARG} + echo "protobuf branch : ${PROTOBUF_REFERENCE}" + ;; + * ) + echo "Usage: $0 [-b branch name] [-d local setup directory name] [-c] [-f compiler flags] [-g google release tag] [-n] [-q] [-s] [subtec [clean]]" + echo " [-r rialto tag] [-p protobuf branch name] [rialto] (Linux only)" + echo + echo "Note: Subtec is built by default but can be rebuilt separately with the subtec" + echo " option 'clean' will delete the subtec source and reinstall before" + echo " building" + echo + echo "Note: Rialto is built with the 'rialto' option. Use '-r' to set the rialto tag, " + echo " '-p' to set the Protobuf branch used for Rialto (Linux only)." + return 1 + ;; + esac + done + + # Parse project clean first, allows for subtec [clean] + if [[ ${@:$OPTIND:1} = "clean" ]]; then + OPTION_CLEAN=true + shift + fi + + # Parse subtec options + if [[ ${@:$OPTIND:1} = "subtec" ]]; then + OPTION_SUBTEC_BUILD=true + shift + if [[ ${@:$OPTIND:1} = "clean" ]]; then + OPTION_SUBTEC_CLEAN=true + shift + fi + fi + + if [[ ${@:$OPTIND:1} = "rialto" ]]; then + OPTION_RIALTO_BUILD=true + shift + fi + +} diff --git a/middleware/scripts/install_rialto.sh b/middleware/scripts/install_rialto.sh new file mode 100644 index 000000000..e63daf8a2 --- /dev/null +++ b/middleware/scripts/install_rialto.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + + +function rialto_install_fn() { + + # Install + if [ -d "protobuf" ]; then + echo "rialto is already installed" + INSTALL_STATUS_ARR+=("protobuf was already installed.") + else + do_clone_github_repo_fn https://github.com/protocolbuffers/protobuf.git protobuf -b ${OPTION_PROTOBUF_REFERENCE} --recursive + fi + + if [ -d "rialto" ]; then + echo "rialto exists" + INSTALL_STATUS_ARR+=("rialto was already installed.") + else + do_clone_fn https://github.com/rdkcentral/rialto.git rialto + pushd rialto + echo "Checkout rialto '${OPTION_RIALTO_REFERENCE}'" + git checkout ${OPTION_RIALTO_REFERENCE} + popd + fi + + if [ -d "rialto-gstreamer" ]; then + echo "rialto-gstreamer exists" + INSTALL_STATUS_ARR+=("rialto-streamer was already installed.") + else + do_clone_fn https://github.com/rdkcentral/rialto-gstreamer.git rialto-gstreamer + pushd rialto-gstreamer + echo "Checkout rialto-gstreamer '${OPTION_RIALTO_REFERENCE}'" + git checkout ${OPTION_RIALTO_REFERENCE} + popd + fi +} + +function rialto_build_repo_fn() +{ + echo "Building $1 " + pushd $1 + shift + mkdir -p build + cd build + env PKG_CONFIG_PATH="${LOCAL_DEPS_BUILD_DIR}/lib/pkgconfig" cmake .. -DCMAKE_LIBRARY_PATH="${LOCAL_DEPS_BUILD_DIR}/lib" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PLATFORM_UBUNTU=1 -DCMAKE_INSTALL_PREFIX="${LOCAL_DEPS_BUILD_DIR}" "$@" + make + make install + popd +} + +function rialto_build_fn() +{ + echo "Building protobuf" + pushd protobuf + ./autogen.sh + ./configure --prefix="${1}" + make + make install + popd + + rialto_build_repo_fn rialto -DNATIVE_BUILD=ON -DRIALTO_BUILD_TYPE=Debug + INSTALL_STATUS_ARR+=("rialto was successfully installed.") + + rialto_build_repo_fn rialto-gstreamer -DCMAKE_INCLUDE_PATH="${1}/rialto/stubs/opencdm/;${1}/rialto/media/public/include/" -DCMAKE_LIBRARY_PATH="${1}/rialto/build/stubs/opencdm;${1}/build/rialto/build/media/client/main/" -DNATIVE_BUILD=ON + INSTALL_STATUS_ARR+=("rialto was successfully built.") +} + +function rialto_install_build_fn() +{ + cd $LOCAL_DEPS_BUILD_DIR + + # OPTION_CLEAN == true + if [ ${1} == true ] ; then + echo " clean" + rm -rf protobuf + rm -rf rialto + rm -rf rialto-gstreamer + fi + + if [ ${OPTION_RIALTO_BUILD} == false ] ; then + return 0 + fi + + rialto_install_fn + + rialto_build_fn ${LOCAL_DEPS_BUILD_DIR} +} diff --git a/middleware/scripts/install_subtec.sh b/middleware/scripts/install_subtec.sh new file mode 100755 index 000000000..97bbbbaed --- /dev/null +++ b/middleware/scripts/install_subtec.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + + +function subtec_install_fn() { + + # Need PLAYER_DIR passed in so we can find patch files + if [ -z "${1}" ] ; then + echo "Middleware directory parameter is empty, can not find patch files." + return 1 + fi + # Need LOCAL_DEPS_BUILD_DIR passed in so we can patch the location of glib + if [ -z "${2}" ] ; then + echo "Dependency directory parameter is empty, can not patch subtec-app CMakeLists.txt" + return 1 + fi + + echo "Cloning subtec-app..." + do_clone_fn "https://code.rdkcentral.com/r/components/generic/subtec-app" + git -C subtec-app checkout a95f7591fff3fb8777781dfdc76d95fc0a1c382b + + echo + echo "Cloning websocket-ipplayer2-utils..." + do_clone_fn https://code.rdkcentral.com/r/components/generic/websocket-ipplayer2-utils subtec-app/websocket-ipplayer2-utils + git -C subtec-app/websocket-ipplayer2-utils checkout 2287fea4d1af0a632aed5f1b8bfba8babbdade1f + + + pushd subtec-app + echo "Patching subtec-app from ${1}" + git apply -p1 ${1}/OSX/patches/subttxrend-app-xkbcommon.patch + git apply -p1 ${1}/OSX/patches/subttxrend-app-packet.patch + git apply -p1 ${1}/OSX/patches/websocket-ipplayer2-link.patch --directory websocket-ipplayer2-utils + git apply -p1 ${1}/OSX/patches/websocket-ipplayer2-typescpp.patch --directory websocket-ipplayer2-utils + cp ${1}/OSX/patches/RDKLogoBlack.png subttxrend-gfx/quartzcpp/assets/RDKLogo.png + git apply -p1 ${1}/OSX/patches/subttxrend-app-ubuntu_24_04_build.patch + git apply -p1 ${1}/OSX/patches/websocket-ipplayer2-ubuntu_24_04_build.patch --directory websocket-ipplayer2-utils + + + echo "Patching subtec-app CMakeLists.txt with '$2'" + if [[ "$OSTYPE" == "darwin"* ]] ; then + SED_ARG="''" # MacOS -i has different -i argument + fi + sed -i ${SED_ARG} 's:COMMAND gdbus-codegen --interface-prefix com.libertyglobal.rdk --generate-c-code SubtitleDbusInterface ${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/SubtitleDbusInterface.xml:COMMAND '"${2}"'/glib/build/gio/gdbus-2.0/codegen/gdbus-codegen --interface-prefix com.libertyglobal.rdk --generate-c-code SubtitleDbusInterface ${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/SubtitleDbusInterface.xml:g' subttxrend-dbus/CMakeLists.txt + + sed -i ${SED_ARG} 's:COMMAND gdbus-codegen --interface-prefix com.libertyglobal.rdk --generate-c-code TeletextDbusInterface ${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/TeletextDbusInterface.xml:COMMAND '"${2}"'/glib/build/gio/gdbus-2.0/codegen/gdbus-codegen --interface-prefix com.libertyglobal.rdk --generate-c-code TeletextDbusInterface ${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/TeletextDbusInterface.xml:g' subttxrend-dbus/CMakeLists.txt + + echo "subtec-app source prepared" + popd +} + +function subtec_install_build_fn() { + + cd $LOCAL_DEPS_BUILD_DIR + + # OPTION_CLEAN == true + if [ $1 = true ] ; then + echo "subtec clean" + rm -rf subtec-app + fi + + + # Install + if [ -d "subtec-app" ]; then + echo "subtec-app is already installed" + INSTALL_STATUS_ARR+=("subtec-app was already installed.") + else + # Tell installer where DEPs are so cmake can be patched + subtec_install_fn ${PLAYER_DIR} ${LOCAL_DEPS_BUILD_DIR} + fi + + # Build + cd subtec-app/subttxrend-app/x86_builder/ + + if [ ! -d build/install ] ; then + PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig:/usr/local/ssl/lib/pkgconfig:/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH ./build.sh fast + + if [ -f ./build/install/usr/local/bin/subttxrend-app ]; then + echo "subtec-app has been built." + INSTALL_STATUS_ARR+=("subtec-app has been built.") + else + echo "subtec-app build has failed." + return 1 + fi + fi +} diff --git a/middleware/scripts/tools.sh b/middleware/scripts/tools.sh new file mode 100755 index 000000000..b91106a75 --- /dev/null +++ b/middleware/scripts/tools.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +function tools_banner_fn () +{ + + echo "" + echo " ###### #### # #### #### ###### ### # #### #### " + echo " ## ## ## ### ### ### ## ## # ## ## ## " + echo " ## ## ## ### ## ## ## ## ## # ## ## " + echo " ## ## ## # ## ## ## ## ## ## ## ## " + echo " ##### ## # ## ##### ##### ##### ## ## ## " + echo " ## ## # ####### ## ## ## ## # ## # ## " + echo " ## ## # # ## ## ## ## # # ## # ## " + echo " #### ###### ### #### #### #### ### #### ###### #### " + echo "" +} + +function tools_arch_fn () +{ + if [[ "${OSTYPE}" == "darwin"* ]]; then + ARCH=$(uname -m) + if [[ ${ARCH} == "x86_64" ]]; then + export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH + echo "${ARCH}" + elif [[ ${ARCH} == "arm64" ]]; then + export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH + echo "${ARCH}" + else + echo "Architecture $ARCH is unsupported" + return 1 + fi + + elif [[ "${OSTYPE}" == "linux"* ]]; then + : + else + echo "OS ${OSTYPE} is unsupported" + return 1 + fi +} + +function tools_install_fn () +{ + if [[ "${OSTYPE}" == "darwin"* ]]; then + + echo "Checking Xcode..." + + #Install XCode Command Line Tools + MIN_MACOS_VER=10.15 + + CUR_MACOS_VER=$(sw_vers | grep ProductVersion | grep -Eo '[0-9]+\.[0-9]+') + if [[ -z $(which xcrun) ]] ; then + CUR_XTOOLS_VER="" + else + CUR_XTOOLS_VER=$(xcrun --sdk macosx --show-sdk-path | grep -Eo '[0-9]+\.[0-9]+') || true + fi + + if [[ -z $(which xcodebuild) ]] ; then + CUR_XCODE_VER="" + else + CUR_XCODE_VER=$(xcodebuild -version | grep Xcode | grep -Eo '[0-9]+\.[0-9]+') || true + fi + + echo "MacOS ${CUR_MACOS_VER} / Xcode ${CUR_XCODE_VER} / Command Line Tools ${CUR_XTOOLS_VER}" + + if [[ -z ${CUR_XTOOLS_VER} || -z ${CUR_XCODE_VER} ]] ; then + echo "Xcode or Xcode CLI tools installation not found." + echo -e " + XCode and Xcode Command Line Tools can be found here. + https://developer.apple.com/download/all/?q=xcode%20${CUR_MACOS_VER} + + Download the required XIP file(s). Double-click the XIP file(s) to make its content available + (the name will show up in the Finder sidebar), and a window generally opens also showing the + content. Drag the application from the dialog into the Applications directory to install (you + may need an administrator password). Wait for the copy process to finish. + + Then run the following: + sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_\${CUR_TOOLS_VER}.pkg -target / + where \${CUR_TOOLS_VER} is the tools version number, e.g. 13.3 or 14.4 + sudo xcode-select -s /Applications/Xcode.app/Contents/Developer +" + return 1 + fi + + #Check if Xcode is installed + if XPATH=$( xcode-select --print-path ) && + test -d "${XPATH}" && test -x "${XPATH}" ; then + echo "Xcode tools installed in '${XPATH}'" + else + #... isn't correctly installed + echo "Xcode tools installation not found." + return 1 + fi + + # Check if homebrew is installed + if [[ -z $(which brew) ]] ; then + echo "brew installation not found." + echo -e " +To install brew download and run the install script. +curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh" + return 1 + fi + + elif [[ "${OSTYPE}" == "linux"* ]]; then + + echo "Checking VSCode..." + sudo snap install --classic code || RC=$? + if [ "${RC}" != 0 ] ; then + INSTALL_STATUS_ARR+=("WARNING tools_install_fn: snap install VSCode failed, not halting the build.") + return 0 + fi + + echo "Installing VSCode Dependencies..." + code --install-extension ms-vscode.cmake-tools + fi +} + +function tools_print_summary_fn() +{ +echo "" +echo "********PLAYER install summary start************" + +for item in "${!INSTALL_STATUS_ARR[@]}"; + do + echo "$item ${INSTALL_STATUS_ARR[$item]}" + done +echo "" +echo "********PLAYER install summary end*************" + +echo -n "build completed $(date) elapsed time " +if [[ "${OSTYPE}" == "darwin"* ]]; then + date -ju -f "%s" ${SECONDS} +%T +else + date -u -d "@$SECONDS" +%T +fi +echo "" +} diff --git a/middleware/subtec/libsubtec/ClosedCaptionsPacket.hpp b/middleware/subtec/libsubtec/ClosedCaptionsPacket.hpp new file mode 100644 index 000000000..65f8580b7 --- /dev/null +++ b/middleware/subtec/libsubtec/ClosedCaptionsPacket.hpp @@ -0,0 +1,117 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include "SubtecPacket.hpp" +#include "SubtecChannel.hpp" +#include "PacketSender.hpp" + + +class ClosedCaptionsPacket : public Packet +{ +public: + + ClosedCaptionsPacket(uint32_t channelId, uint32_t counter, uint32_t ptsValue, uint8_t* data, size_t dataLen) + { + appendType(Packet::PacketType::CC_DATA); + append32(counter); + append32( (uint32_t)(CC_DATA_HEADER_LEN+dataLen) ); + append32(channelId); + append32(CC_CHANNEL_TYPE); + append32(1); // pts presence + append32(ptsValue); + for(int i = 0; i uint32_t + { + return static_cast(e); + }; + + appendType(Packet::PacketType::SUBTITLE_SELECTION); + append32(counter); + append32(CC_SELECTION_LEN); + append32(channelId); + append32(CC_USERDATA_SUBTITLE_TYPE); + append32(to_integral(type)); //aux id 1 + append32(service);//aux id 2 + + + } + + +private: + static constexpr std::uint8_t CC_SELECTION_LEN = 16; + static constexpr std::uint8_t CC_USERDATA_SUBTITLE_TYPE = 3; +}; + +class ClosedCaptionsChannel : public SubtecChannel +{ +public: + ClosedCaptionsChannel() {} + + void SendDataPacketWithPTS(uint32_t ptsValue, uint8_t* data, size_t dataLen) + { + std::unique_lock lock(mChannelMtx); + PacketSender::Instance()->SendPacket(std::unique_ptr(new ClosedCaptionsPacket(m_channelId, m_counter++, ptsValue, data, dataLen))); + } + + void SendDataPacketNoPTS(uint8_t* data, size_t dataLen) + { + std::unique_lock lock(mChannelMtx); + PacketSender::Instance()->SendPacket(std::unique_ptr(new ClosedCaptionsPacket(m_channelId, m_counter++, data, dataLen))); + } + + void SendActiveTypePacket(ClosedCaptionsActiveTypePacket::CEA type, unsigned int channel) + { + std::unique_lock lock(mChannelMtx); + PacketSender::Instance()->SendPacket(std::unique_ptr(new ClosedCaptionsActiveTypePacket(m_channelId, m_counter++, type, channel))); + } +}; diff --git a/middleware/subtec/libsubtec/PacketSender.cpp b/middleware/subtec/libsubtec/PacketSender.cpp new file mode 100644 index 000000000..d397d387e --- /dev/null +++ b/middleware/subtec/libsubtec/PacketSender.cpp @@ -0,0 +1,246 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include + +#include "SubtecPacket.hpp" +#include "PacketSender.hpp" +#include "PlayerLogManager.h" // Included for MW_LOG + +#define MAX_SNDBUF_SIZE (8*1024*1024) + +void runWorkerTask(void *ctx) +{ + try { + PacketSender *pkt = reinterpret_cast(ctx); + pkt->senderTask(); + } + catch (const std::exception& e) { + MW_LOG_WARN("PacketSender: Error in run %s", e.what()); + } +} + +PacketSender *PacketSender::Instance() +{ + static PacketSender instance; + return &instance; +} + +PacketSender::~PacketSender() +{ + PacketSender::Close(); +} + +void PacketSender::Close() +{ + closeSenderTask(); + if (mSubtecSocketHandle) + ::close(mSubtecSocketHandle); + mSubtecSocketHandle = 0; +} + +void PacketSender::Flush() +{ + flushPacketQueue(); +} + +bool PacketSender::Init() +{ + return Init(SOCKET_PATH); +} + +bool PacketSender::Init(const char *socket_path) +{ + bool ret = true; + std::unique_lock lock(mStartMutex); + + MW_LOG_INFO("PacketSender::Init with %s", socket_path); + + if (!running) + { + mSocketPath = socket_path; + ret = initSocket(socket_path) && initSenderTask(); + if (!ret) { + MW_LOG_WARN("SenderTask failed to init"); + } + else + MW_LOG_WARN("senderTask started"); + } + else + MW_LOG_WARN("PacketSender::Init already running"); + + return ret; +} + +void PacketSender::SendPacket(PacketPtr && packet) +{ + std::unique_lock lock(mPktMutex); + uint32_t type = packet->getType(); + std::string typeString = Packet::getTypeString(type); + MW_LOG_TRACE("PacketSender: queue size %zu type %s:%d counter:%d", + mPacketQueue.size(), typeString.c_str(), type, packet->getCounter()); + + mPacketQueue.push(std::move(packet)); + mCv.notify_all(); +} + +void PacketSender::senderTask() +{ + std::unique_lock lock(mPktMutex); + do { + running = true; + mCv.wait(lock); + while (!mPacketQueue.empty()) + { + sendPacket(std::move(mPacketQueue.front())); + mPacketQueue.pop(); + MW_LOG_TRACE("PacketSender: queue size %zu", mPacketQueue.size()); + } + } while(running); +} + +bool PacketSender::IsRunning() +{ + std::unique_lock lock(mPktMutex); + return running.load(); +} + +void PacketSender::flushPacketQueue() +{ + std::queue empty; + std::unique_lock lock(mPktMutex); + + empty.swap(mPacketQueue); +} + +void PacketSender::sendPacket(PacketPtr && pkt) +{ + if(!pkt) + { + MW_LOG_ERR("PacketSender: pkt is null pointer"); + return; + } + auto buffer = pkt->getBytes(); + size_t size = static_cast(buffer.size()); + if (size > mSockBufSize && size < MAX_SNDBUF_SIZE) + { + int newSize = (int)buffer.size(); + if (::setsockopt(mSubtecSocketHandle, SOL_SOCKET, SO_SNDBUF, &newSize, sizeof(newSize)) == -1) + { + MW_LOG_WARN("::setsockopt() SO_SNDBUF failed"); + } + else + { + mSockBufSize = newSize; + MW_LOG_INFO("new socket buffer size %d", mSockBufSize); + } + } + auto written = ::write(mSubtecSocketHandle, &buffer[0], size); + MW_LOG_TRACE("PacketSender: Written %ld bytes with size %zu", static_cast(written), size); + + //Socket reconnect in case packet write fails + if (written == -1) { + mPktWriteFailCtr++; + MW_LOG_TRACE("PacketSender: Write returned -1 with error: %s", strerror(errno)); + } else { + mPktWriteFailCtr = 0; + } + + //Try reconnect after every 5 failed packet writes + if (mPktWriteFailCtr > 5) { + MW_LOG_INFO("PacketSender: Written is -1 for over 5 consecutive packets. Try to reconnect socket"); + + struct sockaddr_un addr; + + (void) std::memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + (void) std::strncpy(addr.sun_path, mSocketPath.c_str(), sizeof(addr.sun_path)); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + if (::connect(mSubtecSocketHandle, reinterpret_cast(&addr), sizeof(addr)) != 0) { + MW_LOG_WARN("PacketSender: cannot reconnect to address \'%s\'", mSocketPath.c_str()); + } else { + MW_LOG_INFO("PacketSender: successful reconnect to address \'%s\'", mSocketPath.c_str()); + } + mPktWriteFailCtr = 0; + } +} + +bool PacketSender::initSenderTask() +{ + try { + mSendThread = std::thread(runWorkerTask, this); + MW_LOG_INFO("Thread created for runWorkerTask [%zx]", std::hash()(mSendThread.get_id())); + } + catch (const std::exception& e) { + MW_LOG_WARN("PacketSender: Error in initSenderTask: %s", e.what()); + return false; + } + + return true; +} + +void PacketSender::closeSenderTask() +{ + if (running) + { + running = false; + mCv.notify_all(); + if (mSendThread.joinable()) + { + mSendThread.join(); + } + } + +} + +bool PacketSender::initSocket(const char *socket_path) +{ + mSubtecSocketHandle = ::socket(AF_UNIX, SOCK_DGRAM, 0); + if (mSubtecSocketHandle == -1) + { + MW_LOG_WARN("PacketSender: Unable to init socket"); + return false; + } + + struct sockaddr_un addr; + + (void) std::memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + (void) std::strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + socklen_t optlen = sizeof(mSockBufSize); + if(::getsockopt(mSubtecSocketHandle, SOL_SOCKET, SO_SNDBUF, &mSockBufSize, &optlen) != 0) + { + MW_LOG_WARN("PacketSender: getsockopt Fails"); + } + mSockBufSize = mSockBufSize / 2; //kernel returns twice the value of actual buffer + MW_LOG_INFO("SockBuffer size : %d", mSockBufSize); + + if (::connect(mSubtecSocketHandle, reinterpret_cast(&addr), sizeof(addr)) != 0) + { + ::close(mSubtecSocketHandle); + MW_LOG_WARN("PacketSender: cannot connect to address \'%s\'", socket_path); + return false; + } + MW_LOG_INFO("PacketSender: Initialized with socket_path %s", socket_path); + + return true; +} diff --git a/middleware/subtec/libsubtec/PacketSender.hpp b/middleware/subtec/libsubtec/PacketSender.hpp new file mode 100644 index 000000000..4e087f18a --- /dev/null +++ b/middleware/subtec/libsubtec/PacketSender.hpp @@ -0,0 +1,103 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SUBTEC_PACKET_DEBUG + +#undef MW_LOG_WARN +#undef MW_LOG_INFO +#undef MW_LOG_TRACE +#undef MW_LOG_ERR +#undef MW_LOG_DEBUG + +#define MW_LOG_WARN(...) printf +#define MW_LOG_INFO(...) printf +#define MW_LOG_TRACE(...) printf +#define MW_LOG_ERR(...) printf +#define MW_LOG_DEBUG(...) printf + +#endif + +#include "SubtecPacket.hpp" + +#if defined (__APPLE__) || defined(UBUNTU) +const constexpr char *SOCKET_PATH = "/tmp/pes_data_main"; // simulator +#else +const constexpr char *SOCKET_PATH = "/run/subttx/pes_data_main"; // device +#endif + +void runWorkerTask(void *ctx); + +class PacketSender +{ +public: + ~PacketSender(); + + void Close(); + void Flush(); + bool Init(); + bool Init(const char *socket_path); + void SendPacket(PacketPtr && packet); + void senderTask(); + bool IsRunning(); + static PacketSender *Instance(); +private: + void closeSenderTask(); + void flushPacketQueue(); + void sendPacket(PacketPtr && pkt); + bool initSenderTask(); + bool initSocket(const char *socket_path); + + std::thread mSendThread; + int mSubtecSocketHandle; + std::atomic_bool running; + std::queue mPacketQueue; + std::mutex mPktMutex; + std::condition_variable mCv; + std::mutex mStartMutex; + int mSockBufSize; + int mPktWriteFailCtr; + std::string mSocketPath; +protected: + PacketSender() : + mSendThread(), + mSubtecSocketHandle(-1), + running(false), + mPacketQueue(), + mPktMutex(), + mCv(), + mStartMutex(), + mSockBufSize(0), + mPktWriteFailCtr(0), + mSocketPath("") + {} +}; diff --git a/middleware/subtec/libsubtec/SubtecAttribute.hpp b/middleware/subtec/libsubtec/SubtecAttribute.hpp new file mode 100644 index 000000000..44e18e7d8 --- /dev/null +++ b/middleware/subtec/libsubtec/SubtecAttribute.hpp @@ -0,0 +1,30 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +/** + * @file SubtecAttribute.hpp + * + * @brief This file has been created to provide common definition related to Attribute. + * + */ +#include +#pragma once + +#define NUMBER_OF_ATTRIBUTES 14 + +using attributesType = std::array; /**< attributesType is alias for an array of NUMBER_OF_ATTRIBUTES uint32_t */ diff --git a/middleware/subtec/libsubtec/SubtecChannel.cpp b/middleware/subtec/libsubtec/SubtecChannel.cpp new file mode 100644 index 000000000..1955cc7ab --- /dev/null +++ b/middleware/subtec/libsubtec/SubtecChannel.cpp @@ -0,0 +1,124 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include + +#include "PacketSender.hpp" +#include "SubtecChannel.hpp" +#include "SubtecPacket.hpp" +#include "TtmlPacket.hpp" +#include "WebVttPacket.hpp" +#include "ClosedCaptionsPacket.hpp" +#include "PlayerMetadata.hpp" + +/** + * @brief Function to convert a string to uppercase + * + * @retval String + */ +std::string player_ConvertToUpperCase(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), ::toupper); + return result; +} + +std::unique_ptr SubtecChannel::SubtecChannelFactory(ChannelType type) +{ + std::unique_ptr subtecChannel; + + switch (type) + { + case ChannelType::TTML: + subtecChannel = player_utils::make_unique(); + break; + case ChannelType::WEBVTT: + subtecChannel = player_utils::make_unique(); + break; + case ChannelType::CC: + break; + default: + break; + } + + return subtecChannel; +} + +bool SubtecChannel::InitComms() +{ + std::string playerName = GetPlayerName(); + std::string subtitleSocket = player_ConvertToUpperCase(playerName) + "_SUBTITLE_SOCKET"; // Prefixing PlayerName to subtitleSocket + MW_LOG_INFO("Subtitle Socket Path ENV:[%s]",subtitleSocket.c_str()); + const char *socket_path = ::getenv(subtitleSocket.c_str()); + + if (!socket_path) + { + socket_path = SOCKET_PATH; + } + + return InitComms(socket_path); +} + +bool SubtecChannel::InitComms(const char* socket_path) +{ + return PacketSender::Instance()->Init(socket_path); +} + + +template +void SubtecChannel::sendPacket(Args && ...args) +{ + std::unique_lock lock(mChannelMtx); + PacketSender::Instance()->SendPacket(player_utils::make_unique(m_channelId, m_counter++, std::forward(args)...)); +} + +void SubtecChannel::SendResetAllPacket() +{ + std::unique_lock lock(mChannelMtx); + m_counter = 1; + PacketSender::Instance()->SendPacket(player_utils::make_unique()); +} + +void SubtecChannel::SendResetChannelPacket() { + sendPacket(); +} +void SubtecChannel::SendPausePacket() { + sendPacket(); +} +void SubtecChannel::SendResumePacket() { + sendPacket(); +} +void SubtecChannel::SendMutePacket() { + sendPacket(); +} +void SubtecChannel::SendUnmutePacket() { + sendPacket(); +} +void SubtecChannel::SendCCSetAttributePacket(std::uint32_t ccType, std::uint32_t attribType, const attributesType &attributesValues) { + MW_LOG_INFO("SendCCSetAttributePacket bit mask is 0x%X", attribType); + for(uint i = 0; i < attributesValues.size(); i++) + { + if (attribType & (1 << i)) + { + MW_LOG_TRACE("SendCCSetAttributePacket attribute[%u]: %u", i, attributesValues[i]); + } + } + sendPacket(ccType, attribType, attributesValues); +} + +SubtecChannel::~SubtecChannel() {} diff --git a/middleware/subtec/libsubtec/SubtecChannel.hpp b/middleware/subtec/libsubtec/SubtecChannel.hpp new file mode 100644 index 000000000..d9fd85c98 --- /dev/null +++ b/middleware/subtec/libsubtec/SubtecChannel.hpp @@ -0,0 +1,93 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include "SubtecAttribute.hpp" + +namespace player_utils +{ + template + std::unique_ptr make_unique(Args&& ...args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } +} + +class SubtecChannelManager +{ +public: + static SubtecChannelManager *getInstance() + { + static SubtecChannelManager instance; + return &instance; + } + int getNextChannelId() { return m_nextChannelId++; } +protected: + SubtecChannelManager() : m_nextChannelId(0) {} +private: + uint32_t m_nextChannelId; +}; + +class SubtecChannel +{ + +protected: + template + void sendPacket(Args && ...args); +public: + enum class ChannelType + { + TTML, + WEBVTT, + CC + }; + + SubtecChannel() : m_counter(0), m_channelId(0), mChannelMtx() + { + m_channelId = SubtecChannelManager::getInstance()->getNextChannelId(); + } + + static std::unique_ptr SubtecChannelFactory(ChannelType type); + + static bool InitComms(); + static bool InitComms(const char* socket_path); + void SendResetAllPacket(); + void SendResetChannelPacket(); + void SendPausePacket(); + void SendResumePacket(); + void SendMutePacket(); + void SendUnmutePacket(); + void SendCCSetAttributePacket(std::uint32_t ccType, std::uint32_t attribType, const attributesType &attributesValues); + + virtual void SendSelectionPacket(uint32_t width, uint32_t height){}; + virtual void SendDataPacket(std::vector &&data, std::int64_t time_offset_ms = 0){}; + virtual void SendTimestampPacket(uint64_t timestampMs){}; + + virtual ~SubtecChannel() = 0; + +protected: + uint32_t m_channelId; + uint32_t m_counter; + std::mutex mChannelMtx; +}; diff --git a/middleware/subtec/libsubtec/SubtecPacket.hpp b/middleware/subtec/libsubtec/SubtecPacket.hpp new file mode 100644 index 000000000..68f8f9c06 --- /dev/null +++ b/middleware/subtec/libsubtec/SubtecPacket.hpp @@ -0,0 +1,392 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "SubtecAttribute.hpp" + +class Packet +{ +public: + Packet() : m_buffer(), m_counter(std::numeric_limits::max()) {} + Packet(std::uint32_t counter) : m_buffer(), m_counter(counter) {} + + const uint32_t getType() + { + uint32_t type = 0; + + if (getBuffer().size() >= 4) + { + std::vector buffer = getBuffer(); + for (int i = 0; i < 4; i++) + { + type += (buffer[i] << (i*8)) & 0xFF; + } + } + return type; + } + + const std::vector& getBytes() + { + return m_buffer; + } + + const std::uint32_t getCounter() + { + return m_counter; + } + + static std::string getTypeString(uint32_t type) + { + std::string ret; + PacketType pktType = static_cast(type); + + switch(pktType) + { + case PacketType::PES_DATA: + ret = "PES_DATA"; + break; + case PacketType::TIMESTAMP: + ret = "TIMESTAMP"; + break; + case PacketType::RESET_ALL: + ret = "RESET_ALL"; + break; + case PacketType::RESET_CHANNEL: + ret = "RESET_CHANNEL"; + break; + case PacketType::SUBTITLE_SELECTION: + ret = "SUBTITLE_SELECTION"; + break; + case PacketType::TELETEXT_SELECTION: + ret = "TELETEXT_SELECTION"; + break; + case PacketType::TTML_SELECTION: + ret = "TTML_SELECTION"; + break; + case PacketType::TTML_DATA: + ret = "TTML_DATA"; + break; + case PacketType::TTML_TIMESTAMP: + ret = "TTML_TIMESTAMP"; + break; + case PacketType::WEBVTT_SELECTION: + ret = "WEBVTT_SELECTION"; + break; + case PacketType::WEBVTT_DATA: + ret = "WEBVTT_DATA"; + break; + case PacketType::WEBVTT_TIMESTAMP: + ret = "WEBVTT_TIMESTAMP"; + break; + case PacketType::CC_DATA : + ret = "CC_DATA"; + break; + case PacketType::PAUSE : + ret = "PAUSE"; + break; + case PacketType::RESUME : + ret = "RESUME"; + break; + case PacketType::MUTE : + ret = "MUTE"; + break; + case PacketType::UNMUTE : + ret = "UNMUTE"; + break; + case PacketType::CC_SET_ATTRIBUTE: + ret = "CC_SET_ATTRIBUTE"; + break; + case PacketType::INVALID: + ret = "INVALID"; + break; + default: + ret = "UNKNOWN"; + break; + } + + return ret; + } + + +protected: + std::vector& getBuffer() { return m_buffer; } + + enum class PacketType : std::uint32_t + { + ZERO, + PES_DATA, + TIMESTAMP, + RESET_ALL, + RESET_CHANNEL, + SUBTITLE_SELECTION, + TELETEXT_SELECTION, + TTML_SELECTION, + TTML_DATA, + TTML_TIMESTAMP, + CC_DATA, + PAUSE, + RESUME, + MUTE, + UNMUTE, + WEBVTT_SELECTION, + WEBVTT_DATA, + WEBVTT_TIMESTAMP, + CC_SET_ATTRIBUTE, + + INVALID = 0xFFFFFFFF, + }; + + std::vector m_buffer; + std::uint32_t m_counter; + + void append32(std::uint32_t value) + { + m_buffer.push_back((static_cast((value >> 0)) & 0xFF)); + m_buffer.push_back((static_cast((value >> 8)) & 0xFF)); + m_buffer.push_back((static_cast((value >> 16)) & 0xFF)); + m_buffer.push_back((static_cast((value >> 24)) & 0xFF)); + } + + void append64(std::int64_t value) + { + append32((static_cast((value >> 0)) & 0xFFFFFFFF)); + append32((static_cast((value >> 32)) & 0xFFFFFFFF)); + } + + void appendType(PacketType type) + { + append32(static_cast::type>(type)); + } +}; + +using PacketPtr = std::unique_ptr; + +class DummyPacket : public Packet +{ +public: + DummyPacket() : Packet() {} +}; + +/** + * Pause packet. + */ +class PausePacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + PausePacket(std::uint32_t channelId, + std::uint32_t counter) : Packet(counter) + { + appendType(PacketType::PAUSE); + append32(counter); + append32(4); + append32(channelId); + } +}; + +/** + * Resume packet. + */ +class ResumePacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + ResumePacket(std::uint32_t channelId, + std::uint32_t counter) : Packet(counter) + { + appendType(PacketType::RESUME); + append32(counter); + append32(4); + append32(channelId); + } +}; + +/** + * Mute packet. + */ +class MutePacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + MutePacket(std::uint32_t channelId, + std::uint32_t counter) : Packet(counter) + { + appendType(PacketType::MUTE); + append32(counter); + append32(4); + append32(channelId); + } +}; + +/** + * Mute packet. + */ +class UnmutePacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + UnmutePacket(std::uint32_t channelId, + std::uint32_t counter) : Packet(counter) + { + appendType(PacketType::UNMUTE); + append32(counter); + append32(4); + append32(channelId); + } +}; + +/** + * Reset all data packet. + */ + +class ResetAllPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + ResetAllPacket() : Packet(0) + { + appendType(PacketType::RESET_ALL); + append32(0); + append32(0); + } +}; + +/** + * Reset all data packet. + */ + +class ResetChannelPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + ResetChannelPacket(std::uint32_t channelId, + std::uint32_t counter) : Packet(counter) + { + appendType(PacketType::RESET_CHANNEL); + append32(counter); + append32(4); + append32(channelId); + } +}; + + +/* + +field size value description +type 4 18 message type +counter 4 0..n +size 4 68 specifies size of transferred "data" that includes channelId, ccType, attribType and attributes payload + +data: + +channelId 4 Specifies channel on which data for subtitles is transmitted. +ccType 4 {0,1} 0 - analog, 1 - digital +attribType 4 bitmask specifying which attribs are set + +attributes payload: - +1. FONT_COLOR 4 0..n +2. BACKGROUND_COLOR 4 0..n +3. FONT_OPACITY 4 0..n +4. BACKGROUND_OPACITY 4 0..n +5. FONT_STYLE 4 0..n +6. FONT_SIZE 4 0..n +7. FONT_ITALIC 4 0..n +8. FONT_UNDERLINE 4 0..n +9. BORDER_TYPE 4 0..n +10. BORDER_COLOR 4 0..n +11. WIN_COLOR 4 0..n +12. WIN_OPACITY 4 0..n +13. EDGE_TYPE 4 0..n +14. EDGE_COLOR 4 0..n + +When adding/removing an attribute to the above list, update the definition in the file SubtecAttribute.hpp + +*/ + +class CCSetAttributePacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + CCSetAttributePacket(std::uint32_t channelId, + std::uint32_t counter, + std::uint32_t ccType, + std::uint32_t attribType, + const attributesType &attributesValues) : Packet(counter) + { + appendType(PacketType::CC_SET_ATTRIBUTE); + append32(counter); + append32((NUMBER_OF_ATTRIBUTES+3)*4); + append32(channelId); + append32(ccType); + append32(attribType); + + for(const auto value : attributesValues) + append32(value); + } +}; diff --git a/middleware/subtec/libsubtec/TtmlPacket.hpp b/middleware/subtec/libsubtec/TtmlPacket.hpp new file mode 100644 index 000000000..3ac0e45df --- /dev/null +++ b/middleware/subtec/libsubtec/TtmlPacket.hpp @@ -0,0 +1,130 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include "SubtecPacket.hpp" +#include "SubtecChannel.hpp" + +class TtmlSelectionPacket : public Packet +{ +public: + /** + * Constructor. + * + * @param channelId + * Packet channelId. + * @param counter + * Packet counter. + * @param width + * Display width. + * @param height + * Display height. + */ + TtmlSelectionPacket(uint32_t channelId, uint32_t counter, uint32_t width, uint32_t height) : Packet(counter) + { + appendType(Packet::PacketType::TTML_SELECTION); + append32(counter); + append32(TTML_SELECTION_PACKET_SIZE); + append32(channelId); + append32(width); + append32(height); + } + +private: + static constexpr std::uint8_t TTML_SELECTION_PACKET_SIZE = 12; +}; + +class TtmlDataPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param channelId + * Packet channelId. + * @param counter + * Packet counter. + * @param dataOffset + * Data offset if needed + * @param dataBuffer + * Packet data. + */ + TtmlDataPacket(std::uint32_t channelId, + std::uint32_t counter, + std::int64_t dataOffset, + std::vector &&dataBuffer) : Packet(counter) + { + auto& buffer = getBuffer(); + uint32_t size = (uint32_t)(8 + 4 + dataBuffer.size()); + + appendType(PacketType::TTML_DATA); + append32(counter); + append32(size); + append32(channelId); + append64(dataOffset); + + buffer.insert(buffer.end(), dataBuffer.begin(), dataBuffer.end()); + } +}; + + +class TtmlTimestampPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + TtmlTimestampPacket(std::uint32_t channelId, + std::uint32_t counter, + std::uint64_t timestamp) : Packet(counter) + { + appendType(PacketType::TTML_TIMESTAMP); + append32(counter); + append32(TTML_TIMESTAMP_PACKET_SIZE); + append32(channelId); + append64(timestamp); + } + +private: + + static constexpr std::uint8_t TTML_TIMESTAMP_PACKET_SIZE = 12; +}; + + +class TtmlChannel : public SubtecChannel +{ +public: + TtmlChannel() : SubtecChannel() {} + + virtual void SendSelectionPacket(uint32_t width, uint32_t height) override { + sendPacket(width, height); + } + virtual void SendDataPacket(std::vector &&data, std::int64_t time_offset_ms = 0) override { + sendPacket(time_offset_ms, std::move(data)); + } + virtual void SendTimestampPacket(uint64_t timestampMs) override { + sendPacket(timestampMs); + } +}; diff --git a/middleware/subtec/libsubtec/WebVttPacket.hpp b/middleware/subtec/libsubtec/WebVttPacket.hpp new file mode 100644 index 000000000..2f89e0859 --- /dev/null +++ b/middleware/subtec/libsubtec/WebVttPacket.hpp @@ -0,0 +1,131 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include "SubtecPacket.hpp" +#include "SubtecChannel.hpp" + +class WebVttSelectionPacket : public Packet +{ +public: + /** + * Constructor. + * + * @param channelId + * Packet channelId. + * @param counter + * Packet counter. + * @param width + * Display width. + * @param height + * Display height. + */ + WebVttSelectionPacket(uint32_t channelId, uint32_t counter, uint32_t width, uint32_t height) : Packet(counter) + { + appendType(Packet::PacketType::WEBVTT_SELECTION); + append32(counter); + append32(WEBVTT_SELECTION_PACKET_SIZE); + append32(channelId); + append32(width); + append32(height); + } + +private: + static constexpr std::uint8_t WEBVTT_SELECTION_PACKET_SIZE = 12; +}; + +class WebVttDataPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param channelId + * Packet channelId. + * @param counter + * Packet counter. + * @param timeOffsetMs + * Data offset if needed + * @param dataBuffer + * Packet data. + */ + WebVttDataPacket(std::uint32_t channelId, + std::uint32_t counter, + std::int64_t timeOffsetMs, + std::vector &&dataBuffer) : Packet(counter) + { + auto& buffer = getBuffer(); + uint32_t size = (uint32_t)(8 + 4 + dataBuffer.size()); + + appendType(PacketType::WEBVTT_DATA); + append32(counter); + append32(size); + append32(channelId); + append64(timeOffsetMs); + + for (auto &byte : dataBuffer) + buffer.push_back(byte); + } +}; + + +class WebVttTimestampPacket : public Packet +{ +public: + + /** + * Constructor. + * + * @param counter + * Packet counter. + */ + WebVttTimestampPacket(std::uint32_t channelId, + std::uint32_t counter, + std::uint64_t timestamp) : Packet(counter) + { + appendType(PacketType::WEBVTT_TIMESTAMP); + append32(counter); + append32(WEBVTT_TIMESTAMP_PACKET_SIZE); + append32(channelId); + append64(timestamp); + } + +private: + + static constexpr std::uint8_t WEBVTT_TIMESTAMP_PACKET_SIZE = 12; +}; + + +class WebVttChannel : public SubtecChannel +{ +public: + WebVttChannel() : SubtecChannel() {} + + virtual void SendSelectionPacket(uint32_t width, uint32_t height) override { + sendPacket(width, height); + } + virtual void SendDataPacket(std::vector &&data, std::int64_t time_offset_ms = 0) override { + sendPacket(time_offset_ms, std::move(data)); + } + virtual void SendTimestampPacket(uint64_t timestampMs) override { + sendPacket(timestampMs); + } +}; diff --git a/middleware/subtec/subtecparser/TextStyleAttributes.cpp b/middleware/subtec/subtecparser/TextStyleAttributes.cpp new file mode 100644 index 000000000..dac784e3a --- /dev/null +++ b/middleware/subtec/subtecparser/TextStyleAttributes.cpp @@ -0,0 +1,456 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file TextStyleAttributes.cpp + * + * @brief This file provides implementation of class methods related to subtitle text attributes + * + */ + +#include +#include +#include +#include +#include "TextStyleAttributes.h" +#include "PlayerJsonObject.h" // For JSON parsing + + +TextStyleAttributes::TextStyleAttributes() +{ +} + +/** + * @brief Get font size value from input string + * + * @param[in] input - input font size value + * @param[out] fontSizeOut - font size option for the input value + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getFontSize(std::string input, FontSize *fontSizeOut) +{ + int retVal = 0; + + if (!input.empty() && fontSizeOut) + { + transform(input.begin(), input.end(), input.begin(), ::tolower); /* Makes sure that string is in lower case before comparison */ + + if (input == "small") + { + *fontSizeOut = FONT_SIZE_SMALL; + } + else if ((input == "standard") || (input =="medium")) + { + *fontSizeOut = FONT_SIZE_STANDARD; + } + else if (input == "large") + { + *fontSizeOut = FONT_SIZE_LARGE; + } + else if (input == "extra_large") + { + *fontSizeOut = FONT_SIZE_EXTRALARGE; + } + else if (input == "auto") + { + *fontSizeOut = FONT_SIZE_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported font size type %s", input.c_str()); + retVal = -1; + } + } + else + { + MW_LOG_ERR("Input is NULL"); + retVal = -1; + } + return retVal; +} + +/** + * @brief Get font style value from input string + * + * @param[in] input - input font style value + * @param[out] fontSizeOut - font style option for the input value + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getFontStyle(std::string input, FontStyle *fontStyleOut) +{ + int retVal = 0; + + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && fontStyleOut) + { + transform(input.begin(), input.end(), input.begin(), ::tolower); /* Makes sure that string is in lower case before comparison */ + if (input == "default") + { + *fontStyleOut = FONT_STYLE_DEFAULT; + } + else if ((input == "monospaced_serif") || (input == "monospaced serif")) + { + *fontStyleOut = FONT_STYLE_MONOSPACED_SERIF; + } + else if ((input == "proportional_serif") || (input == "proportional serif")) + { + *fontStyleOut = FONT_STYLE_PROPORTIONAL_SERIF; + } + else if ((input == "monospaced_sanserif") || (input == "monospaced sans serif")) + { + *fontStyleOut = FONT_STYLE_MONOSPACED_SANSSERIF; + } + else if ((input == "proportional_sanserif") || (input == "proportional sans serif")) + { + *fontStyleOut = FONT_STYLE_PROPORTIONAL_SANSSERIF; + } + else if (input == "casual") + { + *fontStyleOut = FONT_STYLE_CASUAL; + } + else if (input == "cursive") + { + *fontStyleOut = FONT_STYLE_CURSIVE; + } + else if ((input == "smallcaps") || (input == "small capital")) + { + *fontStyleOut = FONT_STYLE_SMALL_CAPITALS; + } + else if (input == "auto") + { + *fontStyleOut = FONT_STYLE_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported font style type %s", input.c_str()); + retVal = -1; + } + } + else + { + MW_LOG_ERR("Input is NULL"); + retVal = -1; + } + return retVal; +} + +/** + * @brief Get color value from input string + * + * @param[in] input - input color value + * @param[out] colorOut - color option for the input value + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getColor(std::string input, SupportedColors *colorOut) +{ + int retVal = 0; + + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && colorOut) + { + transform(input.begin(), input.end(), input.begin(), ::tolower); /* Makes sure that string is in lower case before comparison */ + + auto color = ColorMapTable.find(input); + if (color != ColorMapTable.end()) + { + *colorOut = color->second; + } + else + { + MW_LOG_ERR("Unsupported font color %s", input.c_str()); + retVal = -1; + } + } + else + { + MW_LOG_ERR("Input is NULL"); + retVal = -1; + } + return retVal; +} + +/** + * @brief Get edge type value from input string + * + * @param[in] input - input edge type value + * @param[out] edgeTypeOut - edge type option for the input value + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getEdgeType(std::string input, EdgeType *edgeTypeOut) +{ + int retVal = 0; + + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && edgeTypeOut) + { + transform(input.begin(), input.end(), input.begin(), ::tolower); /* Makes sure that string is in lower case before comparison */ + if (input == "none") + { + *edgeTypeOut = EDGE_TYPE_NONE; + } + else if (input == "raised") + { + *edgeTypeOut = EDGE_TYPE_RAISED; + } + else if (input == "depressed") + { + *edgeTypeOut = EDGE_TYPE_DEPRESSED; + } + else if (input == "uniform") + { + *edgeTypeOut = EDGE_TYPE_UNIFORM; + } + else if ((input == "drop_shadow_left") || (input == "left drop shadow")) + { + *edgeTypeOut = EDGE_TYPE_SHADOW_LEFT; + } + else if ((input == "drop_shadow_right") || (input == "right drop shadow")) + { + *edgeTypeOut = EDGE_TYPE_SHADOW_RIGHT; + } + else if (input == "auto") + { + *edgeTypeOut = EDGE_TYPE_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported edge type %s", input.c_str()); + retVal = -1; + } + } + else + { + MW_LOG_ERR("Input is NULL"); + retVal = -1; + } + return retVal; +} + +/** + * @brief Get opacity value from input string + * + * @param[in] input - input opacity value + * @param[out] opacityOut - opacity option for the input value + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getOpacity(std::string input, Opacity *opacityOut) +{ + int retVal = 0; + + MW_LOG_TRACE("input: %s", input.c_str()); + if (!input.empty() && opacityOut) + { + transform(input.begin(), input.end(), input.begin(), ::tolower); /* Makes sure that string is in lower case before comparison */ + if (input == "solid") + { + *opacityOut = OPACITY_SOLID; + } + else if (input == "flash") + { + *opacityOut = OPACITY_FLASHING; + } + else if (input == "translucent") + { + *opacityOut = OPACITY_TRANSLUCENT; + } + else if (input == "transparent") + { + *opacityOut = OPACITY_TRANSPARENT; + } + else if (input == "auto") + { + *opacityOut = OPACITY_EMBEDDED; + } + else + { + MW_LOG_ERR("Unsupported opacity %s", input.c_str()); + retVal = -1; + } + } + else + { + MW_LOG_ERR("Input is NULL"); + retVal = -1; + } + return retVal; +} + +/** + * @brief Gets Attributes of the subtitle + * + * @param[in] options - Json string containing the attributes + * @param[out] attributesValues - Extracted Attribute values (for now they are font size and position) + * @param[out] attributesMask - Mask corresponding to extracted attribute values + * @return int - 0 for success, -1 for failure + */ +int TextStyleAttributes::getAttributes(std::string options, attributesType &attributesValues, uint32_t &attributesMask) +{ + int retVal = 0; + attributesMask = 0; + + MW_LOG_WARN("TextStyleAttributes::getAttributes"); + + if (!options.empty()) + { + std::string optionValue; + Attributes attribute; + try + { + PlayerJsonObject inputOptions(options); + + if (inputOptions.get("penSize", optionValue)) + { + if(!getFontSize(optionValue, &(attribute.fontSize))) + { + attributesMask |= (1 << FONT_SIZE_ARR_POSITION); + attributesValues[FONT_SIZE_ARR_POSITION] = attribute.fontSize; + MW_LOG_INFO("The font size is %d", attributesValues[FONT_SIZE_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse penSize value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("fontStyle", optionValue)) + { + if(!getFontStyle(optionValue, &(attribute.fontStyle))) + { + attributesMask |= (1 << FONT_STYLE_ARR_POSITION); + attributesValues[FONT_STYLE_ARR_POSITION] = attribute.fontStyle; + MW_LOG_INFO("The font style is %d", attributesValues[FONT_STYLE_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse fontStyle value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("textForegroundColor", optionValue)) + { + if(!getColor(optionValue, &(attribute.fontColor))) + { + attributesMask |= (1 << FONT_COLOR_ARR_POSITION); + attributesValues[FONT_COLOR_ARR_POSITION] = attribute.fontColor; + MW_LOG_INFO("The font color is %d", attributesValues[FONT_COLOR_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse font color value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("textBackgroundColor", optionValue)) + { + if(!getColor(optionValue, &(attribute.backgroundColor))) + { + attributesMask |= (1 << BACKGROUND_COLOR_ARR_POSITION); + attributesValues[BACKGROUND_COLOR_ARR_POSITION] = attribute.backgroundColor; + MW_LOG_INFO("The background color is %d", attributesValues[BACKGROUND_COLOR_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse background color value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("textEdgeStyle", optionValue)) + { + if(!getEdgeType(optionValue, &(attribute.edgeType))) + { + attributesMask |= (1 << EDGE_TYPE_ARR_POSITION); + attributesValues[EDGE_TYPE_ARR_POSITION] = attribute.edgeType; + MW_LOG_INFO("The edge type is %d", attributesValues[EDGE_TYPE_ARR_POSITION]); + } + } + if (inputOptions.get("textEdgeColor", optionValue)) + { + if(!getColor(optionValue, &(attribute.edgeColor))) + { + attributesMask |= (1 << EDGE_COLOR_ARR_POSITION); + attributesValues[EDGE_COLOR_ARR_POSITION] = attribute.edgeColor; + MW_LOG_INFO("The edge color is %d", attributesValues[EDGE_COLOR_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse edge color value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("textBackgroundOpacity", optionValue)) + { + if(!getOpacity(optionValue, &(attribute.backgroundOpacity))) + { + attributesMask |= (1 << BACKGROUND_OPACITY_ARR_POSITION); + attributesValues[BACKGROUND_OPACITY_ARR_POSITION] = attribute.backgroundOpacity; + MW_LOG_INFO("The background opacity is %d", attributesValues[BACKGROUND_OPACITY_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse background opacity value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("textForegroundOpacity", optionValue)) + { + if(!getOpacity(optionValue, &(attribute.fontOpacity))) + { + attributesMask |= (1 << FONT_OPACITY_ARR_POSITION); + attributesValues[FONT_OPACITY_ARR_POSITION] = attribute.fontOpacity; + MW_LOG_INFO("The font opacity is %d", attributesValues[FONT_OPACITY_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse font opacity value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("windowFillColor", optionValue)) + { + if(!getColor(optionValue, &(attribute.windowColor))) + { + attributesMask |= (1 << WIN_COLOR_ARR_POSITION); + attributesValues[WIN_COLOR_ARR_POSITION] = attribute.windowColor; + MW_LOG_INFO("The window color is %d", attributesValues[WIN_COLOR_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse window color value of %s", optionValue.c_str()); + } + } + if (inputOptions.get("windowFillOpacity", optionValue)) + { + if(!getOpacity(optionValue, &(attribute.windowOpacity))) + { + attributesMask |= (1 << WIN_OPACITY_ARR_POSITION); + attributesValues[WIN_OPACITY_ARR_POSITION] = attribute.windowOpacity; + MW_LOG_INFO("The window opacity is %d", attributesValues[WIN_OPACITY_ARR_POSITION]); + } + else + { + MW_LOG_WARN("Cannot parse window opacity value of %s", optionValue.c_str()); + } + } + } + catch(const PlayerJsonParseException& e) + { + MW_LOG_ERR("TextStyleAttributes: PlayerJsonParseException - %s", e.what()); + return -1; + } + } + else + { + retVal = -1; + MW_LOG_WARN("Empty input Json string"); + } + return retVal; +} diff --git a/middleware/subtec/subtecparser/TextStyleAttributes.h b/middleware/subtec/subtecparser/TextStyleAttributes.h new file mode 100644 index 000000000..0596f17a9 --- /dev/null +++ b/middleware/subtec/subtecparser/TextStyleAttributes.h @@ -0,0 +1,220 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#ifndef __TEXT_STYLE_ATTRIBUTES_H__ +#define __TEXT_STYLE_ATTRIBUTES_H__ + +/** + * @file TextStyleAttributes.h + * + * @brief This file provides class and other definition related to subtitle text attributes + * + */ + +#include +#include +#include +#include "PlayerLogManager.h" +#include "SubtecAttribute.hpp" + +class TextStyleAttributes +{ + +public: + /** + * @enum FontSize + * @brief Available Fontsize + */ + typedef enum FontSize { + FONT_SIZE_EMBEDDED = -1, /* Corresponds to Font size of auto */ + FONT_SIZE_SMALL, + FONT_SIZE_STANDARD, + FONT_SIZE_LARGE, + FONT_SIZE_EXTRALARGE, + FONT_SIZE_MAX + } FontSize; + + /** + * @enum FontStyle + * @brief Available Fontstyles + */ + typedef enum FontStyle { + FONT_STYLE_EMBEDDED = -1, + FONT_STYLE_DEFAULT, + FONT_STYLE_MONOSPACED_SERIF, + FONT_STYLE_PROPORTIONAL_SERIF, + FONT_STYLE_MONOSPACED_SANSSERIF, + FONT_STYLE_PROPORTIONAL_SANSSERIF, + FONT_STYLE_CASUAL, + FONT_STYLE_CURSIVE, + FONT_STYLE_SMALL_CAPITALS, + FONT_STYLE_MAX + } FontStyle; + + /** + * @enum SupportedColors + * @brief Supported colors + */ + typedef enum SupportedColors { + COLOR_EMBEDDED = 0xFF000000, + COLOR_BLACK = 0x00000000, + COLOR_WHITE = 0x00FFFFFF, + COLOR_RED = 0x00FF0000, + COLOR_GREEN = 0x0000FF00, + COLOR_BLUE = 0x000000FF, + COLOR_YELLOW = 0x00FFFF00, + COLOR_MAGENTA = 0x00FF00FF, + COLOR_CYAN = 0x0000FFFF + } SupportedColors; + + const std::map< std::string, SupportedColors> ColorMapTable { + { "black", COLOR_BLACK}, + { "white", COLOR_WHITE}, + { "red", COLOR_RED}, + { "green", COLOR_GREEN}, + { "blue", COLOR_BLUE}, + { "yellow", COLOR_YELLOW}, + { "magenta", COLOR_MAGENTA}, + { "cyan", COLOR_CYAN}, + { "auto", COLOR_EMBEDDED} + }; + + /** + * @enum EdgeType + * @brief Available Edge Types + */ + typedef enum EdgeType { + EDGE_TYPE_EMBEDDED = -1, + EDGE_TYPE_NONE, + EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED, + EDGE_TYPE_UNIFORM, + EDGE_TYPE_SHADOW_LEFT, + EDGE_TYPE_SHADOW_RIGHT, + EDGE_TYPE_MAX + } EdgeType; + + /** + * @enum Opacity + * @brief Available Opacity options + */ + typedef enum Opacity { + OPACITY_EMBEDDED = -1, + OPACITY_SOLID, + OPACITY_FLASHING, + OPACITY_TRANSLUCENT, + OPACITY_TRANSPARENT, + OPACITY_MAX + } Opacity; + + /** + * @enum AttribPosInArray + * @brief Provides the indexing postion in array for attributes + */ + typedef enum AttribPosInArray { + FONT_COLOR_ARR_POSITION = 0, + BACKGROUND_COLOR_ARR_POSITION, + FONT_OPACITY_ARR_POSITION, + BACKGROUND_OPACITY_ARR_POSITION, + FONT_STYLE_ARR_POSITION, + FONT_SIZE_ARR_POSITION, + FONT_ITALIC_ARR_POSITION, + FONT_UNDERLINE_ARR_POSITION, + BORDER_TYPE_ARR_POSITION, + BORDER_COLOR_ARR_POSITION, + WIN_COLOR_ARR_POSITION, + WIN_OPACITY_ARR_POSITION, + EDGE_TYPE_ARR_POSITION, + EDGE_COLOR_ARR_POSITION + } AttribPosInArray; + + TextStyleAttributes(); + + TextStyleAttributes(const TextStyleAttributes&) = delete; + TextStyleAttributes& operator=(const TextStyleAttributes&); + + /** + * @fn getAttributes + * @param[in] options - Json string containing the attributes + * @param[out] attributesValues - Extracted Attribute values (for now they are font size and position) + * @param[out] attributesMask - Mask corresponding to extracted attribute values + * @return int - 0 for success, -1 for failure + */ + int getAttributes(std::string options, attributesType &attributesValues, uint32_t &attributesMask); + +private: + /** + * @struct Attributes + * @brief Attributes + */ + struct Attributes { + SupportedColors fontColor; + SupportedColors backgroundColor; + Opacity fontOpacity; + Opacity backgroundOpacity; + FontStyle fontStyle; + FontSize fontSize; + Opacity windowOpacity; + SupportedColors windowColor; + EdgeType edgeType; + SupportedColors edgeColor; + }; + + /** + * @fn getFontSize + * @param[in] input - input font size value + * @param[out] fontSizeOut - font size option for the input value + * @return int - 0 for success, -1 for failure + */ + int getFontSize(std::string input, FontSize *fontSizeOut); + + /** + * @fn getFontStyle + * @param[in] input - input font style value + * @param[out] fontStyleOut - font style option for the input value + * @return int - 0 for success, -1 for failure + */ + int getFontStyle(std::string input, FontStyle *fontStyleOut); + + /** + * @fn getColor + * @param[in] input - input color value + * @patam[out] colorOut - color option for the input value + * @return int - 0 for success, -1 for failure + */ + int getColor(std::string input, SupportedColors *colorOut); + + /** + * @fn getEdgeType + * @param[in] input - input edge type value + * @patam[out] edgeTypeOut - edge type option for the input value + * @return int - 0 for success, -1 for failure + */ + int getEdgeType(std::string input, EdgeType *edgeTypeOut); + + /** + * @fn getOpacity + * @param[in] input - input opacity value + * @patam[out] edgeTypeOut - opacity option for the input value + * @return int - 0 for success, -1 for failure + */ + int getOpacity(std::string input, Opacity *opacityOut); +}; + +#endif // __TEXT_STYLE_ATTRIBUTES_H__ diff --git a/middleware/subtec/subtecparser/TtmlSubtecParser.cpp b/middleware/subtec/subtecparser/TtmlSubtecParser.cpp new file mode 100644 index 000000000..10f544e34 --- /dev/null +++ b/middleware/subtec/subtecparser/TtmlSubtecParser.cpp @@ -0,0 +1,224 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include "TtmlSubtecParser.hpp" +#include + +// #define TTML_DEBUG + +TtmlSubtecParser::TtmlSubtecParser(SubtitleMimeType type, int width, int height) : SubtitleParser(type, width, height), m_channel(nullptr) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::TtmlSubtecParser\n" ); +#endif + m_channel = SubtecChannel::SubtecChannelFactory(SubtecChannel::ChannelType::TTML); + if (!m_channel->InitComms()) + { + MW_LOG_INFO("Init failed - subtitle parsing disabled"); + throw std::runtime_error("PacketSender init failed"); + } + m_channel->SendResetAllPacket(); + + int screenWidth = 1920, screenHeight = 1080; + if(width != 0 && height != 0) + { + screenWidth = width; + screenHeight = height; + } + m_channel->SendSelectionPacket(screenWidth, screenHeight); + m_channel->SendMutePacket(); + if(playerResumeTrackDownloads_CB) + { + playerResumeTrackDownloads_CB(); + } +} + +bool TtmlSubtecParser::init(double startPosSeconds, unsigned long long basePTS) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::init(startPosSeconds=%.3fs,basePTS=%llu\n", startPosSeconds, basePTS ); +#endif + MW_LOG_INFO("startPosSeconds %.3fs basePTS=%llu", startPosSeconds, basePTS); + m_channel->SendTimestampPacket(static_cast(startPosSeconds * 1000.0)); + if(playerResumeTrackDownloads_CB) + { + playerResumeTrackDownloads_CB(); + } + + m_parsedFirstPacket = false; + m_sentOffset = false; + m_firstBeginOffset = 0.0; + + return true; +} + +void TtmlSubtecParser::updateTimestamp(unsigned long long positionMs) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::updateTimestamp(positionMs=%llu\n", positionMs ); +#endif + m_channel->SendTimestampPacket(positionMs); +} + +void TtmlSubtecParser::reset() +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::reset\n" ); +#endif + m_channel->SendResetChannelPacket(); +} + +static std::int64_t parseFirstBegin(std::stringstream &ss) +{ + std::int64_t firstBegin = std::numeric_limits::max(); + std::string line; + static const std::regex beginRegex(R"(begin="([0-9]+):([0-9][0-9]?):([0-9][0-9]?)\.?([0-9]+)?")"); + + while(std::getline(ss, line)) + { + try { + bool matched = false; + //Regex still works with no hours and/or no ms. Mins and secs are required + std::smatch match; + matched = std::regex_search(line, match, beginRegex); + + if (matched) { + std::int64_t hours = 0, minutes = 0, seconds = 0, milliseconds = 0; + + if (!match.str(1).empty()) MW_LOG_WARN("h:%s", match.str(1).c_str()); hours = std::stol(match.str(1)); + if (!match.str(2).empty()) MW_LOG_WARN("m:%s", match.str(2).c_str()); minutes = std::stol(match.str(2)); + if (!match.str(3).empty()) MW_LOG_WARN("s:%s", match.str(3).c_str()); seconds = std::stol(match.str(3)); + if (!match.str(4).empty()) MW_LOG_WARN("ms:%s", match.str(4).c_str()); milliseconds = std::stol(match.str(4)); + + firstBegin = milliseconds + (1000 * (seconds + (60 * (minutes + (60 * hours))))); + break; + } + } + catch (const std::regex_error& e) { + MW_LOG_WARN("Regex error %s from line %s", std::to_string(e.code()).c_str(), line.c_str()); + } + } + + return firstBegin; +} + +bool TtmlSubtecParser::processData(const char* buffer, size_t bufferLen, double position, double duration) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::processData(bufferLen=%zu,position=%f,duration=%f)\n", bufferLen, position, duration ); +#endif + + PlayerIsoBmffBuffer isobuf; + + isobuf.setBuffer( (uint8_t *)buffer, bufferLen); + isobuf.parseBuffer(); + + if (!isobuf.isInitSegment()) + { + uint8_t *mdat; + size_t mdatLen; + + //isobuf.printBoxes(); + isobuf.getMdatBoxSize(mdatLen); + + mdat = (uint8_t *)malloc(mdatLen); + isobuf.parseMdatBox(mdat, mdatLen); + + std::vector data(mdatLen); + data.assign(mdat, mdat+mdatLen); + + //necessary because the offset into the TTML + //is not available in the linear manifest + //Take the first instance of the "begin" tag as the time offset for subtec + if (!m_parsedFirstPacket && m_isLinear) + { + m_firstBeginOffset = position; + m_parsedFirstPacket = true; + } + + if (!m_sentOffset && m_parsedFirstPacket && m_isLinear) + { + MW_LOG_TRACE("Linear content - parsing first begin as offset - pos %.3f dur %.3f m_firstBeginOffset %.3f", + position, duration, m_firstBeginOffset); + std::stringstream ss(std::string(data.begin(), data.end())); + std::int64_t offset = parseFirstBegin(ss); + + if (offset != std::numeric_limits::max()) + { + auto positionDeltaSecs = (position - m_firstBeginOffset); + long long getPositionMS = 0; + double seekPositionSeconds = 0.0; + if(playerGetPositions_CB) + { + playerGetPositions_CB(getPositionMS, seekPositionSeconds); + } + + auto timeFromStartMs = getPositionMS - (seekPositionSeconds * 1000.0); + std::int64_t totalOffset = offset - (positionDeltaSecs * 1000.0) + timeFromStartMs; + + std::stringstream output; + output << "setting totalOffset " << totalOffset << " positionDeltaSecs " << positionDeltaSecs << + " timeFromStartMs " << timeFromStartMs; + MW_LOG_TRACE("%s", output.str().c_str()); + m_sentOffset = true; + m_channel->SendTimestampPacket(totalOffset); + } + } + + m_channel->SendDataPacket(std::move(data), 0); + + free(mdat); + MW_LOG_TRACE("Sent buffer with size %zu position %.3f", bufferLen, position); + } + else + { + MW_LOG_INFO("Init Segment"); + } + return true; +} + +void TtmlSubtecParser::mute(bool mute) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::mute(mute=%d)\n", mute ); +#endif + if (mute) + { + m_channel->SendMutePacket(); + } + else + { + m_channel->SendUnmutePacket(); + } +} + +void TtmlSubtecParser::pause(bool pause) +{ +#ifdef TTML_DEBUG + printf( "TtmlSubtecParser::pause(pause=%d)\n", pause ); +#endif + if (pause) + { + m_channel->SendPausePacket(); + } + else + { + m_channel->SendResumePacket(); + } +} diff --git a/middleware/subtec/subtecparser/TtmlSubtecParser.hpp b/middleware/subtec/subtecparser/TtmlSubtecParser.hpp new file mode 100644 index 000000000..a9070c56c --- /dev/null +++ b/middleware/subtec/subtecparser/TtmlSubtecParser.hpp @@ -0,0 +1,52 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include "subtitleParser.h" +#include "playerisobmffbuffer.h" +#include "SubtecChannel.hpp" + +class TtmlSubtecParser : public SubtitleParser +{ +public: + TtmlSubtecParser(SubtitleMimeType type, int width, int height); + + TtmlSubtecParser(const TtmlSubtecParser&) = delete; + TtmlSubtecParser& operator=(const TtmlSubtecParser&) = delete; + + + bool init(double startPosSeconds, unsigned long long basePTS) override; + bool processData(const char* buffer, size_t bufferLen, double position, double duration) override; + bool close() override { return true; } + void reset() override; + void setProgressEventOffset(double offset) override {} + void updateTimestamp(unsigned long long positionMs) override; + void pause(bool pause) override; + void mute(bool mute) override; + + void isLinear(bool isLinear) override { m_isLinear = isLinear; } + +protected: + std::unique_ptr m_channel; + bool m_isLinear = false; + bool m_parsedFirstPacket = false; + bool m_sentOffset = false; + double m_firstBeginOffset = 0.0; +}; diff --git a/middleware/subtec/subtecparser/WebVttSubtecParser.cpp b/middleware/subtec/subtecparser/WebVttSubtecParser.cpp new file mode 100644 index 000000000..b284e4701 --- /dev/null +++ b/middleware/subtec/subtecparser/WebVttSubtecParser.cpp @@ -0,0 +1,110 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include "WebVttSubtecParser.hpp" +#include "TextStyleAttributes.h" + +WebVTTSubtecParser::WebVTTSubtecParser(SubtitleMimeType type, int width, int height) : SubtitleParser(type, width, height), m_channel(nullptr) +{ + m_channel = SubtecChannel::SubtecChannelFactory(SubtecChannel::ChannelType::WEBVTT); + if (!m_channel->InitComms()) + { + MW_LOG_INFO("Init failed - subtitle parsing disabled"); + throw std::runtime_error("PacketSender init failed"); + } + MW_LOG_INFO("Sending RESET ALL"); + m_channel->SendResetAllPacket(); + int screenWidth = 1920, screenHeight = 1080; + if(width != 0 && height != 0) + { + screenWidth = width; + screenHeight = height; + } + m_channel->SendMutePacket(); + m_channel->SendSelectionPacket(screenWidth, screenHeight); +} + +void WebVTTSubtecParser::updateTimestamp(unsigned long long positionMs) +{ + MW_LOG_INFO("positionMs %lld", positionMs); + m_channel->SendTimestampPacket(positionMs); +} + +void WebVTTSubtecParser::reset() +{ + m_channel->SendMutePacket(); + m_channel->SendResetChannelPacket(); +} + +bool WebVTTSubtecParser::init(double startPosSeconds, unsigned long long basePTS) +{ + MW_LOG_INFO("startPos %f basePTS %lld", startPosSeconds, basePTS); + + m_channel->SendTimestampPacket(static_cast(startPosSeconds*1000.0)); + if(playerResumeTrackDownloads_CB) + { + playerResumeTrackDownloads_CB(); + } + + return true; +} + +bool WebVTTSubtecParser::processData(const char* buffer, size_t bufferLen, double position, double duration) +{ + std::string str(const_cast(buffer), bufferLen); + std::vector data(str.begin(), str.end()); + + m_channel->SendDataPacket(std::move(data), 0); + + return true; +} + +void WebVTTSubtecParser::mute(bool mute) +{ + if (mute) + m_channel->SendMutePacket(); + else + m_channel->SendUnmutePacket(); +} + +void WebVTTSubtecParser::pause(bool pause) +{ + if (pause) + m_channel->SendPausePacket(); + else + m_channel->SendResumePacket(); +} + + +void WebVTTSubtecParser::setTextStyle(const std::string &options) +{ + TextStyleAttributes textStyleAttributes; + uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + int ccType = 0; /* Value not used by WebVTT */ + + if (!textStyleAttributes.getAttributes(options, attributesValues, attributesMask)) + { + if (attributesMask) + { + m_channel->SendCCSetAttributePacket(ccType, attributesMask, attributesValues); + } + } +} diff --git a/middleware/subtec/subtecparser/WebVttSubtecParser.hpp b/middleware/subtec/subtecparser/WebVttSubtecParser.hpp new file mode 100644 index 000000000..5e4186dd8 --- /dev/null +++ b/middleware/subtec/subtecparser/WebVttSubtecParser.hpp @@ -0,0 +1,48 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#pragma once + +#include "subtitleParser.h" +#include "SubtecChannel.hpp" + +class WebVTTSubtecParser : public SubtitleParser +{ +public: + WebVTTSubtecParser(SubtitleMimeType type, int width, int height); + + WebVTTSubtecParser(const WebVTTSubtecParser&) = delete; + WebVTTSubtecParser& operator=(const WebVTTSubtecParser&) = delete; + + + bool init(double startPosSeconds, unsigned long long basePTS) override; + bool processData(const char* buffer, size_t bufferLen, double position, double duration) override; + bool close() override { return true; } + void reset() override; + void setProgressEventOffset(double offset) override {} + void updateTimestamp(unsigned long long positionMs) override; + void pause(bool pause) override; + void mute(bool mute) override; + void setTextStyle(const std::string &options) override; +protected: + std::unique_ptr m_channel; +private: + std::uint64_t time_offset_ms_ = 0; + std::uint64_t start_ms_ = 0; +}; diff --git a/middleware/subtec/subtecparser/WebvttSubtecDevInterface.cpp b/middleware/subtec/subtecparser/WebvttSubtecDevInterface.cpp new file mode 100644 index 000000000..77b584a43 --- /dev/null +++ b/middleware/subtec/subtecparser/WebvttSubtecDevInterface.cpp @@ -0,0 +1,78 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include "WebvttSubtecDevInterface.hpp" +#include "PlayerLogManager.h" +#include + +WebvttSubtecDevInterface::WebvttSubtecDevInterface(int width, int height) : m_channel(nullptr) +{ + m_channel = SubtecChannel::SubtecChannelFactory(SubtecChannel::ChannelType::TTML); + if (!m_channel->InitComms()) + { + MW_LOG_INFO("Init failed - subtitle parsing disabled"); + throw std::runtime_error("PacketSender init failed"); + } + m_channel->SendResetAllPacket(); + m_channel->SendSelectionPacket(width, height); + m_channel->SendMutePacket(); +} + +void WebvttSubtecDevInterface::sendCueData(const std::string& ttml) +{ + std::vector data(ttml.begin(), ttml.end()); + + m_channel->SendDataPacket(std::move(data), 0); +} + +void WebvttSubtecDevInterface::reset() +{ + m_channel->SendResetChannelPacket(); +} + +void WebvttSubtecDevInterface::updateTimestamp(unsigned long long positionMs) +{ + MW_LOG_TRACE("timestamp: %lldms", positionMs ); + m_channel->SendTimestampPacket(positionMs); +} + +bool WebvttSubtecDevInterface::init(unsigned long long basePTS) +{ + bool ret = true; + m_channel->SendTimestampPacket(static_cast(basePTS)); + + return ret; +} + +void WebvttSubtecDevInterface::mute(bool mute) +{ + if (mute) + m_channel->SendMutePacket(); + else + m_channel->SendUnmutePacket(); +} + +void WebvttSubtecDevInterface::pause(bool pause) +{ + if (pause) + m_channel->SendPausePacket(); + else + m_channel->SendResumePacket(); +} + diff --git a/middleware/subtec/subtecparser/WebvttSubtecDevInterface.hpp b/middleware/subtec/subtecparser/WebvttSubtecDevInterface.hpp new file mode 100644 index 000000000..7328375c6 --- /dev/null +++ b/middleware/subtec/subtecparser/WebvttSubtecDevInterface.hpp @@ -0,0 +1,49 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file WebvttSubtecDevInterface.hpp + * + * @brief This file provides interfaces for a subtitle parser in Player + * + */ + + +#ifndef __WEBVTT_SUBTEC_DEV_INTERFACE_HPP__ +#define __WEBVTT_SUBTEC_DEV_INTERFACE_HPP__ + +#include "SubtecChannel.hpp" + +class WebvttSubtecDevInterface +{ +public: + WebvttSubtecDevInterface(int width, int height); + ~WebvttSubtecDevInterface(){} + + bool init(unsigned long long basePTS); + void reset() ; + void sendCueData(const std::string& ttml); + void updateTimestamp(unsigned long long positionMs); + void pause(bool pause); + void mute(bool mute); +protected: + std::unique_ptr m_channel; +}; + +#endif /* __WEBVTT_SUBTEC_DEV_INTERFACE_HPP__ */ diff --git a/middleware/subtec/test/subtec_test.cpp b/middleware/subtec/test/subtec_test.cpp new file mode 100644 index 000000000..6db6abc0c --- /dev/null +++ b/middleware/subtec/test/subtec_test.cpp @@ -0,0 +1,149 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include + +using DataBuffer = std::vector; + +void serverThread(const char *socket_path) +{ + int epoll = ::epoll_create(10); + int serversock = ::socket(AF_UNIX, SOCK_DGRAM, 0); + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + if (NULL != socket_path) + (void) std::strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)); + else + (void) std::strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + if (-1 == ::bind(serversock, (struct sockaddr*)&addr, sizeof(addr))) + { + printf("Couldn't bind server at %s\n", addr.sun_path); + return; + } + listen(serversock, 1); + struct epoll_event event; + event.events = EPOLLIN; + event.data.fd = serversock; + epoll_ctl(epoll, EPOLL_CTL_ADD, serversock, &event); + + while(1) + { + struct epoll_event ev; + printf("Waiting for client message. Epoll %d serversock %d\n", epoll, serversock); + int nfd = epoll_wait(epoll, &ev, 1, 10000); + if (nfd > 0 && event.data.fd == serversock) + { + printf("Message received nfd %d fd %d serversock %d\n", nfd, ev.data.fd, serversock); + std::uint32_t type, counter, size; + std::uint32_t data[1024]; + int rd; + do { + rd = recv(ev.data.fd, data, sizeof(data), 0); + printf("Read data %d\n", rd); + } while (false); + type = data[0]; + counter = data[1]; + size = data[2]; + printf("Packet received: type %d counter %d size %d\n", type, counter, size); + } + } +} + +int main(int argc, char *argv[]) +{ + int opt; + bool test_thread = false, ret, send_reset = false; + std::thread th; + const char *path = NULL; + const char *webvtt_file_path = NULL; + + while ((opt = getopt(argc, argv, "sf:tr")) != -1) + { + switch(opt) { + case 't': + test_thread = true; + break; + case 'f': + webvtt_file_path = optarg; + break; + case 's': + path = optarg; + printf("path is %s\n", path); + break; + case 'r': + send_reset = true; + break; + default: + break; + } + } + + + if (test_thread) + { + if (path) + remove(path); + else + remove(SOCKET_PATH); + + printf("Starting thread\n"); + th = std::thread(serverThread, path); + sleep(2); + } + + WebVttChannel *channel = new WebVttChannel(); + std::vector data; + + ret = channel->InitComms(); + if (ret) + { + channel->SendResetAllPacket(); + if (!send_reset) + { + channel->SendResetChannelPacket(); + channel->SendSelectionPacket(1920, 1080); + channel->SendUnmutePacket(); + channel->SendTimestampPacket(0); + if (webvtt_file_path) { + auto ifile = std::ifstream(webvtt_file_path, std::ios::in | std::ios::binary); + + std::for_each(std::istreambuf_iterator(ifile), + std::istreambuf_iterator(), + [&data](const char c) { + data.push_back(c); + }); + } else { + data = {'a', 'b', 'c'}; + } + channel->SendDataPacket(std::move(data)); + sleep(1); + } + } + + if (test_thread) th.join(); + + return 0; +} \ No newline at end of file diff --git a/middleware/subtitle/subtitleParser.h b/middleware/subtitle/subtitleParser.h new file mode 100644 index 000000000..fb1d88af6 --- /dev/null +++ b/middleware/subtitle/subtitleParser.h @@ -0,0 +1,121 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2018 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file subtitleParser.h + * + * @brief This file provides interfaces for a subtitle parser in Player + * + */ + +#ifndef __SUBTITLE_PARSER_H__ +#define __SUBTITLE_PARSER_H__ + +#include +#include +#include "vttCue.h" +#include "PlayerLogManager.h" +#include +#include +#include + +/** +* \enum SubtitleMimeType +* \brief Subtitle data types +*/ +typedef enum +{ + eSUB_TYPE_WEBVTT, + eSUB_TYPE_MP4, + eSUB_TYPE_TTML, + eSUB_TYPE_UNKNOWN +} SubtitleMimeType; + +/** + * @brief Structure holding the player callback. + */ +struct PlayerCallbacks { + std::function resumeTrackDownloads_CB; /**< Callback to resume track downloads */ + std::function stopTrackDownloads_CB; /**< Callback to stop track downloads */ + std::function sendVTTCueData_CB; /**< Callback to send VTT cue data */ + std::function getPlayerPositions_CB; /**< Callback to get player positions */ +}; + +/** +* \class SubtitleParser +* \brief Subtitle parser class +* +* This is the base class for a subtitle parser impl in Player +*/ +class SubtitleParser +{ +public: + + SubtitleParser(SubtitleMimeType type, int width, int height) : mType(type), + mHeight(height), mWidth(width) + { + } + + /// Copy Constructor + SubtitleParser(const SubtitleParser&) = delete; + + virtual ~SubtitleParser() + { + UnRegisterCallback(); + } + + /// Assignment operator Overloading + SubtitleParser& operator=(const SubtitleParser&) = delete; + + virtual bool init(double startPosSeconds, unsigned long long basePTS) { return false; } + virtual bool processData(const char* buffer, size_t bufferLen, double position, double duration) = 0; + virtual bool close() = 0; + virtual void reset() = 0; + virtual void setProgressEventOffset(double offset) = 0; + virtual void updateTimestamp(unsigned long long positionMs) = 0; + virtual void pause(bool pause) {} + virtual void mute(bool mute) {} + virtual void isLinear(bool isLinear) {} + virtual void setTextStyle(const std::string &options){} + void RegisterCallback(const PlayerCallbacks& playerCallBack) + { + playerResumeTrackDownloads_CB = playerCallBack.resumeTrackDownloads_CB; + playerGetPositions_CB = playerCallBack.getPlayerPositions_CB; + playerStopTrackDownloads_CB = playerCallBack.stopTrackDownloads_CB; + playerSendVTTCueData_CB = playerCallBack.sendVTTCueData_CB; + } + void UnRegisterCallback( ) + { + playerResumeTrackDownloads_CB = NULL; + playerGetPositions_CB = NULL; + playerStopTrackDownloads_CB = NULL; + playerSendVTTCueData_CB = NULL; + } +protected: + + SubtitleMimeType mType; + int mHeight; + int mWidth; + std::function playerResumeTrackDownloads_CB; + std::function playerStopTrackDownloads_CB; + std::function playerGetPositions_CB; + std::function playerSendVTTCueData_CB; +}; + +#endif /* __SUBTITLE_PARSER_H__ */ diff --git a/middleware/subtitle/vttCue.h b/middleware/subtitle/vttCue.h new file mode 100644 index 000000000..cae715196 --- /dev/null +++ b/middleware/subtitle/vttCue.h @@ -0,0 +1,54 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file vttCue.h + * + * @brief Provides data structure to hold a WebVTT cue data + * + */ + +#ifndef __VTT_CUE_H__ +#define __VTT_CUE_H__ + +#include + + +/** +* \struct VTTCue +* \brief Data structure to hold a VTT cue +* +* This is the data structure to store parsed WebVTT cues in AAMP +*/ +struct VTTCue +{ + VTTCue(double startTime, double duration, std::string text, std::string settings): + mStart(startTime), mDuration(duration), + mText(text), mSettings(settings) + { + + } + + double mStart; + double mDuration; + std::string mText; + std::string mSettings; +}; + +#endif /* __VTT_CUE_H__ */ diff --git a/middleware/test/common/CodeCoverage.cmake b/middleware/test/common/CodeCoverage.cmake new file mode 100644 index 000000000..d4a039fd0 --- /dev/null +++ b/middleware/test/common/CodeCoverage.cmake @@ -0,0 +1,742 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) + if(HAVE_fprofile_abs_path) + set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/middleware/test/utests/CMakeLists.txt b/middleware/test/utests/CMakeLists.txt new file mode 100644 index 000000000..d88c8bcee --- /dev/null +++ b/middleware/test/utests/CMakeLists.txt @@ -0,0 +1,92 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2022 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.10.3) + +project(UnitTests) + +# Don't include CTestDashboardTargets in GUI projects +set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) +include(CTest) + +set(PLAYER_ROOT "../../") + +find_package(PkgConfig REQUIRED) + +pkg_check_modules(OPENSSL REQUIRED openssl) +include_directories(${OPENSSL_INCLUDE_DIRS}) + +pkg_check_modules(GTEST REQUIRED gtest) +pkg_check_modules(GMOCK REQUIRED gmock) +pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) +pkg_check_modules(GSTREAMERBASE REQUIRED gstreamer-app-1.0) +pkg_check_modules(GLIB REQUIRED glib-2.0) +pkg_check_modules(LibXml2 REQUIRED libxml-2.0) +pkg_check_modules(LIBCJSON REQUIRED libcjson) + +if (NOT CMAKE_SYSTEM_NAME STREQUAL Darwin) + pkg_search_module(JSCORE REQUIRED javascriptcoregtk-4.1 javascriptcoregtk-4.0) + find_path (JSC_INCDIR JavaScriptCore/JavaScript.h HINTS ${JSCORE_INCLUDE_DIRS} REQUIRED) +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_GST1 -ggdb") +if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() +STRING(REPLACE " -Werror=effc++" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) #disabling effc++ compiler warnings for utest directory +#if (CMAKE_PLATFORM_UBUNTU ) +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") +#endif() + +add_definitions(-DENABLE_LOGGING) + +add_subdirectory(fakes) +if (CMAKE_XCODE_BUILD_SYSTEM) + # ARM64 will fail to build utest schemes in XCode unless this is set + set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST) +endif() + +enable_testing() + +# Add the player/test/common directory to the module include path for access to +# common CMake test functionality, like the CodeCoverage.cmake module. +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/../common") + +if (CMAKE_PLATFORM_UBUNTU) + link_directories(${GTEST_LIBRARY_DIRS} ${GMOCK_LIBRARY_DIRS}) +endif() + +function(player_utest_run_add test_suite) + if (CMAKE_RDKE_TEST_RUN) + # Add the entire googletest test suite to ctest. This means that ctest runs each test + # suite executable only once, so the test_details.json googletest JSON output file + # required by RDK-E does not get overwritten. + add_test(NAME ${test_suite} COMMAND ${test_suite}) + else() + # Discover tests by running the googletest test suite executable with "--gtest_list_tests", + # and add each test individually to ctest. This results in ctest running each test suite + # executable separately for each test, giving greater opportunity for parallelism and + # stdout per-test rather than per-suite, amongst other advantages. There is no issue with + # overwriting of googletest JSON output files, because ctest itself handles the output to + # file via its "--output-junit" argument, which creates a JUnit XML output file instead. + gtest_discover_tests(${test_suite} DISCOVERY_TIMEOUT 10 TEST_PREFIX ${test_suite}:) + endif() +endfunction() +add_subdirectory(tests) diff --git a/middleware/test/utests/ReadMe.md b/middleware/test/utests/ReadMe.md new file mode 100644 index 000000000..19e07fee3 --- /dev/null +++ b/middleware/test/utests/ReadMe.md @@ -0,0 +1,143 @@ +# AAMP Microtests + +A test infrastructure using GoogleTest C++ testing and mocking framework, to verify the behavior of individual AAMP objects. + +CTest is a testing tool that is part of CMake, and is used to automatically execute all the tests, and provides a report of the tests run, whether they passed/failed and time taken. It can be configured to run tests in parallel, output logging on failure, run specific tests etc. + +These tests should be extended on addition of any new functionality. + +These tests should be run on any change to: + - Test any new functionality + - Check for any regression caused by the change + - Check the build has not been broken due to an API change + +NOTE: Writing microtests is a really useful tool in improving code quality but if they are implemented incorrectly they can have a detrimental impact on build times and fail to find the errors they are meant to highlight. If you are new to microtests we strongly recommend you read the following to understand how to write them well: + + - **See [GoogleTest User's Guide](https://google.github.io/googletest/)** + - **See [Testing With CMake and CTest](https://cmake.org/cmake/help/book/mastering-cmake/chapter/Testing%20With%20CMake%20and%20CTest.html)** + +## Pre-requisites to building: + +AAMP installed using install-middleware.sh (-c if code coverage is neeeded) script which: + - installs headers from dependent libraries + - installs GoogleTest and GoogleMock + - installs jq + +## Build and run microtests using script: + +From the *utests* folder run: + +``` +./run.sh +``` +## To run tests and generate combined report in json format + +./run.sh -e + +Report can be found in utests/TestReport.json + +## Check line coverage in microtests: + +For code coverage install-middleware.sh -c +must have been run first to generate a baseline set of middleware files. + +From the *utests* directory, run: + +``` +./run.sh -c +``` + +The aggregated results can be found in the following html file: +*utests/CombinedCoverage/index.html* + +**Note:** + +This takes considerably longer than running the script with no options + +## To build and run the microtests manually: + +From the *utests* folder run: + +``` +mkdir build +cd build +cmake ../ +make +ctest +``` + +## Some examples of additional parameters that can be used with ctest + +To output logging run ctest with verbose option: + +``` +ctest --verbose +``` + +To output logging when a test fails : + +``` +ctest --output-on-failure +``` + +Tests can be run in parallel using -j option, for example: + +``` +ctest -j 4 +``` + +Specific tests can be run using ctest's regex selectors, see ctest --help. For example: + +``` +ctest -R PrivateInstance.*PositionAlready +``` + +## Directory Structure + +### fakes + +A CMake library containing fake/stub implementations of class methods, to allow compiling of class under test in isolation; these fakes are common to all tests. + +Implementation can be extended to call a mock instance, to allow testing of expectations. For example, see FakePrivateInstanceAAMP.cpp where some methods being used by existing tests have been extended to call a mock of PrivateInstanceAAMP if the mock has been constructed. + +The files in here will likely need to be updated for any API changes/additions made to AAMP modules, otherwise unresolved symbol errors are likely to be seen. + +### mocks + +A directory containing Google mocks; these mocks are common to all tests. + +See [gMock for Dummies](https://google.github.io/googletest/gmock_for_dummies.html) + +### tests + +A directory containing the tests for each of AAMP's modules contained within their own sub-directory. +The CMakeLists.txt file adds all the modules' subdirectories. + +See [CMake Modules - GoogleTest](https://cmake.org/cmake/help/latest/module/GoogleTest.html) + +### *Module* test folder + +One or more Googletest executables generated from CMake. + +The CMakeLists.txt contains the instructions for creating the module's target. e.g. +- EXEC_NAME to be same as directory name //Required to generate json report +- Necessary include paths for that module +- The module source file +- The google test file(s) +- The directive, gtest_discover_tests, to discover google tests from the test executable + +General guidance: + - A test class for each area of functionality of the module under test. + - Use of mocks, and testing of expectations, for external calls made by the module + - Use of microtests should not significantly impact build times, and are intended to be run on every change, as such they should be implemented to run as quickly as possible (all tests to run in seconds rather than minutes); avoiding sleeps and other timing delays. + +It may be desired to use the real implementation of an external class, rather than the mock; which can be done in CMakeLists.txt. Also if some tests are best implemented using a mock, and others using the real implementation then it should be possible to create multiple executables configured as such. + +For guidance on creating GoogleTest please see [GoogleTest User's Guide](https://google.github.io/googletest/). + +### Running debugger + +gdb can be run on an individual test via: +cd build/tests/SomeTest +gdb .. +. diff --git a/middleware/test/utests/cmake_exclude_file.list b/middleware/test/utests/cmake_exclude_file.list new file mode 100644 index 000000000..fbfbd7e9d --- /dev/null +++ b/middleware/test/utests/cmake_exclude_file.list @@ -0,0 +1,16 @@ +# A list of files to exclude from the coverage report. +# Set NO_EXCLUDE_DIR in the CMakeLists.txt of any new test so it can be removed from the exclude list. + + get_filename_component(PARENT_DIR ${PROJECT_SOURCE_DIR} DIRECTORY) + get_filename_component(BASE_DIR ${PARENT_DIR} DIRECTORY) + set(COVERAGE_EXCLUDES + "/usr/*" + "${BASE_DIR}/.libs/lib/include/*" + "${BASE_DIR}/subtec/subtecparser/*" + "${BASE_DIR}/test/*" + "/Applications/*" + "./v1/*") + + if (NO_EXCLUDE_DIR) + list(REMOVE_ITEM COVERAGE_EXCLUDES ${NO_EXCLUDE_DIR}) + endif() diff --git a/middleware/test/utests/drm/mocks/FakeID3Metadata.cpp b/middleware/test/utests/drm/mocks/FakeID3Metadata.cpp new file mode 100644 index 000000000..663458911 --- /dev/null +++ b/middleware/test/utests/drm/mocks/FakeID3Metadata.cpp @@ -0,0 +1,81 @@ + +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ID3Metadata.hpp" + +#include +#include +#include + +namespace aamp +{ +namespace id3_metadata +{ +namespace helpers +{ +//constexpr size_t min_id3_header_length = 3u; +//constexpr size_t id3v2_header_size = 10u; + +bool IsValidMediaType(AampMediaType) +{ + return true; +} + +bool IsValidHeader(const uint8_t*, size_t) +{ + return true; +} + +size_t DataSize(const uint8_t*) +{ + return 0; +} + +std::string ToString(const uint8_t*, size_t) +{ + std::string out{}; + return out; +} + +} // namespace helpers + +MetadataCache::MetadataCache() : mCache{} +{ +} + +MetadataCache::~MetadataCache() +{ +} + +void MetadataCache::Reset() +{ +} + +bool MetadataCache::CheckNewMetadata(AampMediaType mediaType, const std::vector& data) const +{ + return false; +} + +void MetadataCache::UpdateMetadataCache(AampMediaType mediaType, std::vector data) +{ +} + +} // namespace id3_metadata +} // namespace aamp diff --git a/middleware/test/utests/drm/mocks/Fakeopencdm.cpp b/middleware/test/utests/drm/mocks/Fakeopencdm.cpp new file mode 100644 index 000000000..7ef1edb67 --- /dev/null +++ b/middleware/test/utests/drm/mocks/Fakeopencdm.cpp @@ -0,0 +1,163 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "open_cdm.h" +#include "open_cdm_adapter.h" +#include + +#include "Fakeopencdm.h" +#include "MockOpenCdm.h" +#include "PlayerLogManager.h" + +MockOpenCdm* g_mockopencdm = nullptr; + +typedef struct _MockOpenCdmInstanceData +{ + MockOpenCdmSessionInfo sessionInfo; + MockOpenCdmCallbacks callbacks; + void* mockUserData; // User data from the client of the mock +} MockOpenCdmInstanceData; + +static MockOpenCdmInstanceData f_mockInstance; + +/* BEGIN - methods to access mock functionality */ +MockOpenCdmSessionInfo* MockOpenCdmGetSessionInfo() +{ + return &f_mockInstance.sessionInfo; +} + +void MockOpenCdmSetCallbacks(MockOpenCdmCallbacks callbacks, void* mockUserData) +{ + f_mockInstance.callbacks = callbacks; + f_mockInstance.mockUserData = mockUserData; +} + +void MockOpenCdmReset(void) +{ + memset(&f_mockInstance, 0, sizeof(f_mockInstance)); +} + +/* END - methods to access mock functionality */ + +struct OpenCDMSystem* opencdm_create_system(const char keySystem[]) +{ + if (g_mockopencdm != nullptr) + { + return g_mockopencdm->opencdm_create_system(keySystem); + } + return nullptr; +} + +OpenCDMError opencdm_construct_session(struct OpenCDMSystem* system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], + const uint16_t initDataLength, const uint8_t CDMData[], + const uint16_t CDMDataLength, + OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session) +{ + OpenCDMError retValue = ERROR_NONE; + *session = nullptr; + MW_LOG_ERR("TRACE Enter opencdm_constructSession"); + if (g_mockopencdm != nullptr) + { + retValue = g_mockopencdm->opencdm_construct_session( + system, licenseType, initDataType, initData, initDataLength, CDMData, CDMDataLength, + callbacks, userData, session); + } +#if 0 + MW_LOG_ERR("TRACE2"); + f_mockInstance.sessionInfo.session = *session; + f_mockInstance.sessionInfo.callbacks = *callbacks; + f_mockInstance.sessionInfo.userData = userData; + MW_LOG_ERR("TRACE3"); + if (f_mockInstance.callbacks.constructSessionCallback) + { + f_mockInstance.callbacks.constructSessionCallback(&f_mockInstance.sessionInfo, + f_mockInstance.mockUserData); + } + MW_LOG_ERR("TRACE4"); +#endif + return retValue; +} + +OpenCDMError opencdm_destruct_system(struct OpenCDMSystem* system) +{ + return ERROR_NONE; +} + +KeyStatus opencdm_session_status(const struct OpenCDMSession* session, const uint8_t keyId[], + const uint8_t length) +{ + return Usable; +} + +OpenCDMError opencdm_session_update(struct OpenCDMSession* session, const uint8_t keyMessage[], + const uint16_t keyLength) +{ + OpenCDMError retValue = ERROR_NONE; + + if (g_mockopencdm != nullptr) + { + retValue = g_mockopencdm->opencdm_session_update(session, keyMessage, keyLength); + } + + if (f_mockInstance.callbacks.sessionUpdateCallback) + { + f_mockInstance.callbacks.sessionUpdateCallback(&f_mockInstance.sessionInfo, keyMessage, + keyLength); + } + + return retValue; +} + +OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, GstBuffer* buffer, + GstBuffer* subSample, const uint32_t subSampleCount, + GstBuffer* IV, GstBuffer* keyID, + uint32_t initWithLast15) +{ + return ERROR_NONE; +} + +OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, const uint8_t* IV, + uint16_t IVLength, const uint8_t* keyId, + const uint16_t keyIdLength, uint32_t initWithLast15) +{ + if (g_mockopencdm != nullptr) + { + return g_mockopencdm->opencdm_session_decrypt(session, encrypted, encryptedLength, + encScheme, pattern, IV, IVLength, keyId, + keyIdLength, initWithLast15); + } + return ERROR_NONE; +} + +OpenCDMError opencdm_session_close(struct OpenCDMSession* session) +{ + return ERROR_NONE; +} + +OpenCDMError opencdm_destruct_session(struct OpenCDMSession* session) +{ + return ERROR_NONE; +} diff --git a/middleware/test/utests/drm/mocks/Fakeopencdm.h b/middleware/test/utests/drm/mocks/Fakeopencdm.h new file mode 100644 index 000000000..2b7bb157e --- /dev/null +++ b/middleware/test/utests/drm/mocks/Fakeopencdm.h @@ -0,0 +1,57 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FAKE_OPEN_CDM_H +#define FAKE_OPEN_CDM_H + +#include "open_cdm.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + typedef struct _MockSessionInfo + { + void *session; + void *userData; // User data from the client of OCDM + OpenCDMSessionCallbacks callbacks; + } MockOpenCdmSessionInfo; + + typedef void (*MockConstructSessionCallback)(const MockOpenCdmSessionInfo *session, + void *mockUserData); + typedef void (*MockSessionUpdateCallback)(const MockOpenCdmSessionInfo *session, + const uint8_t keyMessage[], const uint16_t keyLength); + + typedef struct _MockOpenCdmCallbacks + { + MockConstructSessionCallback constructSessionCallback; + MockSessionUpdateCallback sessionUpdateCallback; + } MockOpenCdmCallbacks; + + MockOpenCdmSessionInfo *MockOpenCdmGetSessionInfo(); + + void MockOpenCdmSetCallbacks(MockOpenCdmCallbacks callbacks, void *mockUserData); + + void MockOpenCdmReset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* FAKE_OPEN_CDM_H */ diff --git a/middleware/test/utests/drm/mocks/MockOpenCdm.h b/middleware/test/utests/drm/mocks/MockOpenCdm.h new file mode 100644 index 000000000..8ebf078b5 --- /dev/null +++ b/middleware/test/utests/drm/mocks/MockOpenCdm.h @@ -0,0 +1,54 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLAYER_MOCK_OPEN_CDM_H +#define PLAYER_MOCK_OPEN_CDM_H + +#include "open_cdm.h" +#include + +class MockOpenCdm +{ +public: + MOCK_METHOD(OpenCDMError, opencdm_session_decrypt, + (struct OpenCDMSession * session, uint8_t encrypted[], + const uint32_t encryptedLength, const EncryptionScheme encScheme, + const EncryptionPattern pattern, const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, uint32_t initWithLast15)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_system, (struct OpenCDMSystem* system)); + MOCK_METHOD(OpenCDMSystem*, opencdm_create_system, (const char keySystem[])); + MOCK_METHOD(OpenCDMError, opencdm_construct_session, + (struct OpenCDMSystem * system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], const uint16_t initDataLength, + const uint8_t CDMData[], const uint16_t CDMDataLength, + OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session)); + MOCK_METHOD(OpenCDMError, opencdm_session_update, + (struct OpenCDMSession * session, const uint8_t keyMessage[], + const uint16_t keyLength)); + MOCK_METHOD(OpenCDMError, opencdm_session_close, (struct OpenCDMSession* session)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_session, (struct OpenCDMSession* session)); + MOCK_METHOD(KeyStatus, opencdm_session_status, (const struct OpenCDMSession* session, + const uint8_t keyId[], + const uint8_t length)); +}; + +extern MockOpenCdm* g_mockopencdm; + +#endif /* PLAYER_MOCK_OPEN_CDM_H */ diff --git a/middleware/test/utests/drm/mocks/gstMocks.c b/middleware/test/utests/drm/mocks/gstMocks.c new file mode 100644 index 000000000..3e58725f2 --- /dev/null +++ b/middleware/test/utests/drm/mocks/gstMocks.c @@ -0,0 +1,100 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +GType _gst_caps_type = 0; +GType _gst_buffer_type = 0; + + +GstVideoTimeCodeMeta * gst_buffer_add_video_time_code_meta_full( + GstBuffer * buffer, + guint fps_n, + guint fps_d, + GDateTime * latest_daily_jam, + GstVideoTimeCodeFlags flags, + guint hours, + guint minutes, + guint seconds, + guint frames, + guint field_count) +{ + return NULL; +} + +gboolean gst_buffer_map(GstBuffer *buffer, GstMapInfo *info, GstMapFlags flags) +{ + return TRUE; +} + +void gst_buffer_unmap(GstBuffer *buffer, GstMapInfo *info) +{ +} + +GstByteReader *gst_byte_reader_new(const guint8 *data, guint size) +{ + static GstByteReader byte_reader; + return &byte_reader; +} + +gboolean gst_byte_reader_set_pos(GstByteReader *reader, guint pos) +{ + return TRUE; +} + +void gst_byte_reader_free(GstByteReader *reader) +{ +} + +gboolean gst_caps_is_empty (const GstCaps * caps) +{ + return FALSE; +} + +GstMeta * gst_buffer_get_meta (GstBuffer * buffer, GType api) +{ + return NULL; +} + +GstStructure * gst_structure_new (const gchar * name, const gchar * firstfield, ...) +{ + return NULL; +} + +void gst_structure_set (GstStructure * structure, const gchar * field, ...) +{ +} + +GstProtectionMeta * gst_buffer_add_protection_meta (GstBuffer * buffer, GstStructure * info) +{ + return NULL; +} + +GType gst_protection_meta_api_get_type (void) +{ + return 0; +} + +void g_object_get(gpointer object, const gchar *first_property_name, ...) +{ +} diff --git a/middleware/test/utests/drm/ocdm/open_cdm.h b/middleware/test/utests/drm/ocdm/open_cdm.h new file mode 100644 index 000000000..0d87f42d4 --- /dev/null +++ b/middleware/test/utests/drm/ocdm/open_cdm.h @@ -0,0 +1,639 @@ +/* + * Copyright 2016-2017 TATA ELXSI + * Copyright 2016-2017 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OPEN_CDM_H +#define OPEN_CDM_H + +// WPEWebkit implementation is using the following header file to integrate +// their solution with +// DRM systems (PlayReady/WideVine/ClearKey). +// The implementation behind this class/interface exists in two flavors. +// 1) Fraunhofers adapted reference implementation, based on SUNRPC +// 2) Metrologicals framework, based on their proprietary RPC mechanism. +// +// The second option exists because during testing the reference/adapted +// implementation of Frauenhofer +// it was observed: +// - Older implementations of ucLibc had different dynamic characteristics that +// caused deadlocks +// - Message exchange over the SUNRPC mechanism is using continous heap memory +// allocations/deallocactions, +// leading to a higher risk of memory fragmentation. +// - SUNRPC only works on UDP/TCP, given the nature and size of the messages, +// UDP was not an option so TCP +// is used. There is no domain socket implementation for the SUNRPC mechanism. +// Domain sockets transfer +// data, as an average, twice as fast as TCP sockets. +// - SUNRPC requires an external process (bind) to do program number lookup to +// TCP port connecting. Currently +// the Frauenhofer OpenCDMi reference implementation is the only +// implementation requiring this service on +// most deplyments with the WPEWebkit. +// - Common Vulnerabilities and Exposures (CVE's) have been reported with the +// SUNRPC that have not been resolved +// on most platforms where the solution is deployed. +// - The Metrological RPC mechanism allows for a configurable in or out of +// process deplyment of the OpenCDMi +// deployment without rebuilding. +// +// So due to performance and security exploits it was decided to offer a second +// implementation of the OpenCDMi +// specification that did notrequire to change the WPEWebKit + +#include +#include + +#include +#include + +#ifndef EXTERNAL +#ifdef _MSVC_LANG +#ifdef OCDM_EXPORTS +#define EXTERNAL __declspec(dllexport) +#else +#define EXTERNAL __declspec(dllimport) +#endif +#else +#define EXTERNAL __attribute__ ((visibility ("default"))) +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WINDOWS) + #if !defined(OCDM_EXPORTS) + #pragma comment(lib, "ocdm.lib") + #endif + + /** + * Sometimes the compiler would like to be smart, if we do not reference + * anything here + * and you enable the rightflags, the linker drops the dependency. Than + * Proxy/Stubs do + * not get loaded, so lets make the instantiation of the ProxyStubs explicit !!! + */ + EXTERNAL void ForceLinkingOfOpenCDM(); +#endif + +#define SESSION_ID_LEN 16 +#define MAX_NUM_SECURE_STOPS 8 + +/** + * Represents an OCDM system + */ +struct OpenCDMSystem; + +/** + * Represents a OpenCDM session, use this one to decrypt. + */ +struct OpenCDMSession; + +typedef enum { + Temporary = 0, + PersistentUsageRecord, + PersistentLicense +} LicenseType; + +// ISO/IEC 23001-7 defines two Common Encryption Schemes with Full Sample and Subsample modes +typedef enum { + Clear = 0, + AesCtr_Cenc, // AES-CTR mode and Sub-Sample encryption + AesCbc_Cbc1, // AES-CBC mode and Sub-Sample encryption + AesCtr_Cens, // AES-CTR mode and Sub-Sample + patterned encryption + AesCbc_Cbcs // AES-CBC mode and Sub-Sample + patterned encryption + Constant IV +} EncryptionScheme; + +typedef enum +{ + MediaType_Unknown = 0, + MediaType_Video, + MediaType_Audio, + MediaType_Data +} OcdmMediaType; // Renamed from "MediaType" to avoid compile error, as AAMP also has a MediaType enum + +//CENC3.0 pattern is a number of encrypted blocks followed a number of clear blocks after which the pattern repeats. +typedef struct { + uint32_t encrypted_blocks; + uint32_t clear_blocks; +} EncryptionPattern; + +typedef struct { + uint16_t clear_bytes; + uint32_t encrypted_bytes; +} SubSampleInfo; + +typedef struct { + EncryptionScheme scheme; // Encryption scheme used in this sample + EncryptionPattern pattern; // Encryption Pattern used in this sample + uint8_t* iv; // Initialization vector(IV) to decrypt this sample. Can be NULL, in that case and IV of all zeros is assumed. + uint8_t ivLength; // Length of IV + uint8_t* keyId; // ID of Key required to decrypt this sample + uint8_t keyIdLength; // Length of KeyId + uint8_t subSampleCount; // Number or Sub-Samples in this sample + SubSampleInfo* subSample; // SubSample mapping - Repeating pair of Clear bytes and Encrypted Bytes representing each subsample. +} SampleInfo; + +// Provides information about the current stream +typedef struct { + uint16_t height; + uint16_t width; + OcdmMediaType media_type; +} MediaProperties; + + +/** + * Key status. + */ +typedef enum { + Usable = 0, + Expired, + Released, + OutputRestricted, + OutputRestrictedHDCP22, + OutputDownscaled, + StatusPending, + InternalError, + HWError +} KeyStatus; + +/** + * OpenCDM error code. Zero always means success. + */ +typedef enum { + ERROR_NONE = 0, + ERROR_UNKNOWN = 1, + ERROR_MORE_DATA_AVAILABLE = 2, + ERROR_INTERFACE_NOT_IMPLEMENTED = 3, + ERROR_BUFFER_TOO_SMALL = 4, + ERROR_INVALID_ACCESSOR = 0x80000001, + ERROR_KEYSYSTEM_NOT_SUPPORTED = 0x80000002, + ERROR_INVALID_SESSION = 0x80000003, + ERROR_INVALID_DECRYPT_BUFFER = 0x80000004, + ERROR_OUT_OF_MEMORY = 0x80000005, + ERROR_METHOD_NOT_IMPLEMENTED = 0x80000006, + ERROR_FAIL = 0x80004005, + ERROR_INVALID_ARG = 0x80070057, + ERROR_SERVER_INTERNAL_ERROR = 0x8004C600, + ERROR_SERVER_INVALID_MESSAGE = 0x8004C601, + ERROR_SERVER_SERVICE_SPECIFIC = 0x8004C604, + ERROR_BUSY_CANNOT_INITIALIZE = 0x8004DD00 + +} OpenCDMError; + +/** + * OpenCDM bool type. 0 is false, 1 is true. + */ +typedef enum { + OPENCDM_BOOL_FALSE = 0, + OPENCDM_BOOL_TRUE = 1 +} OpenCDMBool; + +/** + * Registered callbacks with OCDM sessions. + */ +typedef struct { + /** + * Request of process of DRM challenge data. Server is indicated by \ref url. The response of the server + * needs to be send to \ref opencdm_session_update. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param url Target URL to send challenge to. + * \param challenge Buffer containing challenge. + * \param challengeLength Length of challenge (in bytes). + */ + void (*process_challenge_callback)(struct OpenCDMSession* session, void* userData, const char url[], const uint8_t challenge[], const uint16_t challengeLength); + + /** + * Called when status of a key changes. Use \ref opencdm_session_status to find out new key status. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param keyId Buffer containing key ID. + * \param length Length of key ID buffer. + */ + void (*key_update_callback)(struct OpenCDMSession* session, void* userData, const uint8_t keyId[], const uint8_t length); + + /** + * Called when an error message is received from the DRM system + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param message Text string, null terminated, from the DRM session. + */ + void (*error_message_callback)(struct OpenCDMSession* session, void* userData, const char message[]); + + /** + * Called after all known key status changes were reported. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + */ + void (*keys_updated_callback)(const struct OpenCDMSession* session, void* userData); +} OpenCDMSessionCallbacks; + +/** + * \brief Creates DRM system. + * + * \return \ref OpenCDMAccessor instance, NULL on error. + */ +EXTERNAL struct OpenCDMSystem* opencdm_create_system(const char keySystem[]); + +/** + * \brief Creates DRM system. + * + * \param system Output parameter that will contain pointer to instance of \ref OpenCDMSystem. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_create_system_extended(const char keySystem[], struct OpenCDMSystem** system); + +/** + * Destructs an \ref OpenCDMAccessor instance. + * \param system \ref OpenCDMAccessor instance to desctruct. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_destruct_system(struct OpenCDMSystem* system); + +/** + * \brief Checks if a DRM system is supported. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of required key system (e.g. + * "com.microsoft.playready"). + * \param mimeType MIME type. + * \return Zero if supported, Non-zero otherwise. + * \remark mimeType is currently ignored. + */ +EXTERNAL OpenCDMError opencdm_is_type_supported(const char keySystem[], + const char mimeType[]); + +/** + * \brief Retrieves DRM system specific metadata. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param metadata, buffer to write metadata into, always 0 terminated (also when not large enough to hold all data) except when metadata is + * Null of course. Null allowed to retrieve required size needed for this buffer in metadataSize to be able to allocate required buffer + * for subsequent call to opencdm_is_type_supported + * \param metadataSize, in: size of metadata buffer, out: required size to hold all data available when return value is ERROR_MORE_DATA_AVAILBALE, + * , number of characters written into metadata (incl 0 terminator) otherwise. Note in case metadata could not hold all data but was not of zero + * length it is filled up to the maximum size (still zero terminated) but also ERROR_MORE_DATA_AVAILBALE is returned with the required size needed + * to hold all data + * \return Zero on success, non-zero on error. ERROR_MORE_DATA_AVAILBALE when the buffer was not large enough to hold all the data available. + */ +EXTERNAL OpenCDMError opencdm_system_get_metadata(struct OpenCDMSystem* system, + char metadata[], + uint16_t* metadataSize); + +/** + * \brief Returns string describing version of DRM system. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of queried key system (e.g. + * "com.microsoft.playready"). + * \param versionStr Char buffer to receive NULL-terminated version string. + * (Should as least be 64 chars long.) + * \return Zero if successful, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_get_version(struct OpenCDMSystem* system, + char versionStr[]); + +/** + * \brief Returns time according to DRM system. + * Some systems (e.g. PlayReady) keep their own clocks, for example to prevent + * rollback. Systems + * not implementing their own clock can return the system time. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of queried key system (e.g. "com.microsoft.playready"). + * \param time Output variable that will contain DRM system time. + * \return Zero if successful, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_get_drm_time(struct OpenCDMSystem* system, + uint64_t* time); + +/** + * \brief Maps key ID to \ref OpenCDMSession instance. + * + * In some situations we only have the key ID, but need the specific \ref + * OpenCDMSession instance that + * belongs to this key ID. This method facilitates this requirement. + * \param system Instance of \ref OpenCDMAccessor. + * \param keyId Array containing key ID. + * \param length Length of keyId array. + * \param maxWaitTime Maximum allowed time to block (in milliseconds). + * \return \ref OpenCDMSession belonging to key ID, or NULL when not found or + * timed out. This instance + * also needs to be destructed using \ref opencdm_session_destruct. + */ +EXTERNAL struct OpenCDMSession* opencdm_get_session(const uint8_t keyId[], + const uint8_t length, + const uint32_t waitTime); + +/** + * \brief Maps key ID to \ref OpenCDMSession instance within the given system instance. + * + * In some situations we only have the key ID, but need the specific \ref + * OpenCDMSession instance that + * belongs to this key ID. This method facilitates this requirement. + * \param system Instance of \ref OpenCDMSystem. + * \param keyId Array containing key ID. + * \param length Length of keyId array. + * \param maxWaitTime Maximum allowed time to block (in milliseconds). + * \return \ref OpenCDMSession belonging to key ID, or NULL when not found or + * timed out. This instance + * also needs to be destructed using \ref opencdm_session_destruct. + */ +EXTERNAL struct OpenCDMSession* opencdm_get_system_session(struct OpenCDMSystem* system, const uint8_t keyId[], + const uint8_t length, const uint32_t waitTime); + +/** + * \brief Gets support server certificate. + * + * Some DRMs (e.g. WideVine) use a system-wide server certificate. This method + * gets if system has support for that certificate. + * \param system Instance of \ref OpenCDMAccessor. + * \return Non-zero on success, zero on error. + */ +EXTERNAL OpenCDMBool opencdm_system_supports_server_certificate( + struct OpenCDMSystem* system); + +/** + * \brief Sets server certificate. + * + * Some DRMs (e.g. WideVine) use a system-wide server certificate. This method + * will set that certificate. Other DRMs will ignore this call. + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of key system to set server certificate for. + * \param serverCertificate Buffer containing certificate data. + * \param serverCertificateLength Buffer length of certificate data. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_set_server_certificate( + struct OpenCDMSystem* system, + const uint8_t serverCertificate[], const uint16_t serverCertificateLength); + +/** + * \brief Get metrics associated with a DRM system. + * + * Some DRMs (e.g. WideVine) offer metric data that can be used for any + * analyses. This function retrieves the metric data of the passed in + * system. It is up to the callee to interpret the baniary data correctly. + * \param system Instance of \ref OpenCDMAccessor. + * \param bufferLength Actual buffer length of the buffer parameter, on return + * it holds the number of bytes actually written in it. + * \param buffer Buffer length of buffer that can hold the metric data. + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_get_metric_system_data(struct OpenCDMSystem* system, + uint32_t* bufferLength, + uint8_t* buffer); + +/** + * \brief Create DRM session (for actual decrypting of data). + * + * Creates an instance of \ref OpenCDMSession using initialization data. + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem DRM system to create the session for. + * \param licenseType DRM specifc signed integer selecting License Type (e.g. + * "Limited Duration" for PlayReady). + * \param initDataType Type of data passed in \ref initData. + * \param initData Initialization data. + * \param initDataLength Length (in bytes) of initialization data. + * \param CDMData CDM data. + * \param CDMDataLength Length (in bytes) of \ref CDMData. + * \param callbacks the instance of \ref OpenCDMSessionCallbacks with callbacks to be called on events. + * \param userData the user data to be passed back to the \ref OpenCDMSessionCallbacks callbacks. + * \param session Output parameter that will contain pointer to instance of \ref OpenCDMSession. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_construct_session(struct OpenCDMSystem* system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], const uint16_t initDataLength, + const uint8_t CDMData[], const uint16_t CDMDataLength, OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session); + +/** + * Destructs an \ref OpenCDMSession instance. + * \param system \ref OpenCDMSession instance to desctruct. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_destruct_session(struct OpenCDMSession* session); + +/** + * Loads the data stored for a specified OpenCDM session into the CDM context. + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_load(struct OpenCDMSession* session); + +/** + * Process a key message response. + * \param session \ref OpenCDMSession instance. + * \param keyMessage Key message to process. + * \param keyLength Length of key message buffer (in bytes). + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_update(struct OpenCDMSession* session, + const uint8_t keyMessage[], + const uint16_t keyLength); + +/** + * Removes all keys/licenses related to a session. + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_remove(struct OpenCDMSession* session); + +/** + * Retrieves DRM session specific metadata of a session. + * \param session \ref OpenCDMSession instance. +* \param metadata, buffer to write metadata into, always 0 terminated (also when not large enough to hold all data) except when metadata is + * Null of course. Null allowed to retrieve required size needed for this buffer in metadataSize to be able to allocate required buffer + * for subsequent call to opencdm_session_metadata + * \param metadataSize, in: size of metadata buffer, out: required size to hold all data available when return value is ERROR_MORE_DATA_AVAILBALE, + * , number of characters written into metadata (incl 0 terminator) otherwise. Note in case metadata could not hold all data but was not of zero + * length it is filled up to the maximum size (still zero terminated) but also ERROR_MORE_DATA_AVAILBALE is returned with the required size needed + * to hold all data + * \return Zero on success, non-zero on error. ERROR_MORE_DATA_AVAILBALE when the buffer was not large enough to hold all the data available. + + */ +EXTERNAL OpenCDMError opencdm_session_metadata(const struct OpenCDMSession* session, + char metadata[], + uint16_t* metadataSize); + +/** + * Let CDM know playback stopped and reset output protection + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_resetoutputprotection(struct OpenCDMSession* session); + +/** + * Gets Session ID for a session. + * \param session \ref OpenCDMSession instance. + * \return Session ID, valid as long as \ref session is valid. + */ +EXTERNAL const char* opencdm_session_id(const struct OpenCDMSession* session); + +/** + * Checks if a session has a specific keyid. Will check both BE/LE + * \param session \ref OpenCDMSession instance. + * \param length Length of key ID buffer (in bytes). + * \param keyId Key ID. + * \return 1 if keyID found else 0. + */ +EXTERNAL uint32_t opencdm_session_has_key_id(struct OpenCDMSession* session, + const uint8_t length, const uint8_t keyId[]); + +/** + * Returns status of a particular key assigned to a session. + * \param session \ref OpenCDMSession instance. + * \param keyId Key ID. + * \param length Length of key ID buffer (in bytes). + * \return key status. + */ +EXTERNAL KeyStatus opencdm_session_status(const struct OpenCDMSession* session, + const uint8_t keyId[], const uint8_t length); + +/** + * Returns error for key (if any). + * \param session \ref OpenCDMSession instance. + * \param keyId Key ID. + * \param length Length of key ID buffer (in bytes). + * \return Key error (zero if no error, non-zero if error). + */ +EXTERNAL uint32_t opencdm_session_error(const struct OpenCDMSession* session, + const uint8_t keyId[], const uint8_t length); + +/** + * Returns system error. This reference general system, instead of specific key. + * \param session \ref OpenCDMSession instance. + * \return System error code, zero if no error. + */ +EXTERNAL OpenCDMError opencdm_session_system_error(const struct OpenCDMSession* session); + +/** + * Gets buffer ID for a session. + * \param session \ref OpenCDMSession instance. + * \return Buffer ID, valid as long as \ref session is valid. + */ +EXTERNAL const char* opencdm_session_buffer_id(const struct OpenCDMSession* session); + +/** + * Closes a session. + * \param session \ref OpenCDMSession instance. + * \return zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_close(struct OpenCDMSession* session); + +/** + * \brief Performs decryption. + * + * This method accepts encrypted data and will typically decrypt it + * out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system + * allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param encrypted Buffer containing encrypted data. If applicable, decrypted + * data will be stored here after this call returns. + * \param encryptedLength Length of encrypted data buffer (in bytes). + * \param encScheme CENC Schemes as defined in EncryptionScheme enum + * \param pattern Encryption pattern containing number of Encrypted and Clear blocks. + * \param IV Initial vector (IV) used during decryption. Can be NULL, in that + * case and IV of all zeroes is assumed. + * \param IVLength Length of IV buffer (in bytes). + * \param keyID keyID to use for decryption + * \param keyIDLength Length of keyID buffer (in bytes). + * \param initWithLast15 Whether decryption context needs to be initialized with + * last 15 bytes. Currently this only applies to PlayReady DRM. + * \return Zero on success, non-zero on error. + */ +#ifdef __cplusplus +EXTERNAL OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, + const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, + uint32_t initWithLast15 = 0); + +#else +EXTERNAL OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, + const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, + uint32_t initWithLast15); +#endif // __cplusplus + +/** + * \brief Get metrics associated with a DRM session. + * + * Some DRMs (e.g. WideVine) offer metric data that can be used for any + * analyses. This function retrieves the metric data of the passed in + * system. It is up to the callee to interpret the baniary data correctly. + * \param session Instance of \ref OpenCDMSession. + * \param bufferLength Actual buffer length of the buffer parameter, on return + * it holds the number of bytes actually written in it. + * \param buffer Buffer length of buffer that can hold the metric data. + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_get_metric_session_data(struct OpenCDMSession* session, + uint32_t* bufferLength, + uint8_t* buffer); + +/** + * \brief Performs decryption. + * + * This method accepts encrypted data and will typically decrypt it + * out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system + * allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param encrypted Buffer containing encrypted data. If applicable, decrypted + * data will be stored here after this call returns. + * \param encryptedLength Length of encrypted data buffer (in bytes). + * \param sampleInfo Per Sample information needed to decrypt this sample + * \param streamProperties Provides info about current stream + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_session_decrypt_v2(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const SampleInfo* sampleInfo, + const MediaProperties* streamProperties); + +/** + * @brief Close the cached open connection if it exists. + * + */ +EXTERNAL void opencdm_dispose(); + +#ifdef __cplusplus +} +#endif + +#endif // OPEN_CDM_H diff --git a/middleware/test/utests/drm/ocdm/open_cdm_adapter.h b/middleware/test/utests/drm/ocdm/open_cdm_adapter.h new file mode 100644 index 000000000..d4300d3ac --- /dev/null +++ b/middleware/test/utests/drm/ocdm/open_cdm_adapter.h @@ -0,0 +1,96 @@ + /* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __OPEN_CDM_ADAPTER_H +#define __OPEN_CDM_ADAPTER_H + +#include "open_cdm.h" + +struct _GstBuffer; +typedef struct _GstBuffer GstBuffer; + +struct _GstCaps; +typedef struct _GstCaps GstCaps; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Performs decryption based on adapter implementation. + * + * This method accepts encrypted data and will typically decrypt it out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param buffer Gstreamer buffer containing encrypted data and related meta data. If applicable, decrypted data will be stored here after this call returns. + * \param subSample Gstreamer buffer containing subsamples size which has been parsed from protection meta data. + * \param subSampleCount count of subsamples + * \param IV Gstreamer buffer containing initial vector (IV) used during decryption. + * \param keyID Gstreamer buffer containing keyID to use for decryption + * + * This method handles the Subsample mapping by consolidating all the encrypted data into one buffer before decrypting. This means the Subsample mappings are + * not passed on to the DRM implementation side. + * + * For CBCS support, EncryptionScheme and EncryptionPattern information can be added as part of the ProtectionMeta in the given format below + * "cipher-mode" G_TYPE_STRING (One of the Four Character Code (FOURCC) Protection schemes as defined in https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1:en) + * "crypt_byte_block" G_TYPE_UINT (Present only if cipher-mode is "cbcs") + * "skip_byte_block" G_TYPE_UINT (Present only cipher-mode is "cbcs") + + * \return Zero on success, non-zero on error. + */ + EXTERNAL OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, GstBuffer* buffer, GstBuffer* subSample, const uint32_t subSampleCount, + GstBuffer* IV, GstBuffer* keyID, uint32_t initWithLast15); + +/** + * \brief Performs decryption based on adapter implementation. + * + * This version 3 method accepts encrypted data and will typically decrypt it out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * This version assumes all data required is attached as metadata to the buffer. Specification for this data is as follows: + * + * Typically, the caller would parse the protection information for a video/audio frame from its input data and use this information to populate the + * GstStructure info field, which is then encapsulated in a GstProtectionMeta object and attached to the corresponding GstBuffer using the + * gst_buffer_add_protection_meta function. + * + * gst_structure [application/x-cenc] + * "iv" GST_TYPE_BUFFER + * "kid" GST_TYPE_BUFFER + * "subsample_count" G_TYPE_UINT + * "subsamples" GST_TYPE_BUFFER + * "cipher-mode" G_TYPE_STRING (One of the Four Character Code (FOURCC) Protection schemes as defined in https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1:en) + * "crypt_byte_block" G_TYPE_UINT (Present only if cipher-mode is "cbcs") + * "skip_byte_block" G_TYPE_UINT (Present only cipher-mode is "cbcs") + * + * This method passes on the subsample mapping to the DRM implementation and assumes that the DRM implementaion will handle the decryption based on subsample mapping. + * + * \param session \ref OpenCDMSession instance. + * \param buffer Gstreamer buffer containing encrypted data and related meta data. If applicable, decrypted data will be stored here after this call returns. + * \return Zero on success, non-zero on error. + */ + + EXTERNAL OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* session, GstBuffer* buffer, GstCaps* caps); + + +#ifdef __cplusplus +} +#endif + +#endif // __OPEN_CDM_ADAPTER_H diff --git a/middleware/test/utests/fakes/CMakeLists.txt b/middleware/test/utests/fakes/CMakeLists.txt new file mode 100644 index 000000000..1e6093eb2 --- /dev/null +++ b/middleware/test/utests/fakes/CMakeLists.txt @@ -0,0 +1,65 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2022 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +file(GLOB fakes_SRC "*.cpp") + +set(PLAYER_ROOT "../../../") +set(UTESTS_ROOT "..") + +include_directories(${PLAYER_ROOT}) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/playerJsonObject) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/drm/helper) +include_directories(${UTESTS_ROOT}/drm/ocdm) +include_directories(${UTESTS_ROOT}/drm/mocks) +include_directories(${UTESTS_ROOT}/mocks) +include_directories(${UTESTS_ROOT}/ocdm) +include_directories(${PLAYER_ROOT}/drm) +include_directories(${PLAYER_ROOT}/drm/ocdm) +include_directories(${PLAYER_ROOT}/baseConversion) +include_directories(${PLAYER_ROOT}/externals/contentsecuritymanager) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${LibXml2_INCLUDE_DIRS}) +message(STATUS "PLAYER_ROOT=${PLAYER_ROOT}") + + +if (JSC_INCDIR) + include_directories(${JSC_INCDIR}) +endif() + +# Mac OS X +if(CMAKE_SYSTEM_NAME STREQUAL Darwin) + include_directories(${PLAYER_ROOT}/.libs/systemd/src) +endif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + +include_directories(${PLAYER_ROOT}/.libs/include) +include_directories(${UTESTS_ROOT}/mocks) + +add_library(fakes ${fakes_SRC}) +target_include_directories(fakes PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../) +set_target_properties(fakes PROPERTIES FOLDER "utests") +set_target_properties(fakes PROPERTIES COMPILE_FLAGS "-Wno-effc++ -Wno-varargs") diff --git a/middleware/test/utests/fakes/FakeBase16.cpp b/middleware/test/utests/fakes/FakeBase16.cpp new file mode 100644 index 000000000..c0970cc98 --- /dev/null +++ b/middleware/test/utests/fakes/FakeBase16.cpp @@ -0,0 +1,25 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2023 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "base16.h" + +unsigned char *base16_Decode( const char *srcPtr, size_t srcLen, size_t *len ) +{ + return NULL; +} diff --git a/middleware/test/utests/fakes/FakeDRMHelper.cpp b/middleware/test/utests/fakes/FakeDRMHelper.cpp new file mode 100644 index 000000000..3721bb4af --- /dev/null +++ b/middleware/test/utests/fakes/FakeDRMHelper.cpp @@ -0,0 +1,38 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "MockDrmHelper.h" + +MockDrmHelper *g_mockDrmHelper = nullptr; + +DrmHelperEngine& DrmHelperEngine::getInstance() +{ + static DrmHelperEngine instance; + return instance; +} + +DrmHelperPtr DrmHelperEngine::createHelper(const struct DrmInfo& drmInfo) const +{ + return nullptr; +} + +bool DrmHelperEngine::hasDRM(const struct DrmInfo& drmInfo) const +{ + return false; +} \ No newline at end of file diff --git a/middleware/test/utests/fakes/FakeDRMSession.cpp b/middleware/test/utests/fakes/FakeDRMSession.cpp new file mode 100644 index 000000000..da347f6d4 --- /dev/null +++ b/middleware/test/utests/fakes/FakeDRMSession.cpp @@ -0,0 +1,25 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "DrmSession.h" + +string DrmSession::getKeySystem() +{ + return ""; +} diff --git a/middleware/test/utests/fakes/FakeDRMSessionManager.cpp b/middleware/test/utests/fakes/FakeDRMSessionManager.cpp new file mode 100644 index 000000000..289cb8525 --- /dev/null +++ b/middleware/test/utests/fakes/FakeDRMSessionManager.cpp @@ -0,0 +1,73 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "DrmSessionManager.h" +#include "DrmHelper.h" +#include "MockDrmSessionManager.h" +MockDRMSessionManager *g_mockDRMSessionManager = nullptr; + +DrmSessionManager::DrmSessionManager(int maxDrmSessions, void *player, std::function watermarkSessionUpdateCallback) +{ +} + +DrmSessionManager::~DrmSessionManager() +{ +} + + +void DrmSessionManager::setPlaybackSpeedState(bool live, double currentLatency, bool livepoint , double liveOffsetMs, int speed, double positionMs, bool firstFrameSeen) +{ +} + +void DrmSessionManager::hideWatermarkOnDetach() +{ +} + +void DrmSessionManager::setVideoMute(bool live, double currentLatency, bool livepoint , double liveOffsetMs,bool isVideoOnMute, double positionMs) +{ +} + +void DrmSessionManager::setVideoWindowSize(int width, int height) +{ + if (g_mockDRMSessionManager) + { + g_mockDRMSessionManager->setVideoWindowSize(width, height); + } +} + + +void DrmSessionManager::UpdateMaxDRMSessions(int maxSessions) +{ +} + + +DrmSession * DrmSessionManager::createDrmSession(int& responsecode, int& err, + const char* systemId, MediaFormat mediaFormat, const unsigned char * initDataPtr, + uint16_t initDataLen, int streamType, + DrmCallbacks* aamp, void *ptr , const unsigned char* contentMetadataPtr, + bool isPrimarySession) + { + return nullptr; + } + +SessionMgrState DrmSessionManager::getSessionMgrState() +{ + return SessionMgrState::eSESSIONMGR_INACTIVE; +} + diff --git a/middleware/test/utests/fakes/FakeGLib.cpp b/middleware/test/utests/fakes/FakeGLib.cpp new file mode 100644 index 000000000..d00304559 --- /dev/null +++ b/middleware/test/utests/fakes/FakeGLib.cpp @@ -0,0 +1,314 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "MockGLib.h" + +using namespace std; + +#if 0 +#define TRACE_FUNC() printf("%s\n" ,__func__) +#else +#define TRACE_FUNC() ((void)0) +#endif + +MockGLib *g_mockGLib = nullptr; + +void g_object_set(gpointer object, const gchar *first_property_name, ...) +{ + TRACE_FUNC(); + + if (g_mockGLib != nullptr) + { + va_list args_list; + va_start(args_list, first_property_name); + const gchar *property_name = first_property_name; + + while (property_name) + { + if ((strcmp(property_name, "mute") == 0) || + (strcmp(property_name, "show-video-window") == 0) || + (strcmp(property_name, "zoom-mode") == 0) || + (strcmp(property_name, "seamless-switch") == 0) + ) + { + g_mockGLib->g_object_set(object, property_name, va_arg(args_list, int)); + } + else if((strcmp(property_name, "rectangle") == 0)) + { + g_mockGLib->g_object_set(object, property_name, va_arg(args_list, char *)); + } + else if((strcmp(property_name, "volume") == 0)|| (strcmp(property_name, "stream-volume") == 0)) + { + g_mockGLib->g_object_set(object, property_name, va_arg(args_list, double)); + } + else if((strcmp(property_name, "attribute-values") == 0)) + { + g_mockGLib->g_object_set(object, property_name, va_arg(args_list, GstStructure*)); + } + else + { + va_arg(args_list, int); + } + property_name = va_arg(args_list, gchar *); + } + va_end(args_list); + } +} + +void g_object_get(gpointer object, const gchar *first_property_name, ...) +{ + TRACE_FUNC(); + if(g_mockGLib != nullptr) + { + va_list args_list; + va_start(args_list, first_property_name); + const gchar *property_name = first_property_name; + + while (property_name) + { + + if((strcmp(property_name, "stats") == 0)) + { + g_mockGLib->g_object_get(object, property_name, va_arg(args_list, GstStructure*)); + } + else if((strcmp(property_name, "video-pts") == 0)) + { + g_mockGLib->g_object_get(object, property_name, va_arg(args_list, gint64*)); + } + else if((strcmp(property_name, "queued_frames") == 0)) + { + g_mockGLib->g_object_get(object, property_name, va_arg(args_list, uint*)); + } + else if((strcmp(property_name, "videodecoder") == 0)) + { + g_mockGLib->g_object_get(object, property_name, va_arg(args_list, gpointer*)); + } + property_name = va_arg(args_list, gchar *); + } + va_end(args_list); + } +} + +gulong g_signal_connect_data(gpointer instance, const gchar *detailed_signal, GCallback c_handler, + gpointer data, GClosureNotify destroy_data, + GConnectFlags connect_flags) +{ + TRACE_FUNC(); + gulong retval = 0; + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_signal_connect_data(instance, detailed_signal, c_handler, + data, destroy_data, connect_flags); + } + return retval; +} + +gboolean g_type_check_instance_is_a(GTypeInstance *instance, GType iface_type) +{ + TRACE_FUNC(); + gboolean retval = FALSE; + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_type_check_instance_is_a(instance, iface_type); + } + + return retval; +} + +gboolean g_signal_handler_is_connected(gpointer instance, gulong handler_id) +{ + TRACE_FUNC(); + gboolean retval = FALSE; + printf("instance = %p, iface_type = %ld\n", instance, handler_id); + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_signal_handler_is_connected(instance, handler_id); + } + + return retval; +} + +void g_signal_handler_disconnect(gpointer instance, gulong handler_id) +{ + TRACE_FUNC(); + printf("instance = %p, iface_type = %ld\n", instance, handler_id); + + if (g_mockGLib != nullptr) + { + g_mockGLib->g_signal_handler_disconnect(instance, handler_id); + } +} + +GTypeInstance *g_type_check_instance_cast(GTypeInstance *instance, GType iface_type) +{ + TRACE_FUNC(); + return instance; +} + +GValue *g_value_init(GValue *value, GType g_type) +{ + TRACE_FUNC(); + return NULL; +} + +void g_value_set_pointer(GValue *value, gpointer v_pointer) +{ + TRACE_FUNC(); +} + +GParamSpec* g_object_class_find_property(GObjectClass* oclass, const gchar* property_name) +{ + GParamSpec* retval = NULL; + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_object_class_find_property(oclass, property_name); + } + + return retval; +} + + +void g_object_set_property(GObject *object, const gchar *property_name, const GValue *value) +{ + TRACE_FUNC(); +} + +void g_signal_emit_by_name(gpointer instance, const gchar *detailed_signal, ...) +{ +} + +gboolean g_type_check_instance(GTypeInstance *instance) +{ + TRACE_FUNC(); + return FALSE; +} + +gpointer g_value_get_object(const GValue *value) +{ + TRACE_FUNC(); + return NULL; +} + +gpointer g_object_new(GType object_type, const gchar *first_property_name, ...) +{ + TRACE_FUNC(); + return NULL; +} + +void g_object_unref(gpointer object) +{ + TRACE_FUNC(); +} + +guint g_timeout_add(guint interval, GSourceFunc function, gpointer data) +{ + guint retval = 0; + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_timeout_add(interval, function, data); + } + + return retval; +} + +gboolean g_source_remove(guint tag) +{ + gboolean retval = FALSE; + + if (g_mockGLib != nullptr) + { + retval = g_mockGLib->g_source_remove(tag); + } + return retval; +} + +GSource *g_main_current_source(void) +{ + return nullptr; +} + +guint g_source_get_id(GSource *source) +{ + return 0; +} + +void g_printerr(const gchar *format, ...) +{ + +} + +void g_clear_error(GError **err) +{ + +} + +void g_free(gpointer mem) +{ + if (g_mockGLib != nullptr) + { + g_mockGLib->g_free(mem); + } +} + +int g_strcmp0(const char *str1, const char *str2) +{ + return 0; +} + +guint g_timeout_add_full(gint priority, guint interval, GSourceFunc function, gpointer data, GDestroyNotify notify) +{ + return 0; +} + +void g_usleep(gulong microseconds) +{ + +} + +gpointer g_malloc(gsize n_bytes) +{ + gpointer ptr = NULL; + + if (g_mockGLib != nullptr) + { + ptr = g_mockGLib->g_malloc(n_bytes); + } + return ptr; +} + +gpointer g_realloc (gpointer mem, gsize n_bytes) +{ + gpointer ptr = NULL; + + if (g_mockGLib != nullptr) + { + ptr = g_mockGLib->g_realloc(mem, n_bytes); + } + return ptr; + +} diff --git a/middleware/test/utests/fakes/FakeGStreamer.cpp b/middleware/test/utests/fakes/FakeGStreamer.cpp new file mode 100644 index 000000000..ee05fa5f7 --- /dev/null +++ b/middleware/test/utests/fakes/FakeGStreamer.cpp @@ -0,0 +1,1063 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "MockGStreamer.h" + +MockGStreamer *g_mockGStreamer = nullptr; + +GstDebugCategory *GST_CAT_DEFAULT; +GstDebugLevel _gst_debug_min; + +#if 0 +#define TRACE_FUNC_ARG(format,...) printf("%s " ,__func__); printf(format,__VA_ARGS__) +#define TRACE_FUNC() printf("%s\n" ,__func__) +#else +#define TRACE_FUNC() ((void)0) +#define TRACE_FUNC_ARG(format,...) ((void)0) +#endif + +void gst_debug_bin_to_dot_file(GstBin *bin, GstDebugGraphDetails details, const gchar *file_name) +{ +} + +GType gst_object_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +GType gst_bin_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +GstMiniObject *gst_mini_object_ref(GstMiniObject *mini_object) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_mini_object_unref(GstMiniObject *mini_object) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_mini_object_unref(mini_object); + } +} + +GType gst_app_src_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +void gst_app_src_set_caps(GstAppSrc *appsrc, const GstCaps *caps) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_app_src_set_caps(appsrc, caps); + } +} + +void gst_app_src_set_stream_type(GstAppSrc *appsrc, GstAppStreamType type) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_app_src_set_stream_type(appsrc,type); + } +} + +GstFlowReturn gst_app_src_push_buffer(GstAppSrc *appsrc, GstBuffer *buffer) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_app_src_push_buffer(appsrc, buffer); + } + return GST_FLOW_OK; +} + +GstBuffer *gst_buffer_new(void) +{ + + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_buffer_new(); + } + return NULL; +} + +GstBuffer *gst_buffer_new_allocate(GstAllocator *allocator, gsize size, GstAllocationParams *params) +{ + + TRACE_FUNC(); + return NULL; +} + +GstBuffer *gst_buffer_new_wrapped(gpointer data, gsize size) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_buffer_new_wrapped(data, size); + } + return NULL; +} + +gboolean gst_buffer_map(GstBuffer *buffer, GstMapInfo *info, GstMapFlags flags) +{ + TRACE_FUNC(); + return FALSE; +} + +void gst_buffer_unmap(GstBuffer *buffer, GstMapInfo *info) +{ + TRACE_FUNC(); +} + +const gchar *gst_element_state_get_name(GstState state) +{ + const char *ptr = NULL; + const char *tbl[] = { + "GST_STATE_VOID_PENDING", //(0) – no pending state. + "GST_STATE_NULL", // (1) – the NULL state or initial state of an element. + "GST_STATE_READY", // (2) – the element is ready to go to PAUSED. + "GST_STATE_PAUSED", // (3) – the element is PAUSED, it is ready to accept and process data. Sink elements however only accept one buffer and then block. + "GST_STATE_PLAYING", // (4) – + }; + + if (state < (sizeof(tbl) / sizeof(tbl[0]))) + { + ptr = tbl[state]; + } + else + { + ptr = "error"; + } + TRACE_FUNC_ARG("%s \n",ptr); + + + return (const gchar *)ptr; +} + +GType gst_element_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +void gst_message_parse_warning(GstMessage *message, GError **gerror, gchar **debug) +{ + + TRACE_FUNC(); +} + +void gst_message_parse_error(GstMessage *message, GError **gerror, gchar **debug) +{ + TRACE_FUNC(); +} + +void gst_message_parse_state_changed(GstMessage *message, GstState *oldstate, GstState *newstate, + GstState *pending) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_message_parse_state_changed(message, oldstate, newstate, pending); + } +} + +void gst_message_parse_qos(GstMessage *message, gboolean *live, guint64 *running_time, + guint64 *stream_time, guint64 *timestamp, guint64 *duration) +{ + TRACE_FUNC(); +} + +const GstStructure *gst_message_get_structure(GstMessage *message) +{ + + TRACE_FUNC(); + return NULL; +} + +gboolean gst_structure_has_name(const GstStructure *structure, const gchar *name) +{ + TRACE_FUNC(); + return FALSE; +} + +const gchar *gst_message_type_get_name(GstMessageType type) +{ + TRACE_FUNC(); + return NULL; +} + +gboolean gst_message_parse_context_type(GstMessage *message, const gchar **context_type) +{ + + TRACE_FUNC(); + return FALSE; +} + +GstContext *gst_context_new(const gchar *context_type, gboolean persistent) +{ + TRACE_FUNC(); + return NULL; +} + +GstStructure *gst_context_writable_structure(GstContext *context) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_structure_set(GstStructure *structure, const gchar *fieldname, ...) +{ + TRACE_FUNC(); +} + +void gst_element_set_context(GstElement *element, GstContext *context) +{ + TRACE_FUNC(); +} + +GstElement *gst_pipeline_new(const gchar *name) +{ + + TRACE_FUNC(); + GstElement *return_ptr = nullptr; + if (g_mockGStreamer != nullptr) + { + return_ptr = g_mockGStreamer->gst_pipeline_new(name); + } + return return_ptr; +} + +GType gst_pipeline_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +GstBus *gst_pipeline_get_bus(GstPipeline *pipeline) +{ + GstBus *return_ptr = nullptr; + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return_ptr = g_mockGStreamer->gst_pipeline_get_bus(pipeline); + } + return return_ptr; +} + +guint gst_bus_add_watch(GstBus *bus, GstBusFunc func, gpointer user_data) +{ + guint id = 0; + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + id = g_mockGStreamer->gst_bus_add_watch(bus,func,user_data); + } + return id; +} + +void gst_bus_set_sync_handler(GstBus *bus, GstBusSyncHandler func, gpointer user_data, + GDestroyNotify notify) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_bus_set_sync_handler(bus, func, user_data, notify); + } +} + +GstQuery *gst_query_new_position(GstFormat format) +{ + GstQuery *return_ptr = nullptr; + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return_ptr = g_mockGStreamer->gst_query_new_position(format); + } + return return_ptr; +} + +gpointer gst_object_ref(gpointer object) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_object_unref(gpointer object) +{ + TRACE_FUNC(); + + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_object_unref(object); + } +} + +void gst_debug_log(GstDebugCategory *category, GstDebugLevel level, const gchar *file, + const gchar *function, gint line, GObject *object, const gchar *format, ...) +{ + TRACE_FUNC(); +} + +GstElement *gst_element_factory_make(const gchar *factoryname, const gchar *name) +{ + TRACE_FUNC_ARG("%s\n",(char *)factoryname); + + GstElement *return_ptr = nullptr; + if (g_mockGStreamer != nullptr) + { + return_ptr = g_mockGStreamer->gst_element_factory_make(factoryname,name); + } + return return_ptr; +} + +gboolean gst_element_seek_simple(GstElement *element, GstFormat format, GstSeekFlags seek_flags, + gint64 seek_pos) +{ + TRACE_FUNC(); + gboolean rtn = FALSE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_seek_simple(element, format, seek_flags, seek_pos); + } + return rtn; +} + +gboolean gst_bin_add(GstBin *bin, GstElement *element) +{ + TRACE_FUNC(); + gboolean rtn = FALSE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_bin_add(bin, element); + } + + return rtn; +} + +gboolean gst_bin_remove(GstBin *bin, GstElement *element) +{ + TRACE_FUNC(); + return FALSE; +} + +void gst_bin_add_many(GstBin *bin, GstElement *element_1, ...) +{ + TRACE_FUNC(); +} + +gboolean gst_element_link(GstElement *src, GstElement *dest) +{ + TRACE_FUNC(); + return FALSE; +} + +gboolean gst_element_link_many(GstElement *element_1, GstElement *element_2, ...) +{ + TRACE_FUNC(); + return FALSE; +} + +gboolean gst_element_sync_state_with_parent(GstElement *element) +{ + TRACE_FUNC(); + return FALSE; +} + +GstPad *gst_element_get_static_pad(GstElement *element, const gchar *name) +{ + TRACE_FUNC(); + if (g_mockGStreamer) + { + return g_mockGStreamer->gst_element_get_static_pad(element, name); + } + return NULL; +} + +GstEvent *gst_event_new_segment(const GstSegment *segment) +{ + TRACE_FUNC(); + return NULL; +} + +GstEvent *gst_event_new_flush_start(void) +{ + TRACE_FUNC(); + return NULL; +} + +GstEvent *gst_event_new_flush_stop(gboolean reset_time) +{ + TRACE_FUNC(); + return NULL; +} + +GstEvent *gst_event_new_step (GstFormat format, + guint64 amount, + gdouble rate, + gboolean flush, + gboolean intermediate) +{ + TRACE_FUNC(); + GstEvent *rtn = nullptr; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_event_new_step(format, amount, rate, flush, intermediate); + } + return rtn; +} + +gboolean gst_pad_push_event(GstPad *pad, GstEvent *event) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_pad_push_event(pad, event); + } + return FALSE; +} + +void gst_segment_init(GstSegment *segment, GstFormat format) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_segment_init(segment, format); + } +} + +const gchar *gst_flow_get_name(GstFlowReturn ret) +{ + TRACE_FUNC(); + return NULL; +} + +gboolean gst_element_query_position(GstElement *element, GstFormat format, gint64 *cur) +{ + TRACE_FUNC(); + bool rtn = FALSE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_query_position(element, format, cur ); + } + return rtn; +} + +GstStateChangeReturn gst_element_get_state(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) +{ + TRACE_FUNC(); + GstStateChangeReturn rtn = GST_STATE_CHANGE_FAILURE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_get_state(element, state, pending, timeout); + } + return rtn; +} + +GstEvent *gst_event_new_eos(void) +{ + TRACE_FUNC(); + return NULL; +} + +GstMessage *gst_bus_timed_pop_filtered(GstBus *bus, GstClockTime timeout, GstMessageType types) +{ + TRACE_FUNC(); + return NULL; +} + +gboolean gst_bus_remove_watch(GstBus *bus) +{ + TRACE_FUNC(); + gboolean retval = FALSE; + + if (g_mockGStreamer != nullptr) + { + retval = g_mockGStreamer->gst_bus_remove_watch(bus); + } + return retval; +} + +gchar *gst_object_get_name(GstObject *object) +{ + if (object && object->name) + { + TRACE_FUNC_ARG("%s \n", object->name); + // See SafeName() in gstplayer.cpp + char *ptr = object->name ; + char *g_ptr = (char *)malloc(strlen(ptr) + 1); + strcpy(g_ptr, ptr); + + return (gchar *)g_ptr; + } + + return NULL; +} + +GstStateChangeReturn gst_element_set_state(GstElement *element, GstState state) +{ + TRACE_FUNC_ARG("%d \n",state); + + GstStateChangeReturn rtn = GST_STATE_CHANGE_FAILURE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_set_state(element, state); + } + + return rtn; +} + +gboolean gst_element_send_event(GstElement *element, GstEvent *event) +{ + TRACE_FUNC(); + gboolean rtn = FALSE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_send_event(element, event); + } + return rtn; +} + +GstQuery *gst_query_new_duration(GstFormat format) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_query_new_duration(format); + } + return NULL; +} + +gboolean gst_element_query(GstElement *element, GstQuery *query) +{ + TRACE_FUNC(); + gboolean rtn = FALSE; + + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_element_query(element, query); + } + return rtn; +} + +void gst_query_parse_duration(GstQuery *query, GstFormat *format, gint64 *duration) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_query_parse_duration(query, format, duration); + } +} + +GstQuery *gst_query_new_segment(GstFormat format) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_query_parse_segment(GstQuery *query, gdouble *rate, GstFormat *format, gint64 *start_value, + gint64 *stop_value) +{ + + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_query_parse_segment(query, rate, format, start_value, stop_value); + } +} + +void gst_query_parse_position(GstQuery *query, GstFormat *format, gint64 *cur) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_query_parse_position(query, format, cur); + } +} + +gboolean gst_element_seek(GstElement *element, gdouble rate, GstFormat format, GstSeekFlags flags, + GstSeekType start_type, gint64 start, GstSeekType stop_type, gint64 stop) +{ + TRACE_FUNC(); + gboolean retval = FALSE; + + if (g_mockGStreamer != nullptr) + { + retval = g_mockGStreamer->gst_element_seek(element, rate, format, flags, start_type, start, stop_type, stop); + } + return retval; +} + +guint64 gst_app_src_get_current_level_bytes(GstAppSrc *appsrc) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_app_src_get_current_level_bytes(appsrc); + } + return 0; +} + +GType gst_registry_get_type(void) +{ + + TRACE_FUNC(); + return 0; +} + +GstRegistry *gst_registry_get(void) +{ + TRACE_FUNC(); + return NULL; +} + +GstPluginFeature *gst_registry_lookup_feature(GstRegistry *registry, const char *name) +{ + + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_registry_lookup_feature(registry, name); + } + return NULL; +} + +GstStructure *gst_structure_new(const gchar *name, const gchar *firstfield, ...) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_structure_new(); + } + return NULL; +} + +GstEvent *gst_event_new_custom(GstEventType type, GstStructure *structure) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_event_new_custom(type, structure); + } + return NULL; +} + +gboolean gst_buffer_copy_into(GstBuffer *dest, GstBuffer *src, GstBufferCopyFlags flags, + gsize offset, gsize size) +{ + + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_buffer_copy_into(dest, src, flags, offset, size); + } + return FALSE; +} + +void gst_plugin_feature_set_rank(GstPluginFeature *feature, guint rank) +{ +} + +gboolean gst_object_replace(GstObject **oldobj, GstObject *newobj) +{ + TRACE_FUNC(); + gboolean rtn = FALSE; + if (g_mockGStreamer != nullptr) + { + rtn = g_mockGStreamer->gst_object_replace(oldobj, newobj); + } + + return rtn; +} + +void gst_message_parse_stream_status(GstMessage *message, GstStreamStatusType *type, + GstElement **owner) +{ + TRACE_FUNC(); +} + +const GValue *gst_message_get_stream_status_object(GstMessage *message) +{ + TRACE_FUNC(); + return NULL; +} + +GType gst_task_get_type(void) +{ + TRACE_FUNC(); + return 0; +} + +void gst_task_set_pool(GstTask *task, GstTaskPool *pool) +{ + TRACE_FUNC(); +} + +gpointer gst_object_ref_sink(gpointer object) +{ + TRACE_FUNC_ARG("%p\n",object); + return object; +} + +const GValue *gst_structure_get_value(const GstStructure *structure, const gchar *fieldname) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_structure_get_value(structure,fieldname); + } + return NULL; +} + +guint64 g_value_get_uint64(const GValue *value) +{ + TRACE_FUNC(); + if(g_mockGStreamer != nullptr) + { + return g_mockGStreamer->g_value_get_uint64(value); + } + return 0; +} + +void gst_structure_free(GstStructure *structure) +{ + TRACE_FUNC(); +} + +gboolean gst_init_check(int *argc, char **argv[], GError **error) +{ + TRACE_FUNC(); + return TRUE; +} + +GstCaps *gst_caps_new_simple(const char *media_type, const char *fieldname, ...) +{ + GstCaps *return_ptr = NULL; + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + va_list ap; + va_start(ap, fieldname); + GType var1 = va_arg(ap, GType); + int var2 = va_arg(ap, int); + void *ptr = va_arg(ap, void *); + return_ptr = g_mockGStreamer->gst_caps_new_simple(media_type, fieldname, var1, var2, ptr); + va_end(ap); + } + + return return_ptr; +} + +void gst_debug_set_threshold_from_string(const gchar *list, gboolean reset) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_debug_set_threshold_from_string(list, reset); + } +} + +GstFlowReturn gst_app_src_end_of_stream (GstAppSrc * appsrc) +{ + return GST_FLOW_OK; +} + +gulong gst_pad_add_probe (GstPad * pad, + GstPadProbeType mask, + GstPadProbeCallback callback, + gpointer user_data, + GDestroyNotify destroy_data) +{ + TRACE_FUNC(); + return 0; +} + +GstCaps *gst_pad_get_current_caps (GstPad * pad) +{ + TRACE_FUNC(); + return NULL; +} + +gchar *gst_caps_to_string (const GstCaps * caps) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_pad_remove_probe (GstPad * pad, gulong id) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_pad_remove_probe(pad, id); + } +} + +gboolean gst_mini_object_replace (GstMiniObject **olddata, GstMiniObject *newdata) +{ + TRACE_FUNC(); + return FALSE; +} + +void gst_event_copy_segment (GstEvent *event, GstSegment *segment) +{ + TRACE_FUNC(); +} + +void gst_event_parse_seek (GstEvent *event, gdouble *rate, GstFormat *format, + GstSeekFlags *flags, + GstSeekType *start_type, gint64 *start, + GstSeekType *stop_type, gint64 *stop) +{ + TRACE_FUNC(); +} + +GstSegment * gst_segment_new(void) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_segment_free (GstSegment *segment) +{ + TRACE_FUNC(); +} + +GstEvent * gst_event_new_instant_rate_change (gdouble rate_multiplier, + GstSegmentFlags new_flags) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_event_set_seqnum(GstEvent *event, guint32 seqnum) +{ + TRACE_FUNC(); +} + +GstPad* gst_pad_get_peer(GstPad *pad) +{ + TRACE_FUNC(); + return NULL; +} + +gboolean gst_pad_send_event(GstPad *pad, GstEvent *event) +{ + TRACE_FUNC(); + return false; +} + +guint32 gst_event_get_seqnum(GstEvent *event) +{ + TRACE_FUNC(); + return 0; +} + +gboolean gst_base_sink_is_async_enabled (GstBaseSink * sink) +{ + TRACE_FUNC(); + gboolean retval = FALSE; + + if (g_mockGStreamer != nullptr) + { + retval = g_mockGStreamer->gst_base_sink_is_async_enabled(sink); + } + return retval; +} + +void gst_base_sink_set_async_enabled (GstBaseSink * sink, gboolean enabled) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + g_mockGStreamer->gst_base_sink_set_async_enabled(sink, enabled); + } +} + +GstEvent* gst_event_new_seek(gdouble rate, GstFormat format, GstSeekFlags flags, + GstSeekType start_type, gint64 start, + GstSeekType stop_type, gint64 stop) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_caps_set_simple (GstCaps * caps, const char *field, ...) +{ + TRACE_FUNC(); +} + +GType gst_base_sink_get_type (void) +{ + TRACE_FUNC(); + return 0; +} + +gboolean gst_element_add_pad (GstElement * element, GstPad * pad) +{ + TRACE_FUNC(); + return FALSE; +} + +GstEvent* gst_event_new_protection(const gchar * system_id, GstBuffer * data, const gchar * origin) +{ + TRACE_FUNC(); + if (g_mockGStreamer != nullptr) + { + return g_mockGStreamer->gst_event_new_protection(system_id, data, origin); + } + return NULL; +} + +void gst_message_parse_reset_time(GstMessage *message, GstClockTime *running_time) +{ + TRACE_FUNC(); +} + +gchar * gst_structure_to_string(const GstStructure *structure) +{ + TRACE_FUNC(); + return NULL; +} + +GstElement * gst_bin_new (const gchar * name) +{ + TRACE_FUNC(); + return NULL; +} +void gst_deinit (void) +{ + TRACE_FUNC(); +} + +void gst_init(int *argc, char **argv[]) +{ + TRACE_FUNC(); +} + +GstPlugin *gst_plugin_load_by_name (const gchar * name) +{ + TRACE_FUNC(); + return NULL; +} + +void gst_registry_remove_feature (GstRegistry * registry, + GstPluginFeature * feature) +{ + TRACE_FUNC(); +} + +gboolean +gst_registry_add_feature (GstRegistry * registry, + GstPluginFeature * feature) + +{ + TRACE_FUNC(); + return false; +} + +GstPad * gst_ghost_pad_new (const gchar * name, GstPad * target) +{ + TRACE_FUNC(); + return NULL; +} + +GstCaps *gst_app_src_get_caps(GstAppSrc *appsrc) +{ + TRACE_FUNC(); + return NULL; +} + +GstSample *gst_sample_new (GstBuffer * buffer, GstCaps * caps, const GstSegment * segment, GstStructure * info) +{ + TRACE_FUNC(); + return NULL; +} + +GstFlowReturn gst_app_src_push_sample (GstAppSrc * appsrc, GstSample * sample) +{ + TRACE_FUNC(); + return GST_FLOW_OK; +} + +GstMeta * gst_buffer_get_meta (GstBuffer * buffer, GType api){ return NULL; } +GstStructure * gst_caps_get_structure ( const GstCaps *caps , guint index ){ return NULL; } +void gst_structure_set_name (GstStructure * structure, const gchar * name){} +const gchar * gst_structure_nth_field_name (const GstStructure * structure, guint index){ return NULL; } +gboolean gst_structure_has_field (const GstStructure * structure, const gchar * fieldname){ return FALSE; } +gboolean gst_buffer_remove_meta(GstBuffer *buffer, GstMeta *meta){ return FALSE; } +void gst_caps_append_structure(GstCaps *caps, GstStructure *structure){} +guint gst_caps_get_size(const GstCaps *caps){ return 0; } +GstCaps *gst_caps_intersect_full(GstCaps *caps1, GstCaps *caps2, GstCapsIntersectMode mode){ return NULL; } +gboolean gst_caps_is_empty(const GstCaps *caps){ return FALSE; } +gboolean gst_caps_is_subset(const GstCaps *subset,const GstCaps *superset){ return FALSE; } +GstCaps * gst_caps_new_empty(void){ return NULL; } +void gst_element_class_add_static_pad_template (GstElementClass *klass, GstStaticPadTemplate *static_templ){} +void gst_element_class_set_static_metadata( GstElementClass *klass, const gchar *longname, const gchar *classification, const gchar *description, const gchar *author){} +gboolean gst_element_post_message(GstElement * element, GstMessage * message){ return FALSE; } +void gst_event_parse_protection(GstEvent * event, const gchar ** system_id, GstBuffer ** data, const gchar ** origin){} +GstMessage *gst_message_new_application(GstObject * src, GstStructure * structure){ return NULL; } +GstMessage *gst_message_new_error(GstObject * src, GError * error, const gchar * debug){ return NULL; } +gboolean gst_pad_peer_query_position(GstPad *pad, GstFormat format, gint64 *cur){ return FALSE; } +gboolean gst_pad_peer_query(GstPad *pad, GstQuery *query){ return FALSE; } +GstCaps * gst_pad_query_caps(GstPad *pad, GstCaps *filter){ return NULL; } +const GstStructure * gst_query_get_structure(GstQuery *query){ return NULL; } +GstQuery * gst_query_new_custom(GstQueryType type, GstStructure *structure){ return NULL; } +GstStructure *gst_structure_copy(const GstStructure * structure){ return NULL; } +gboolean gst_structure_get_boolean(const GstStructure * structure, const gchar * fieldname, gboolean * value){ return FALSE; } +const gchar *gst_structure_get_name(const GstStructure * structure){ return NULL; } +const gchar *gst_structure_get_string(const GstStructure * structure, const gchar * fieldname){ return NULL; } +gboolean gst_structure_get_uint(const GstStructure * structure, const gchar * fieldname, guint * value){ return FALSE; } +gboolean gst_structure_is_equal(const GstStructure * structure1, const GstStructure * structure2){ return FALSE; } +gint gst_structure_n_fields(const GstStructure * structure){ return 0; } +void gst_structure_remove_field(GstStructure * structure, const gchar * fieldname){} +GstMiniObject * gst_mini_object_copy (const GstMiniObject * mini_object){ return NULL; } +GType gst_protection_meta_api_get_type (void){ return 0; } +GQuark gst_stream_error_quark( void ){ return 0; } +GstDebugCategory *_gst_debug_category_new(const gchar * name, guint color, const gchar * description){ return NULL; } +void _gst_debug_register_funcptr(GstDebugFuncPtr func, const gchar * ptrname){} +const gchar * _gst_debug_nameof_funcptr(GstDebugFuncPtr func){ return NULL; } + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _GstBaseTransform GstBaseTransform; +GType gst_base_transform_get_type(void){ return 0; } +void gst_base_transform_set_gap_aware(GstBaseTransform *trans, gboolean gap_aware){} +void gst_base_transform_set_in_place(GstBaseTransform *trans, gboolean in_place){} +void gst_base_transform_set_passthrough(GstBaseTransform *trans, gboolean passthrough){} + +#ifdef __cplusplus +} +#endif diff --git a/middleware/test/utests/fakes/FakeGstHandlerControl.cpp b/middleware/test/utests/fakes/FakeGstHandlerControl.cpp new file mode 100644 index 000000000..6e58019e7 --- /dev/null +++ b/middleware/test/utests/fakes/FakeGstHandlerControl.cpp @@ -0,0 +1,51 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "MockGstHandlerControl.h" +#include "GstHandlerControl.h" + +MockGstHandlerControl *g_mockGstHandlerControl = nullptr; + +GstHandlerControl::ScopeHelper& GstHandlerControl::ScopeHelper::operator=(GstHandlerControl::ScopeHelper&& other) +{ + return *this; +} + +void GstHandlerControl::handlerEnd() +{ +} + +bool GstHandlerControl::isEnabled() const +{ + bool retvalue = false; + if (g_mockGstHandlerControl != nullptr) + { + retvalue = g_mockGstHandlerControl->isEnabled(); + } + return retvalue; +} + +GstHandlerControl::ScopeHelper GstHandlerControl::getScopeHelper() +{ + return GstHandlerControl::ScopeHelper(this); +} + +bool GstHandlerControl::waitForDone(int MaximumDelayMilliseconds, std::string name) +{ + return true; +} diff --git a/middleware/test/utests/fakes/FakeGstPlayerTaskPool.cpp b/middleware/test/utests/fakes/FakeGstPlayerTaskPool.cpp new file mode 100644 index 000000000..eaefcef23 --- /dev/null +++ b/middleware/test/utests/fakes/FakeGstPlayerTaskPool.cpp @@ -0,0 +1,25 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gstplayertaskpool.h" + +GType gst_player_taskpool_get_type(void) +{ + return 0; +} diff --git a/middleware/test/utests/fakes/FakeGstUtils.cpp b/middleware/test/utests/fakes/FakeGstUtils.cpp new file mode 100644 index 000000000..73eab7ac0 --- /dev/null +++ b/middleware/test/utests/fakes/FakeGstUtils.cpp @@ -0,0 +1,29 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2023 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "MockGstUtils.h" +#include "GstUtils.h" +MockGstUtils *g_mockGstUtils = nullptr; +GstCaps *GetCaps(GstStreamOutputFormat format) +{ + if(g_mockGstUtils) + { + return g_mockGstUtils->GetCaps(format); + } + return nullptr; +} diff --git a/middleware/test/utests/fakes/FakePlayerJsonObject.cpp b/middleware/test/utests/fakes/FakePlayerJsonObject.cpp new file mode 100644 index 000000000..71ca852b3 --- /dev/null +++ b/middleware/test/utests/fakes/FakePlayerJsonObject.cpp @@ -0,0 +1,226 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "MockPlayerJsonObject.h" +#include "PlayerJsonObject.h" + +std::shared_ptr g_mockPlayerJsonObject; + +PlayerJsonObject::PlayerJsonObject() : mParent(NULL), mJsonObj() +{ +} + +PlayerJsonObject::PlayerJsonObject(const std::string& jsonStr) : mParent(NULL), mJsonObj() +{ +} + +PlayerJsonObject::PlayerJsonObject(const char* jsonStr) : mParent(NULL), mJsonObj() +{ +} + +PlayerJsonObject::~PlayerJsonObject() +{ +} + +bool PlayerJsonObject::add(const std::string& name, const std::string& value, const ENCODING encoding) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value, encoding); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, const char *value, const ENCODING encoding) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value, encoding); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, const std::vector& values) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, values); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, const std::vector& values, const ENCODING encoding) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, values, encoding); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, PlayerJsonObject& value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, std::vector& values) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, values); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, cJSON *value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, bool value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, int value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, double value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::add(const std::string& name, long value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->add(name, value); + } + return false; +} + +bool PlayerJsonObject::set(PlayerJsonObject *parent, cJSON *object) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->set(parent, object); + } + return false; +} + +bool PlayerJsonObject::get(const std::string& name, PlayerJsonObject &value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->get(name, value); + } + return false; +} + +bool PlayerJsonObject::get(const std::string& name, std::string& value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->get(name, value); + } + return false; +} + +bool PlayerJsonObject::get(const std::string& name, int& value) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->get(name, value); + } + return false; +} + +bool PlayerJsonObject::get(const std::string& name, std::vector& values) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->get(name, values); + } + return false; +} + +bool PlayerJsonObject::get(const std::string& name, std::vector& values, const ENCODING encoding) +{ + if (g_mockPlayerJsonObject != nullptr) + { + return g_mockPlayerJsonObject->get(name, values, encoding); + } + return false; +} + +std::string PlayerJsonObject::print() +{ + return ""; +} + +std::string PlayerJsonObject::print_UnFormatted() +{ + return ""; +} + +void PlayerJsonObject::print(std::vector& data) +{ +} + +bool PlayerJsonObject::isArray(const std::string& name) +{ + return false; +} + +bool PlayerJsonObject::isString(const std::string& name) +{ + return false; +} + +bool PlayerJsonObject::isNumber(const std::string& name) +{ + return false; +} + +bool PlayerJsonObject::isObject(const std::string& name) +{ + return false; +} diff --git a/middleware/test/utests/fakes/FakePlayerScheduler.cpp b/middleware/test/utests/fakes/FakePlayerScheduler.cpp new file mode 100644 index 000000000..a96d26d6c --- /dev/null +++ b/middleware/test/utests/fakes/FakePlayerScheduler.cpp @@ -0,0 +1,68 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "MockPlayerScheduler.h" +#include "PlayerScheduler.h" + +MockPlayerScheduler *g_mockPlayerScheduler = nullptr; + +PlayerScheduler::PlayerScheduler() +{ +} + +PlayerScheduler::~PlayerScheduler() +{ +} + +void PlayerScheduler::StartScheduler() +{ +} + +void PlayerScheduler::StopScheduler() +{ +} + +void PlayerScheduler::SuspendScheduler() +{ +} + +void PlayerScheduler::ResumeScheduler() +{ +} + +void PlayerScheduler::RemoveAllTasks() +{ +} + +int PlayerScheduler::ScheduleTask(PlayerAsyncTaskObj obj) +{ + if (g_mockPlayerScheduler != nullptr) + { + return g_mockPlayerScheduler->ScheduleTask(obj); + } + else + { + return 0; + } +} + +bool PlayerScheduler::RemoveTask(int id) +{ + return false; +} diff --git a/middleware/test/utests/fakes/FakePlayerThunderInterface.cpp b/middleware/test/utests/fakes/FakePlayerThunderInterface.cpp new file mode 100644 index 000000000..085c4b718 --- /dev/null +++ b/middleware/test/utests/fakes/FakePlayerThunderInterface.cpp @@ -0,0 +1,174 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** + * @file FakePlayerThunderInterface.cpp + * @brief Fake Player Thunder Interface + */ + +#include "PlayerThunderInterface.h" + +PlayerThunderInterface::PlayerThunderInterface(PlayerThunderAccessPlugin callsign) +{ +} + +PlayerThunderInterface::~PlayerThunderInterface() +{ +} + +bool PlayerThunderInterface::ActivatePlugin() +{ + return false; +} + +bool PlayerThunderInterface::UnSubscribeEvent(std::string eventName) +{ + return false; +} + +bool PlayerThunderInterface::SetVideoRectangle(int x, int y, int w, int h, std::string videoInputType, PlayerThunderAccessShim shim) +{ + return false; +} + + +void PlayerThunderInterface::RegisterAllEventsVideoin(std::function OnSignalChangedCb, std::function OnInputStatusChangedCb) +{ +} + +void PlayerThunderInterface::UnRegisterAllEventsVideoin() +{ +} + +void PlayerThunderInterface::StartHelperVideoin(int port, std::string videoInputType) +{ +} + +void PlayerThunderInterface::StopHelperVideoin(std::string videoInputType) +{ +} + +void PlayerThunderInterface::RegisterEventOnVideoStreamInfoUpdateHdmiin(std::function videoInfoUpdatedMethodCb) +{ +} + +void PlayerThunderInterface::RegisterOnPlayerStatusOta(std::function onPlayerStatusCb) +{ +} + +void PlayerThunderInterface::ReleaseOta() +{ +} + +void PlayerThunderInterface::StartOta(std::string url, std::string waylandDisplay, std::string preferredLanguagesString, std::string atsc_preferredLanguagesString, std::string preferredRenditionString, std::string atsc_preferredRenditionString) +{ +} + +void PlayerThunderInterface::StopOta() +{ +} + +std::string PlayerThunderInterface::GetAudioTracksOta(std::vector audData) +{ + std::string ret = ""; + return ret; +} + +std::string PlayerThunderInterface::SetAudioTrackOta(int index, int primaryKey) +{ + std::string ret = ""; + return ret; +} + +bool PlayerThunderInterface::GetTextTracksOta(std::vector txtData) +{ + return false; +} + +void PlayerThunderInterface::DisableContentRestrictionsOta(long grace, long time, bool eventChange) +{ +} + +void PlayerThunderInterface::EnableContentRestrictionsOta() +{ +} + +bool PlayerThunderInterface::InitRmf() +{ + return false; +} + +bool PlayerThunderInterface::StartRmf(std::string url, std::function onPlayerStatusHandlerCb, std::function onPlayerErrorHandlerCb) +{ + return false; +} + +void PlayerThunderInterface::SetPreferredAudioLanguages(PlayerPreferredAudioData data, PlayerThunderAccessShim shim) +{ +} + +void PlayerThunderInterface::StopRmf() +{ +} + +bool PlayerThunderInterface::DeleteWatermark(int layerID) +{ + return false; +} + +bool PlayerThunderInterface::CreateWatermark(int layerID) +{ + return false; +} + +bool PlayerThunderInterface::ShowWatermark(int opacity) +{ + return false; +} + +bool PlayerThunderInterface::HideWatermark() +{ + return false; +} + +bool PlayerThunderInterface::UpdateWatermark(int layerID, int sharedMemoryKey, int size) +{ + return false; +} + +std::string PlayerThunderInterface::GetMetaDataWatermark() +{ + std::string ret = ""; + return ret; +} + +bool PlayerThunderInterface::PersistentStoreSaveWatermark(const char* base64Image, std::string metaData) +{ + return false; +} + +bool PlayerThunderInterface::PersistentStoreLoadWatermark(int layerID) +{ + return false; +} + +bool PlayerThunderInterface::IsThunderAccess() +{ + return false; +} \ No newline at end of file diff --git a/middleware/test/utests/fakes/FakePlayerUtils.cpp b/middleware/test/utests/fakes/FakePlayerUtils.cpp new file mode 100644 index 000000000..7d46ba91c --- /dev/null +++ b/middleware/test/utests/fakes/FakePlayerUtils.cpp @@ -0,0 +1,64 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "PlayerUtils.h" +#include "MockPlayerUtils.h" +#include + +MockPlayerUtils *g_mockPlayerUtils = nullptr; + +char *base64_URL_Encode(const unsigned char *src, size_t len) +{ + return NULL; +} + +unsigned char *base64_URL_Decode(const char *src, size_t *len, size_t srcLen) +{ + return NULL; +} +long long GetCurrentTimeMS(void) +{ + long long timeMS = 0; + + if (g_mockPlayerUtils) + { + timeMS = g_mockPlayerUtils->GetCurrentTimeMS(); + } + + return timeMS; +} + +static std::hash std_thread_hasher; + +std::size_t GetThreadID( const std::thread &t ) +{ + return std_thread_hasher( t.get_id() ); +} +std::size_t GetThreadID( void ) +{ + return std_thread_hasher( std::this_thread::get_id() ); +} +/** + * @brief support for POSIX threads + */ +std::size_t GetThreadID( const pthread_t &t ) +{ + static std::hash pthread_hasher; + return pthread_hasher( t ); +} diff --git a/middleware/test/utests/fakes/FakeSocInterface.cpp b/middleware/test/utests/fakes/FakeSocInterface.cpp new file mode 100644 index 000000000..75a2321b6 --- /dev/null +++ b/middleware/test/utests/fakes/FakeSocInterface.cpp @@ -0,0 +1,257 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +#include "SocInterface.h" +#include "vendor/default/DefaultSocInterface.h" +#include "vendor/amlogic/AmlogicSocInterface.h" +#include "vendor/brcm/BrcmSocInterface.h" +#include "vendor/realtek/RealtekSocInterface.h" +DefaultSocInterface::DefaultSocInterface() +{ +} +std::shared_ptr SocInterface::CreateSocInterface() +{ + std::shared_ptr obj = std::make_shared(); + return obj; +} +bool DefaultSocInterface::UseAppSrc() +{ +#if defined (__APPLE__) + return true; +#endif + return false; +} + +void DefaultSocInterface::SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume) +{ + isSinkBinVolume = false; + volume = "volume"; + mute = "mute"; +#if defined(__APPLE__) + isSinkBinVolume = true; +#endif +} +/** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ +void DefaultSocInterface::SetAC4Tracks(GstElement *src, int trackId) +{ + g_object_set(src, "ac4-presentation-group-index", trackId, NULL); +} +bool DefaultSocInterface::IsVideoSink(const char* name) +{ + return name && ( + StartsWith(name,"rialtomsevideosink") || + StartsWith(name, "brcmvideosink") || + StartsWith(name, "westerossink") ); +} +/** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @param isRialto Rialto flag. + * @param isWesteros Westeros flag. + * @return True if it's a video decoder, false otherwise. + */ +bool DefaultSocInterface::IsVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name,"rialtomsevideosink") || + StartsWith(name, "brcmvideosink") || + StartsWith(name, "westerossink") ); +} + +/** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ +bool DefaultSocInterface::IsAudioOrVideoDecoder(const char* name) +{ + return StartsWith(name,"rialtomsevideosink") || StartsWith(name,"rialtomseaudiosink"); +} + +/** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ +void DefaultSocInterface::SetPlaybackFlags(gint &flags, bool isSub) +{ +#if (defined(__APPLE__)) + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_SOFT_VOLUME; +#else + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_NATIVE_AUDIO | PLAY_FLAG_NATIVE_VIDEO; +#endif + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_SOFT_VOLUME; + if(isSub) + { + flags = PLAY_FLAG_TEXT; + } +} +bool DefaultSocInterface::IsSimulatorFirstFrame() +{ +#if (defined(RPI) || defined(__APPLE__) || defined(UBUNTU)) + return true; +#endif + return false; +} +bool DefaultSocInterface::IsSimulatorSink() +{ +#if !defined(UBUNTU) + return false; +#endif + return true; +} +void DefaultSocInterface::ConfigurePluginPriority() +{ +#ifdef UBUNTU + GstPluginFeature* pluginFeature = gst_registry_lookup_feature(gst_registry_get(), "pulsesink"); + if (pluginFeature != NULL) + { + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_SECONDARY); + gst_object_unref(pluginFeature); + } +#endif +} +bool DefaultSocInterface::ShouldTearDownForTrickplay() +{ +#if defined(__APPLE__) || defined(UBUNTU) + return true; +#endif + return false; +} +bool DefaultSocInterface::IsSimulatorVideoSample() +{ +#if defined(__APPLE__) + return true; +#endif + return true; +} +void DefaultSocInterface::SetH264Caps(GstCaps *caps) +{ +#ifdef UBUNTU + // below required on Ubuntu - harmless on OSX, but breaks RPI + gst_caps_set_simple (caps, + "alignment", G_TYPE_STRING, "au", + "stream-format", G_TYPE_STRING, "avc", + NULL); +#endif +} +void DefaultSocInterface::SetHevcCaps(GstCaps *caps) +{ +#ifdef UBUNTU + // below required on Ubuntu - harmless on OSX, but breaks RPI +gst_caps_set_simple(caps, + "alignment", G_TYPE_STRING, "au", + "stream-format", G_TYPE_STRING, "hev1", + NULL); +#endif +} +void SocInterface::SetDecodeError(GstObject* src) +{ + g_object_set(src, "report_decode_errors", TRUE, NULL); +} +void SocInterface::SetWesterosSinkState(bool status) +{ + mUsingWesterosSink = true; +} +long long SocInterface::GetVideoPts(GstElement *video_sink, GstElement *video_dec, bool isWesteros) +{ + gint64 currentPTS = 0; + GstElement *element; + element = video_dec; + if(element) + { + g_object_get(element, "video-pts", ¤tPTS, NULL);/* Gets the 'video-pts' from the element into the currentPTS */ + if(!isWesteros) + { + currentPTS = currentPTS * 2; + } + } + return (long long)currentPTS; +} +bool SocInterface::StartsWith( const char *inputStr, const char *prefix ) +{ + bool rc = true; + while( *prefix ) + { + if( *inputStr++ != *prefix++ ) + { + rc = false; + break; + } + } + return rc; +} +bool DefaultSocInterface::ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) +{ + bool status = false; + if (StartsWith(GST_OBJECT_NAME(src), "amlhalasink") == true) + { + gst_object_replace((GstObject **)audio_sink, src); + g_object_set(audio_sink, "disable-xrun", TRUE, NULL); + status = true; + } + return status; +} + +bool SocInterface::IsVideoMaster(GstElement *videoSink) +{ + return true; +} + +/** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @param isRialto True if rialtosink is used. + * @return True if the playback rate was set successfully, false otherwise. + */ +bool DefaultSocInterface::SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) +{ + return false; +} + +/** + * @brief Configure Capability Acceptance for GStreamer Transform + * + * Sets up the accept_caps function pointer for a GStreamer base transform class. + * This allows the transform element to decide whether it can accept a given set of capabilities (caps), + * which is essential for negotiating media formats during pipeline setup. + * + * @param base_transform_class Pointer to the GStreamer base transform class to configure. + * @param accept_caps_func Function used to determine if the transform accepts specific caps. + */ +void SocInterface::ConfigureAcceptCaps(GstBaseTransformClass* base_transform_class , + AcceptCapsFunc accept_caps_func) +{ +} + +bool DefaultSocInterface::IsVideoMaster(GstElement *videoSink) +{ + return true; +} diff --git a/middleware/test/utests/fakes/FakeSocUtils.cpp b/middleware/test/utests/fakes/FakeSocUtils.cpp new file mode 100644 index 000000000..daf88972a --- /dev/null +++ b/middleware/test/utests/fakes/FakeSocUtils.cpp @@ -0,0 +1,70 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SocUtils.h" + +namespace SocUtils +{ + void InitializePlatformConfigs() + { + } + bool UseAppSrcForProgressivePlayback( void ) + { + return false; + } + bool IsSupportedAC4( void ) + { + return false; + } + bool UseWesterosSink( void ) + { + return false; + } + bool IsAudioFragmentSyncSupported( void ) + { + return false; + } + bool EnableLiveLatencyCorrection( void ) + { + return false; + } + bool DisableAC3( void ) + { + return false; + } + + bool IsSupportedAC3() + { + return false; + } + + int RequiredQueuedFrames( void ) + { + return 0; + } + bool EnablePTSRestamp(void) + { + return false; + } + + bool ResetNewSegmentEvent() + { + return false; + } +} diff --git a/middleware/test/utests/fakes/FakeTextStyleAttributes.cpp b/middleware/test/utests/fakes/FakeTextStyleAttributes.cpp new file mode 100644 index 000000000..3980268d3 --- /dev/null +++ b/middleware/test/utests/fakes/FakeTextStyleAttributes.cpp @@ -0,0 +1,30 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TextStyleAttributes.h" + +TextStyleAttributes::TextStyleAttributes() +{ +} + +int TextStyleAttributes::getAttributes(std::string options, attributesType &attributesValues, + uint32_t &attributesMask) +{ + return 0; +} diff --git a/middleware/test/utests/fakes/Fakeopencdm.cpp b/middleware/test/utests/fakes/Fakeopencdm.cpp new file mode 100644 index 000000000..8ee2b3145 --- /dev/null +++ b/middleware/test/utests/fakes/Fakeopencdm.cpp @@ -0,0 +1,124 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "open_cdm.h" +#include "open_cdm_adapter.h" +#include "MockOpenCdm.h" + +MockOpenCdm *g_mockopencdm = nullptr; + +struct OpenCDMSystem* opencdm_create_system(const char keySystem[]) +{ + OpenCDMSystem *system = nullptr; + if (g_mockopencdm != nullptr) + { + system = g_mockopencdm->opencdm_create_system(keySystem); + } + return system; +} + +OpenCDMError opencdm_destruct_system(struct OpenCDMSystem* system) +{ + OpenCDMError ret = ERROR_NONE; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_destruct_system(system); + } + return ret; +} + +OpenCDMError opencdm_construct_session(struct OpenCDMSystem* system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], const uint16_t initDataLength, + const uint8_t CDMData[], const uint16_t CDMDataLength, OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session) +{ + OpenCDMError ret = ERROR_NONE; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_construct_session(system, licenseType, initDataType, initData, initDataLength, CDMData, CDMDataLength, callbacks, userData, session); + } + return ret; +} + +KeyStatus opencdm_session_status(const struct OpenCDMSession* session, + const uint8_t keyId[], const uint8_t length) +{ + KeyStatus ret = KeyStatus::Usable; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_session_status(session, keyId, length); + } + return ret; +} + +OpenCDMError opencdm_session_update(struct OpenCDMSession* session, + const uint8_t keyMessage[], + const uint16_t keyLength) +{ + OpenCDMError ret = ERROR_NONE; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_session_update(session, keyMessage, keyLength); + } + return ret; +} + +OpenCDMError opencdm_session_close(struct OpenCDMSession* session) +{ + OpenCDMError ret = ERROR_NONE; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_session_close(session); + } + return ret; +} + +OpenCDMError opencdm_destruct_session(struct OpenCDMSession* session) +{ + OpenCDMError ret = ERROR_NONE; + if (g_mockopencdm != nullptr) + { + ret = g_mockopencdm->opencdm_destruct_session(session); + } + return ret; +} + +OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, GstBuffer* buffer, + GstBuffer* subSample, const uint32_t subSampleCount, + GstBuffer* IV, GstBuffer* keyID, + uint32_t initWithLast15) +{ + return ERROR_NONE; +} + +OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, const uint8_t* IV, + uint16_t IVLength, const uint8_t* keyId, + const uint16_t keyIdLength, uint32_t initWithLast15) +{ + if (g_mockopencdm != nullptr) + { + return g_mockopencdm->opencdm_session_decrypt(session, encrypted, encryptedLength, + encScheme, pattern, IV, IVLength, keyId, + keyIdLength, initWithLast15); + } + return ERROR_NONE; +} diff --git a/middleware/test/utests/fakes/Fakeopencdmsessionadapter.cpp b/middleware/test/utests/fakes/Fakeopencdmsessionadapter.cpp new file mode 100644 index 000000000..52a7e395d --- /dev/null +++ b/middleware/test/utests/fakes/Fakeopencdmsessionadapter.cpp @@ -0,0 +1,70 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "opencdmsessionadapter.h" +#include "DrmData.h" +#include "DrmSession.h" +#include "MockOpenCdmSessionAdapter.h" + +MockOpenCdmSessionAdapter *g_mockOpenCdmSessionAdapter = nullptr; +std::vector g_mockKeyId{1,2,3,4,5,6,7,8,9,0,1,2,3,4}; + +OCDMSessionAdapter::OCDMSessionAdapter(std::shared_ptr drmHelper, DrmCallbacks *callbacks) : + DrmSession("ocdmkeysystem"), m_keyId{g_mockKeyId}, m_drmHelper{drmHelper} +{ +} + +OCDMSessionAdapter::~OCDMSessionAdapter() +{} +bool OCDMSessionAdapter::verifyOutputProtection() +{ + bool ret_val = true; + if (g_mockOpenCdmSessionAdapter != nullptr) + { + ret_val = g_mockOpenCdmSessionAdapter->verifyOutputProtection(); + } + return ret_val; +} + +void OCDMSessionAdapter::generateDRMSession(const uint8_t *f_pbInitData, uint32_t f_cbInitData, std::string &customData) +{ + +} + +DrmData * OCDMSessionAdapter::generateKeyRequest(string& destinationURL, uint32_t timeout) +{ + return nullptr; +} + +int OCDMSessionAdapter::processDRMKey(DrmData* key, uint32_t timeout) +{ + return 0; +} + +KeyState OCDMSessionAdapter::getState() +{ + return KEY_INIT; +} +void OCDMSessionAdapter::clearDecryptContext() +{ +} +bool OCDMSessionAdapter::waitForState(KeyState state, const uint32_t timeout) +{ + return true; +} diff --git a/middleware/test/utests/mocks/MockDrmHelper.h b/middleware/test/utests/mocks/MockDrmHelper.h new file mode 100644 index 000000000..06e2b0986 --- /dev/null +++ b/middleware/test/utests/mocks/MockDrmHelper.h @@ -0,0 +1,85 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef PLAYER_MOCK_DRM_HELPER_H +#define PLAYER_MOCK_DRM_HELPER_H + +#include +#include "DrmHelper.h" + +class MockDrmHelper : public DrmHelper +{ +public: + + MockDrmHelper() : DrmHelper(DrmInfo{}) + { + + } + + MOCK_METHOD(const std::string&, ocdmSystemId, (), (const, override)); + + MOCK_METHOD(void, createInitData, (std::vector& initData), (const, override)); + + MOCK_METHOD(bool, parsePssh, (const uint8_t* initData, uint32_t initDataLen), (override)); + + MOCK_METHOD(bool, isClearDecrypt, (), (const, override)); + + MOCK_METHOD(bool, isHdcp22Required, (), (const, override)); + + MOCK_METHOD(const std::string&, getDrmMetaData, (), (const, override)); + + MOCK_METHOD(void, setDrmMetaData, (const std::string& metaData), (override)); + + MOCK_METHOD(void, setDefaultKeyID, (const std::string& cencData), (override)); + + MOCK_METHOD(int, getDrmCodecType, (), (const, override)); + + MOCK_METHOD(uint32_t, licenseGenerateTimeout, (), (const, override)); + + MOCK_METHOD(uint32_t, keyProcessTimeout, (), (const, override)); + + MOCK_METHOD(void, getKey, (std::vector& keyID), (const, override)); + + MOCK_METHOD(void, getKeys, ((std::map>)& keyIDs), (const, override)); + + MOCK_METHOD(const std::string&, getUuid, (), (const, override)); + + MOCK_METHOD(bool, isExternalLicense, (), (const, override)); + + MOCK_METHOD(void, generateLicenseRequest, (const ChallengeInfo& challengeInfo, LicenseRequest& licenseRequest), (const, override)); + + MOCK_METHOD(void, transformLicenseResponse, (std::shared_ptr licenseResponse), (const, override)); + + MOCK_METHOD(DRMMemorySystem*, getMemorySystem, (), (override)); + + MOCK_METHOD(void, cancelDrmSession, (), (override)); + + MOCK_METHOD(bool, canCancelDrmSession, (), (override)); + + MOCK_METHOD(const std::string&, friendlyName, (), (const, override)); + + MOCK_METHOD(void, setOutputProtectionFlag, (bool bValue)); + + MOCK_METHOD(bool, isDecryptClearSamplesRequired,()); + + +}; + + +#endif /* PLAYER_MOCK_CONFIG_H */ diff --git a/middleware/test/utests/mocks/MockDrmMemorySystem.h b/middleware/test/utests/mocks/MockDrmMemorySystem.h new file mode 100644 index 000000000..65755bcf7 --- /dev/null +++ b/middleware/test/utests/mocks/MockDrmMemorySystem.h @@ -0,0 +1,43 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef DRM_MOCK_MEMORY_SYSTEM_H +#define DRM_MOCK_MEMORY_SYSTEM_H + +#include +#include "DrmMemorySystem.h" + +class MockDrmMemorySystem : public DRMMemorySystem +{ +public: + + MockDrmMemorySystem() : DRMMemorySystem() + { + + } + + MOCK_METHOD(bool, encode, (const uint8_t *dataIn, uint32_t dataInSz, std::vector& dataOut), (override)); + + MOCK_METHOD(bool, decode, (const uint8_t* dataIn, uint32_t dataInSz, uint8_t *dataOut, uint32_t dataOutSz), (override)); + + MOCK_METHOD(void, terminateEarly, ()); +}; + + +#endif /* DRM_MOCK_MEMORY_SYSTEM_H */ diff --git a/middleware/test/utests/mocks/MockDrmSessionManager.h b/middleware/test/utests/mocks/MockDrmSessionManager.h new file mode 100644 index 000000000..4d23f961e --- /dev/null +++ b/middleware/test/utests/mocks/MockDrmSessionManager.h @@ -0,0 +1,33 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef PLAYER_MOCK_DRM_SESSION_MANAGER_H +#define PLAYER_MOCK_DRM_SESSION_MANAGER_H + +#include +#include "DrmSessionManager.h" + +class MockDRMSessionManager +{ +public: + MOCK_METHOD(void, setVideoWindowSize, (int width, int height)); +}; + +extern MockDRMSessionManager *g_mockDRMSessionManager; + +#endif /* PLAYER_MOCK_DRM_SESSION_MANAGER_H */ diff --git a/middleware/test/utests/mocks/MockGLib.h b/middleware/test/utests/mocks/MockGLib.h new file mode 100644 index 000000000..44e05eede --- /dev/null +++ b/middleware/test/utests/mocks/MockGLib.h @@ -0,0 +1,60 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAMP_MOCK_GLIB_H +#define AAMP_MOCK_GLIB_H + +#include +#include +#include +#include + + +class MockGLib +{ +public: + MOCK_METHOD(GParamSpec*, g_object_class_find_property, (GObjectClass* oclass, const gchar* property_name)); + MOCK_METHOD(guint, g_timeout_add, (guint interval, GSourceFunc function, gpointer data)); + MOCK_METHOD(gboolean, g_source_remove, (guint tag)); + MOCK_METHOD(gpointer, g_malloc, (gsize n_bytes)); + MOCK_METHOD(void, g_free, (gpointer mem)); + MOCK_METHOD(gpointer, g_realloc, (gpointer mem, gsize n_bytes)); + + MOCK_METHOD(void, g_object_set, (gpointer object, const gchar *property_name, int value)); + MOCK_METHOD(void, g_object_set, (gpointer object, const gchar *property_name, char * value)); + MOCK_METHOD(void, g_object_set, (gpointer object, const gchar *property_name, double value)); + MOCK_METHOD(void, g_object_set, (gpointer object, const gchar *property_name, GstStructure *value)); + MOCK_METHOD(void, g_object_set, (gpointer object, const gchar *property_name, gpointer value)); + + MOCK_METHOD(gulong, g_signal_connect_data, (gpointer instance, const gchar *detailed_signal, GCallback c_handler, + gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags)); + MOCK_METHOD(gboolean, g_type_check_instance_is_a, (gpointer instance, GType iface_type)); + MOCK_METHOD(gboolean, g_signal_handler_is_connected, (gpointer instance, gulong handler_id)); + MOCK_METHOD(gboolean, g_signal_handler_disconnect, (gpointer instance, gulong handler_id)); + MOCK_METHOD(void, g_object_get, (gpointer object, const gchar *first_property_name, GstStructure *structure)); + MOCK_METHOD(void, g_object_get, (gpointer object, const gchar *first_property_name, uint *value)); + MOCK_METHOD(void, g_object_get, (gpointer object, const gchar *first_property_name, gpointer *value)); + MOCK_METHOD(void, g_object_get, (gpointer object, const gchar *first_property_name, gint64 *value)); + + +}; + +extern MockGLib *g_mockGLib; + +#endif /* AAMP_MOCK_GLIB_H */ diff --git a/middleware/test/utests/mocks/MockGStreamer.h b/middleware/test/utests/mocks/MockGStreamer.h new file mode 100644 index 000000000..b1db26c9b --- /dev/null +++ b/middleware/test/utests/mocks/MockGStreamer.h @@ -0,0 +1,101 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLAYER_MOCK_GSTREAMER_H +#define PLAYER_MOCK_GSTREAMER_H + +#include +#include +#include +#include + +class MockGStreamer +{ +public: + MOCK_METHOD(GstCaps *, gst_caps_new_simple, + (const char *media_type, const char *fieldname, GType var, const int val, + void *ptr)); + MOCK_METHOD(void, gst_debug_set_threshold_from_string, (const gchar *list, gboolean reset)); + MOCK_METHOD(GstElement *, gst_pipeline_new, (const gchar *name)); + MOCK_METHOD(GstBus *, gst_pipeline_get_bus, (GstPipeline *pipeline)); + MOCK_METHOD(guint , gst_bus_add_watch, (GstBus *bus, GstBusFunc func, gpointer user_data)); + MOCK_METHOD(void, gst_bus_set_sync_handler, (GstBus *bus, GstBusSyncHandler func, gpointer user_data, GDestroyNotify notify)); + MOCK_METHOD(GstQuery *, gst_query_new_position, (GstFormat format)); + MOCK_METHOD(GstStateChangeReturn, gst_element_get_state, (GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout)); + MOCK_METHOD(GstElement *, gst_element_factory_make, (const gchar *factoryname,const gchar *name)); + MOCK_METHOD(GstStateChangeReturn, gst_element_set_state,(GstElement *element, GstState state)); + MOCK_METHOD(gboolean, gst_bin_add, (GstBin *bin, GstElement *element)); + MOCK_METHOD(void, gst_object_unref,(gpointer object)); + MOCK_METHOD(void, gst_mini_object_unref,(GstMiniObject *mini_object)); + + MOCK_METHOD(GstSample *,gst_app_sink_pull_sample,(GstAppSink *appsink)); + MOCK_METHOD(GstStructure *,gst_app_sink_set_caps,(GstAppSink *appsink, const GstCaps *caps)); + MOCK_METHOD(GstStructure *,gst_caps_get_structure,(const GstCaps *caps, guint index)); + MOCK_METHOD(void, gst_message_parse_state_changed, (GstMessage * message, GstState * oldstate, GstState * newstate, GstState * pending)); + + MOCK_METHOD(gboolean, gst_object_replace, (GstObject ** oldobj, GstObject * newobj)); + MOCK_METHOD(gboolean, gst_element_send_event, (GstElement *element, GstEvent *event)); + MOCK_METHOD(GstEvent *, gst_event_new_step, (GstFormat format, guint64 amount, gdouble rate, gboolean flush, gboolean intermediate)); + MOCK_METHOD(gboolean, gst_element_query_position, (GstElement *element, GstFormat format, gint64 *cur)); + + MOCK_METHOD(void, gst_pad_remove_probe, (GstPad * pad, gulong id)); + MOCK_METHOD(gboolean, gst_bus_remove_watch, (GstBus* bus)); + MOCK_METHOD(gboolean, gst_element_seek, (GstElement *element, gdouble rate, GstFormat format, GstSeekFlags flags, + GstSeekType start_type, gint64 start, GstSeekType stop_type, gint64 stop)); + MOCK_METHOD(gboolean, gst_base_sink_is_async_enabled, (GstBaseSink * sink)); + MOCK_METHOD(void, gst_base_sink_set_async_enabled, (GstBaseSink * sink, gboolean enabled)); + MOCK_METHOD(void, gst_app_src_set_caps, (GstAppSrc *appsrc, const GstCaps *caps)); + MOCK_METHOD(void, gst_app_src_set_stream_type, (GstAppSrc *appsrc, GstAppStreamType type)); + MOCK_METHOD(gboolean, gst_element_seek_simple, (GstElement *element, GstFormat format, GstSeekFlags seek_flags,gint64 seek_pos)); + MOCK_METHOD(const GValue *, gst_structure_get_value, (const GstStructure *structure, const gchar *fieldname)); + MOCK_METHOD(guint64 ,g_value_get_uint64, (const GValue *value)); + MOCK_METHOD(gboolean,gst_element_query, (GstElement *element, GstQuery *query)); + MOCK_METHOD(void,gst_query_parse_segment, (GstQuery *query, gdouble *rate, GstFormat *format, gint64 *start_value, gint64 *stop_value)); + MOCK_METHOD(void,gst_query_parse_position, (GstQuery *query, GstFormat *format, gint64 *cur)); + MOCK_METHOD(GstQuery *, gst_query_new_duration, (GstFormat format)); + MOCK_METHOD(void,gst_query_parse_duration, (GstQuery *query, GstFormat *format, gint64 *duration)); + MOCK_METHOD(GstBuffer *, gst_buffer_new, ()); + MOCK_METHOD(gboolean, gst_buffer_copy_into, (GstBuffer *dest, GstBuffer *src, GstBufferCopyFlags flags, gsize offset, gsize size)); + MOCK_METHOD(GstFlowReturn, gst_app_src_push_buffer, (GstAppSrc *appsrc, GstBuffer *buffer)); + MOCK_METHOD(GstBuffer *, gst_buffer_new_wrapped, (gpointer data, gsize size)); + MOCK_METHOD(GstEvent *, gst_event_new_protection, (const gchar *system_id, GstBuffer *data, const gchar *origin)); + MOCK_METHOD(guint64, gst_app_src_get_current_level_bytes, (GstAppSrc *appsrc)); + MOCK_METHOD(GstStructure *, gst_structure_new, ()); + MOCK_METHOD(GstPluginFeature *, gst_registry_lookup_feature, (GstRegistry *registry, const char *name)); + MOCK_METHOD(GstPad*, gst_element_get_static_pad, (GstElement *element, const gchar *name)); + MOCK_METHOD(gboolean, gst_pad_push_event, (GstPad* pad, GstEvent* event), ()); + MOCK_METHOD(void, gst_segment_init, (GstSegment *segment, GstFormat format)); + MOCK_METHOD(GstEvent *, gst_event_new_segment, (GstSegment *segment)); + MOCK_METHOD(GstEvent*, gst_event_new_custom, (GstEventType type, GstStructure* structure), ()); + + /* +gst_app_sink_get_type +gst_app_sink_pull_sample +gst_app_sink_set_caps +gst_caps_get_structure +gst_sample_get_buffer +gst_sample_get_caps +gst_structure_get_int + */ +}; + +extern MockGStreamer *g_mockGStreamer; + +#endif /* PLAYER_MOCK_GSTREAMER_H */ diff --git a/middleware/test/utests/mocks/MockGstHandlerControl.h b/middleware/test/utests/mocks/MockGstHandlerControl.h new file mode 100644 index 000000000..03704238e --- /dev/null +++ b/middleware/test/utests/mocks/MockGstHandlerControl.h @@ -0,0 +1,35 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef GST_MOCK_GST_HANDLER_CONTROL_H +#define GST_MOCK_GST_HANDLER_CONTROL_H + +#include +#include "GstHandlerControl.h" + +class MockGstHandlerControl +{ +public: + + MOCK_METHOD(bool, isEnabled, ()); +}; + +extern MockGstHandlerControl *g_mockGstHandlerControl; + +#endif /* GST_MOCK_GST_HANDLER_CONTROL_H */ diff --git a/middleware/test/utests/mocks/MockGstUtils.h b/middleware/test/utests/mocks/MockGstUtils.h new file mode 100644 index 000000000..602efa5ae --- /dev/null +++ b/middleware/test/utests/mocks/MockGstUtils.h @@ -0,0 +1,31 @@ +#ifndef MOCK_GST_UTILS_H +#define MOCK_GST_UTILS_H + +#include +#include "GstUtils.h" // Includes the enums and function signatures + +// Namespace or class-based mocks depending on usage +// If functions are global, we need to wrap them in a mockable interface. + +class IGstUtils { +public: + virtual ~IGstUtils() = default; + + virtual const char* gstGetMediaTypeName(GstMediaType mediaType) = 0; + virtual void PlayerCliGstInit(int* argc, char*** argv) = 0; + virtual void PlayerCliGstTerm() = 0; + virtual GstCaps* GetCaps(GstStreamOutputFormat format) = 0; + virtual long long GetCurrentTimeMS() = 0; +}; + +// Google Mock implementation +class MockGstUtils : public IGstUtils { +public: + MOCK_METHOD(const char*, gstGetMediaTypeName, (GstMediaType mediaType), (override)); + MOCK_METHOD(void, PlayerCliGstInit, (int* argc, char*** argv), (override)); + MOCK_METHOD(void, PlayerCliGstTerm, (), (override)); + MOCK_METHOD(GstCaps*, GetCaps, (GstStreamOutputFormat format), (override)); + MOCK_METHOD(long long, GetCurrentTimeMS, (), (override)); +}; +extern MockGstUtils *g_mockGstUtils; +#endif // MOCK_GST_UTILS_H \ No newline at end of file diff --git a/middleware/test/utests/mocks/MockOpenCdm.h b/middleware/test/utests/mocks/MockOpenCdm.h new file mode 100644 index 000000000..a551448ed --- /dev/null +++ b/middleware/test/utests/mocks/MockOpenCdm.h @@ -0,0 +1,56 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef PLAYER_MOCK_OPEN_CDM_H +#define PLAYER_MOCK_OPEN_CDM_H + +#include "open_cdm.h" +#include + +struct _GstBuffer; +typedef struct _GstBuffer GstBuffer; + +class MockOpenCdm +{ +public: + MOCK_METHOD(OpenCDMError, opencdm_session_decrypt, + (struct OpenCDMSession * session, uint8_t encrypted[], + const uint32_t encryptedLength, const EncryptionScheme encScheme, + const EncryptionPattern pattern, const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, uint32_t initWithLast15)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_system, (struct OpenCDMSystem* system)); + MOCK_METHOD(OpenCDMSystem*, opencdm_create_system, (const char keySystem[])); + MOCK_METHOD(OpenCDMError, opencdm_construct_session, + (struct OpenCDMSystem * system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], const uint16_t initDataLength, + const uint8_t CDMData[], const uint16_t CDMDataLength, + OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session)); + MOCK_METHOD(OpenCDMError, opencdm_session_update, + (struct OpenCDMSession * session, const uint8_t keyMessage[], + const uint16_t keyLength)); + MOCK_METHOD(OpenCDMError, opencdm_session_close, (struct OpenCDMSession* session)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_session, (struct OpenCDMSession* session)); + MOCK_METHOD(KeyStatus, opencdm_session_status, (const struct OpenCDMSession* session, const uint8_t keyId[], const uint8_t length)); + +}; + +extern MockOpenCdm* g_mockopencdm; + +#endif /* PLAYER_MOCK_OPEN_CDM_H */ diff --git a/middleware/test/utests/mocks/MockOpenCdmSessionAdapter.h b/middleware/test/utests/mocks/MockOpenCdmSessionAdapter.h new file mode 100644 index 000000000..f9a001197 --- /dev/null +++ b/middleware/test/utests/mocks/MockOpenCdmSessionAdapter.h @@ -0,0 +1,36 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef PLAYER_MOCK_OPEN_CDM_SESSION_ADAPTER_H +#define PLAYER_MOCK_OPEN_CDM_SESSION_ADAPTER_H + +#include + +extern std::vector g_mockKeyId; +class MockOpenCdmSessionAdapter +{ + public: + + MOCK_METHOD(bool, verifyOutputProtection, ()); + +}; + +extern MockOpenCdmSessionAdapter *g_mockOpenCdmSessionAdapter; + +#endif /* PLAYER_MOCK_OPEN_CDM_SESSION_ADAPTER_H */ diff --git a/middleware/test/utests/mocks/MockPlayerConfig.h b/middleware/test/utests/mocks/MockPlayerConfig.h new file mode 100644 index 000000000..76629ec41 --- /dev/null +++ b/middleware/test/utests/mocks/MockPlayerConfig.h @@ -0,0 +1,53 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef PLAYER_MOCK_AAMP_CONFIG_H +#define PLAYER_MOCK_AAMP_CONFIG_H + +#include +#include "AampConfig.h" + +class MockAampConfig +{ +public: + MOCK_METHOD(void, SetConfigValue, (AAMPConfigSettingBool cfg, const bool &value)); + MOCK_METHOD(void, SetConfigValue, (AAMPConfigSettingInt cfg, const int &value)); + MOCK_METHOD(void, SetConfigValue, (AAMPConfigSettingFloat cfg, const double &value)); + MOCK_METHOD(void, SetConfigValue, (AAMPConfigSettingString cfg, const std::string &value)); + + MOCK_METHOD(bool, IsConfigSet, (AAMPConfigSettingBool cfg)); + MOCK_METHOD(int, GetConfigValue, (AAMPConfigSettingInt cfg)); + MOCK_METHOD(double, GetConfigValue, (AAMPConfigSettingFloat cfg)); + MOCK_METHOD(std::string, GetConfigValue, (AAMPConfigSettingString cfg)); + + MOCK_METHOD(ConfigPriority, GetConfigOwner, (AAMPConfigSettingBool cfg)); + MOCK_METHOD(ConfigPriority, GetConfigOwner, (AAMPConfigSettingInt cfg)); + MOCK_METHOD(ConfigPriority, GetConfigOwner, (AAMPConfigSettingFloat cfg)); + MOCK_METHOD(ConfigPriority, GetConfigOwner, (AAMPConfigSettingString cfg)); + + MOCK_METHOD(void, RestoreConfiguration, (ConfigPriority owner, AAMPConfigSettingBool cfg)); + MOCK_METHOD(void, RestoreConfiguration, (ConfigPriority owner, AAMPConfigSettingInt cfg)); + MOCK_METHOD(void, RestoreConfiguration, (ConfigPriority owner, AAMPConfigSettingFloat cfg)); + MOCK_METHOD(void, RestoreConfiguration, (ConfigPriority owner, AAMPConfigSettingString cfg)); + +}; + +extern MockAampConfig *g_mockAampConfig; + +#endif /* PLAYER_MOCK_AAMP_CONFIG_H */ diff --git a/middleware/test/utests/mocks/MockPlayerJsonObject.h b/middleware/test/utests/mocks/MockPlayerJsonObject.h new file mode 100644 index 000000000..96c691ae3 --- /dev/null +++ b/middleware/test/utests/mocks/MockPlayerJsonObject.h @@ -0,0 +1,49 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2025 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#pragma once + +#include +#include "PlayerJsonObject.h" + +class MockPlayerJsonObject : public PlayerJsonObject +{ +public: + MOCK_METHOD(bool, add, (const std::string& name, const std::string& value, const ENCODING encoding)); + MOCK_METHOD(bool, add, (const std::string& name, const char *value, const ENCODING encoding)); + MOCK_METHOD(bool, add, (const std::string& name, const std::vector& values)); + MOCK_METHOD(bool, add, (const std::string& name, const std::vector& values, const ENCODING encoding)); + MOCK_METHOD(bool, add, (const std::string& name, PlayerJsonObject& value)); + MOCK_METHOD(bool, add, (const std::string& name, std::vector& values)); + MOCK_METHOD(bool, add, (const std::string& name, cJSON *value)); + MOCK_METHOD(bool, add, (const std::string& name, bool value)); + MOCK_METHOD(bool, add, (const std::string& name, int value)); + MOCK_METHOD(bool, add, (const std::string& name, long value)); + MOCK_METHOD(bool, add, (const std::string& name, double value)); + + MOCK_METHOD(bool, set, (PlayerJsonObject *parent, cJSON *object)); + + MOCK_METHOD(bool, get, (const std::string& name, PlayerJsonObject &value)); + MOCK_METHOD(bool, get, (const std::string& name, std::string& value)); + MOCK_METHOD(bool, get, (const std::string& name, int& value)); + MOCK_METHOD(bool, get, (const std::string& name, std::vector& values)); + MOCK_METHOD(bool, get, (const std::string& name, std::vector& values, const ENCODING encoding)); +}; + +extern std::shared_ptr g_mockPlayerJsonObject; diff --git a/middleware/test/utests/mocks/MockPlayerScheduler.h b/middleware/test/utests/mocks/MockPlayerScheduler.h new file mode 100644 index 000000000..2735ad192 --- /dev/null +++ b/middleware/test/utests/mocks/MockPlayerScheduler.h @@ -0,0 +1,36 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MOCK_PLAYER_SCHEDULER_H +#define MOCK_PLAYER_SCHEDULER_H + +#include +#include "PlayerScheduler.h" + +class MockPlayerScheduler +{ +public: + + MOCK_METHOD(int, ScheduleTask, (PlayerAsyncTaskObj obj)); + +}; + +extern MockPlayerScheduler *g_mockPlayerScheduler; + +#endif /* PLAYER_MOCK_PLAYER_SCHEDULER_H */ diff --git a/middleware/test/utests/mocks/MockPlayerUtils.h b/middleware/test/utests/mocks/MockPlayerUtils.h new file mode 100644 index 000000000..bf618d8ac --- /dev/null +++ b/middleware/test/utests/mocks/MockPlayerUtils.h @@ -0,0 +1,33 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef MOCK_PLAYER_UTILS_H +#define MOCK_PLAYER_UTILS_H + +#include +class MockPlayerUtils +{ +public: + + MOCK_METHOD(long long, GetCurrentTimeMS, ()); +}; + +extern MockPlayerUtils *g_mockPlayerUtils; +#endif + diff --git a/middleware/test/utests/mocks/MockSocInterface.h b/middleware/test/utests/mocks/MockSocInterface.h new file mode 100644 index 000000000..92f8b75d8 --- /dev/null +++ b/middleware/test/utests/mocks/MockSocInterface.h @@ -0,0 +1,38 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PLAYER_MOCK_SOC_INTERFACE_H +#define PLAYER_MOCK_SOC_INTERFACE_H + +#include +#include +#include +#include + +class MockSocInterface +{ +public: + MOCK_METHOD(bool, IsPlaybackQualityFromSink, (), (const)); + +}; + +extern MockSocInterface *g_mockSocInterface; + +#endif /* PLAYER_MOCK_SOC_INTERFACE_H */ + diff --git a/middleware/test/utests/mocks/opencdmMocks.cpp b/middleware/test/utests/mocks/opencdmMocks.cpp new file mode 100755 index 000000000..f03ecc124 --- /dev/null +++ b/middleware/test/utests/mocks/opencdmMocks.cpp @@ -0,0 +1,103 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "opencdmMocks.h" + +MockOpenCdm *g_mockOpenCdm; + +struct OpenCDMSystem* opencdm_create_system(const char keySystem[]) +{ + return g_mockOpenCdm->opencdm_create_system(keySystem); +} + +OpenCDMError opencdm_construct_session(struct OpenCDMSystem* system, + const LicenseType licenseType, + const char initDataType[], + const uint8_t initData[], + const uint16_t initDataLength, + const uint8_t CDMData[], + const uint16_t CDMDataLength, + OpenCDMSessionCallbacks* callbacks, + void* userData, + struct OpenCDMSession** session) +{ + return g_mockOpenCdm->opencdm_construct_session(system, + licenseType, + initDataType, + initData, + initDataLength, + CDMData, + CDMDataLength, + callbacks, + userData, + session); +} + +OpenCDMError opencdm_destruct_system(struct OpenCDMSystem* system) +{ + return g_mockOpenCdm->opencdm_destruct_system(system); +} + +KeyStatus opencdm_session_status(const struct OpenCDMSession* session, + const uint8_t keyId[], + const uint8_t length) +{ + return g_mockOpenCdm->opencdm_session_status(session, keyId, length); +} + +OpenCDMError opencdm_session_update(struct OpenCDMSession* session, + const uint8_t keyMessage[], + const uint16_t keyLength) +{ + return g_mockOpenCdm->opencdm_session_update(session, keyMessage, keyLength); +} + +OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, + GstBuffer* buffer, + GstBuffer* subSample, + const uint32_t subSampleCount, + GstBuffer* IV, + GstBuffer* keyID, + uint32_t initWithLast15) +{ + return g_mockOpenCdm->opencdm_gstreamer_session_decrypt(session, buffer, subSample, subSampleCount, IV, keyID,initWithLast15); +} + +OpenCDMError opencdm_session_decrypt( struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const uint8_t* IV, + uint16_t IVLength, + const uint8_t* keyId, + const uint16_t keyIdLength, + uint32_t initWithLast15, + uint8_t* streamInfo, + uint16_t streamInfoLength) +{ + return g_mockOpenCdm->opencdm_session_decrypt(session, encrypted, encryptedLength, IV, IVLength, keyId, keyIdLength, initWithLast15, streamInfo, streamInfoLength); +} + +OpenCDMError opencdm_session_close(struct OpenCDMSession* session) +{ + return g_mockOpenCdm->opencdm_session_close(session); +} + +OpenCDMError opencdm_destruct_session(struct OpenCDMSession* session) +{ + return ERROR_NONE; +} diff --git a/middleware/test/utests/mocks/opencdmMocks.h b/middleware/test/utests/mocks/opencdmMocks.h new file mode 100755 index 000000000..fddcfe20f --- /dev/null +++ b/middleware/test/utests/mocks/opencdmMocks.h @@ -0,0 +1,70 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2022 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAMP_OPENCDM_MOCKS +#define AAMP_OPENCDM_MOCKS + +#include "open_cdm.h" +#include "open_cdm_adapter.h" +#include +#include + +class MockOpenCdm +{ + public: + MOCK_METHOD(struct OpenCDMSystem *, opencdm_create_system, (const char keySystem[])); + MOCK_METHOD(OpenCDMError, opencdm_construct_session, (struct OpenCDMSystem* system, + const LicenseType licenseType, + const char initDataType[], + const uint8_t initData[], + const uint16_t initDataLength, + const uint8_t CDMData[], + const uint16_t CDMDataLength, + OpenCDMSessionCallbacks* callbacks, + void* userData, + struct OpenCDMSession** session)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_system, (struct OpenCDMSystem* system)); + MOCK_METHOD(KeyStatus, opencdm_session_status, (const struct OpenCDMSession* session, + const uint8_t keyId[], + const uint8_t length)); + MOCK_METHOD(OpenCDMError, opencdm_session_update, (struct OpenCDMSession* session, + const uint8_t keyMessage[], + const uint16_t keyLength)); + MOCK_METHOD(OpenCDMError, opencdm_gstreamer_session_decrypt, (struct OpenCDMSession* session, + GstBuffer* buffer, + GstBuffer* subSample, + const uint32_t subSampleCount, + GstBuffer* IV, + GstBuffer* keyID, + uint32_t initWithLast15)); + MOCK_METHOD(OpenCDMError, opencdm_session_decrypt, (struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const uint8_t* IV, + uint16_t IVLength, + const uint8_t* keyId, + const uint16_t keyIdLength, + uint32_t initWithLast15, + uint8_t* streamInfo, + uint16_t streamInfoLength)); + MOCK_METHOD(OpenCDMError, opencdm_session_close, (struct OpenCDMSession* session)); + MOCK_METHOD(OpenCDMError, opencdm_destruct_session, (struct OpenCDMSession* session)); +}; + +#endif /* AAMP_OPENCDM_MOCKS */ diff --git a/middleware/test/utests/ocdm/open_cdm.h b/middleware/test/utests/ocdm/open_cdm.h new file mode 100644 index 000000000..0054ede53 --- /dev/null +++ b/middleware/test/utests/ocdm/open_cdm.h @@ -0,0 +1,639 @@ +/* + * Copyright 2016-2017 TATA ELXSI + * Copyright 2016-2017 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OPEN_CDM_H +#define OPEN_CDM_H + +// WPEWebkit implementation is using the following header file to integrate +// their solution with +// DRM systems (PlayReady/WideVine/ClearKey). +// The implementation behind this class/interface exists in two flavors. +// 1) Fraunhofers adapted reference implementation, based on SUNRPC +// 2) Metrologicals framework, based on their proprietary RPC mechanism. +// +// The second option exists because during testing the reference/adapted +// implementation of Frauenhofer +// it was observed: +// - Older implementations of ucLibc had different dynamic characteristics that +// caused deadlocks +// - Message exchange over the SUNRPC mechanism is using continous heap memory +// allocations/deallocactions, +// leading to a higher risk of memory fragmentation. +// - SUNRPC only works on UDP/TCP, given the nature and size of the messages, +// UDP was not an option so TCP +// is used. There is no domain socket implementation for the SUNRPC mechanism. +// Domain sockets transfer +// data, as an average, twice as fast as TCP sockets. +// - SUNRPC requires an external process (bind) to do program number lookup to +// TCP port connecting. Currently +// the Frauenhofer OpenCDMi reference implementation is the only +// implementation requiring this service on +// most deplyments with the WPEWebkit. +// - Common Vulnerabilities and Exposures (CVE's) have been reported with the +// SUNRPC that have not been resolved +// on most platforms where the solution is deployed. +// - The Metrological RPC mechanism allows for a configurable in or out of +// process deplyment of the OpenCDMi +// deployment without rebuilding. +// +// So due to performance and security exploits it was decided to offer a second +// implementation of the OpenCDMi +// specification that did notrequire to change the WPEWebKit + +#include +#include + +#include +#include + +#ifndef EXTERNAL +#ifdef _MSVC_LANG +#ifdef OCDM_EXPORTS +#define EXTERNAL __declspec(dllexport) +#else +#define EXTERNAL __declspec(dllimport) +#endif +#else +#define EXTERNAL __attribute__ ((visibility ("default"))) +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WINDOWS) + #if !defined(OCDM_EXPORTS) + #pragma comment(lib, "ocdm.lib") + #endif + + /** + * Sometimes the compiler would like to be smart, if we do not reference + * anything here + * and you enable the rightflags, the linker drops the dependency. Than + * Proxy/Stubs do + * not get loaded, so lets make the instantiation of the ProxyStubs explicit !!! + */ + EXTERNAL void ForceLinkingOfOpenCDM(); +#endif + +#define SESSION_ID_LEN 16 +#define MAX_NUM_SECURE_STOPS 8 + +/** + * Represents an OCDM system + */ +struct OpenCDMSystem; + +/** + * Represents a OpenCDM session, use this one to decrypt. + */ +struct OpenCDMSession; + +typedef enum { + Temporary = 0, + PersistentUsageRecord, + PersistentLicense +} LicenseType; + +// ISO/IEC 23001-7 defines two Common Encryption Schemes with Full Sample and Subsample modes +typedef enum { + Clear = 0, + AesCtr_Cenc, // AES-CTR mode and Sub-Sample encryption + AesCbc_Cbc1, // AES-CBC mode and Sub-Sample encryption + AesCtr_Cens, // AES-CTR mode and Sub-Sample + patterned encryption + AesCbc_Cbcs // AES-CBC mode and Sub-Sample + patterned encryption + Constant IV +} EncryptionScheme; + +typedef enum +{ + MediaType_Unknown = 0, + MediaType_Video, + MediaType_Audio, + MediaType_Data +} OcdmMediaType; + +//CENC3.0 pattern is a number of encrypted blocks followed a number of clear blocks after which the pattern repeats. +typedef struct { + uint32_t encrypted_blocks; + uint32_t clear_blocks; +} EncryptionPattern; + +typedef struct { + uint16_t clear_bytes; + uint32_t encrypted_bytes; +} SubSampleInfo; + +typedef struct { + EncryptionScheme scheme; // Encryption scheme used in this sample + EncryptionPattern pattern; // Encryption Pattern used in this sample + uint8_t* iv; // Initialization vector(IV) to decrypt this sample. Can be NULL, in that case and IV of all zeros is assumed. + uint8_t ivLength; // Length of IV + uint8_t* keyId; // ID of Key required to decrypt this sample + uint8_t keyIdLength; // Length of KeyId + uint8_t subSampleCount; // Number or Sub-Samples in this sample + SubSampleInfo* subSample; // SubSample mapping - Repeating pair of Clear bytes and Encrypted Bytes representing each subsample. +} SampleInfo; + +// Provides information about the current stream +typedef struct { + uint16_t height; + uint16_t width; + OcdmMediaType media_type; +} MediaProperties; + + +/** + * Key status. + */ +typedef enum { + Usable = 0, + Expired, + Released, + OutputRestricted, + OutputRestrictedHDCP22, + OutputDownscaled, + StatusPending, + InternalError, + HWError +} KeyStatus; + +/** + * OpenCDM error code. Zero always means success. + */ +typedef enum { + ERROR_NONE = 0, + ERROR_UNKNOWN = 1, + ERROR_MORE_DATA_AVAILABLE = 2, + ERROR_INTERFACE_NOT_IMPLEMENTED = 3, + ERROR_BUFFER_TOO_SMALL = 4, + ERROR_INVALID_ACCESSOR = 0x80000001, + ERROR_KEYSYSTEM_NOT_SUPPORTED = 0x80000002, + ERROR_INVALID_SESSION = 0x80000003, + ERROR_INVALID_DECRYPT_BUFFER = 0x80000004, + ERROR_OUT_OF_MEMORY = 0x80000005, + ERROR_METHOD_NOT_IMPLEMENTED = 0x80000006, + ERROR_FAIL = 0x80004005, + ERROR_INVALID_ARG = 0x80070057, + ERROR_SERVER_INTERNAL_ERROR = 0x8004C600, + ERROR_SERVER_INVALID_MESSAGE = 0x8004C601, + ERROR_SERVER_SERVICE_SPECIFIC = 0x8004C604, + ERROR_BUSY_CANNOT_INITIALIZE = 0x8004DD00 + +} OpenCDMError; + +/** + * OpenCDM bool type. 0 is false, 1 is true. + */ +typedef enum { + OPENCDM_BOOL_FALSE = 0, + OPENCDM_BOOL_TRUE = 1 +} OpenCDMBool; + +/** + * Registered callbacks with OCDM sessions. + */ +typedef struct { + /** + * Request of process of DRM challenge data. Server is indicated by \ref url. The response of the server + * needs to be send to \ref opencdm_session_update. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param url Target URL to send challenge to. + * \param challenge Buffer containing challenge. + * \param challengeLength Length of challenge (in bytes). + */ + void (*process_challenge_callback)(struct OpenCDMSession* session, void* userData, const char url[], const uint8_t challenge[], const uint16_t challengeLength); + + /** + * Called when status of a key changes. Use \ref opencdm_session_status to find out new key status. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param keyId Buffer containing key ID. + * \param length Length of key ID buffer. + */ + void (*key_update_callback)(struct OpenCDMSession* session, void* userData, const uint8_t keyId[], const uint8_t length); + + /** + * Called when an error message is received from the DRM system + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + * \param message Text string, null terminated, from the DRM session. + */ + void (*error_message_callback)(struct OpenCDMSession* session, void* userData, const char message[]); + + /** + * Called after all known key status changes were reported. + * + * \param session The session the notification applies to. + * \param userData Pointer passed along when \ref opencdm_construct_session was issued. + */ + void (*keys_updated_callback)(const struct OpenCDMSession* session, void* userData); +} OpenCDMSessionCallbacks; + +/** + * \brief Creates DRM system. + * + * \return \ref OpenCDMAccessor instance, NULL on error. + */ +EXTERNAL struct OpenCDMSystem* opencdm_create_system(const char keySystem[]); + +/** + * \brief Creates DRM system. + * + * \param system Output parameter that will contain pointer to instance of \ref OpenCDMSystem. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_create_system_extended(const char keySystem[], struct OpenCDMSystem** system); + +/** + * Destructs an \ref OpenCDMAccessor instance. + * \param system \ref OpenCDMAccessor instance to desctruct. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_destruct_system(struct OpenCDMSystem* system); + +/** + * \brief Checks if a DRM system is supported. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of required key system (e.g. + * "com.microsoft.playready"). + * \param mimeType MIME type. + * \return Zero if supported, Non-zero otherwise. + * \remark mimeType is currently ignored. + */ +EXTERNAL OpenCDMError opencdm_is_type_supported(const char keySystem[], + const char mimeType[]); + +/** + * \brief Retrieves DRM system specific metadata. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param metadata, buffer to write metadata into, always 0 terminated (also when not large enough to hold all data) except when metadata is + * Null of course. Null allowed to retrieve required size needed for this buffer in metadataSize to be able to allocate required buffer + * for subsequent call to opencdm_is_type_supported + * \param metadataSize, in: size of metadata buffer, out: required size to hold all data available when return value is ERROR_MORE_DATA_AVAILBALE, + * , number of characters written into metadata (incl 0 terminator) otherwise. Note in case metadata could not hold all data but was not of zero + * length it is filled up to the maximum size (still zero terminated) but also ERROR_MORE_DATA_AVAILBALE is returned with the required size needed + * to hold all data + * \return Zero on success, non-zero on error. ERROR_MORE_DATA_AVAILBALE when the buffer was not large enough to hold all the data available. + */ +EXTERNAL OpenCDMError opencdm_system_get_metadata(struct OpenCDMSystem* system, + char metadata[], + uint16_t* metadataSize); + +/** + * \brief Returns string describing version of DRM system. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of queried key system (e.g. + * "com.microsoft.playready"). + * \param versionStr Char buffer to receive NULL-terminated version string. + * (Should as least be 64 chars long.) + * \return Zero if successful, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_get_version(struct OpenCDMSystem* system, + char versionStr[]); + +/** + * \brief Returns time according to DRM system. + * Some systems (e.g. PlayReady) keep their own clocks, for example to prevent + * rollback. Systems + * not implementing their own clock can return the system time. + * + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of queried key system (e.g. "com.microsoft.playready"). + * \param time Output variable that will contain DRM system time. + * \return Zero if successful, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_get_drm_time(struct OpenCDMSystem* system, + uint64_t* time); + +/** + * \brief Maps key ID to \ref OpenCDMSession instance. + * + * In some situations we only have the key ID, but need the specific \ref + * OpenCDMSession instance that + * belongs to this key ID. This method facilitates this requirement. + * \param system Instance of \ref OpenCDMAccessor. + * \param keyId Array containing key ID. + * \param length Length of keyId array. + * \param maxWaitTime Maximum allowed time to block (in milliseconds). + * \return \ref OpenCDMSession belonging to key ID, or NULL when not found or + * timed out. This instance + * also needs to be destructed using \ref opencdm_session_destruct. + */ +EXTERNAL struct OpenCDMSession* opencdm_get_session(const uint8_t keyId[], + const uint8_t length, + const uint32_t waitTime); + +/** + * \brief Maps key ID to \ref OpenCDMSession instance within the given system instance. + * + * In some situations we only have the key ID, but need the specific \ref + * OpenCDMSession instance that + * belongs to this key ID. This method facilitates this requirement. + * \param system Instance of \ref OpenCDMSystem. + * \param keyId Array containing key ID. + * \param length Length of keyId array. + * \param maxWaitTime Maximum allowed time to block (in milliseconds). + * \return \ref OpenCDMSession belonging to key ID, or NULL when not found or + * timed out. This instance + * also needs to be destructed using \ref opencdm_session_destruct. + */ +EXTERNAL struct OpenCDMSession* opencdm_get_system_session(struct OpenCDMSystem* system, const uint8_t keyId[], + const uint8_t length, const uint32_t waitTime); + +/** + * \brief Gets support server certificate. + * + * Some DRMs (e.g. WideVine) use a system-wide server certificate. This method + * gets if system has support for that certificate. + * \param system Instance of \ref OpenCDMAccessor. + * \return Non-zero on success, zero on error. + */ +EXTERNAL OpenCDMBool opencdm_system_supports_server_certificate( + struct OpenCDMSystem* system); + +/** + * \brief Sets server certificate. + * + * Some DRMs (e.g. WideVine) use a system-wide server certificate. This method + * will set that certificate. Other DRMs will ignore this call. + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem Name of key system to set server certificate for. + * \param serverCertificate Buffer containing certificate data. + * \param serverCertificateLength Buffer length of certificate data. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_system_set_server_certificate( + struct OpenCDMSystem* system, + const uint8_t serverCertificate[], const uint16_t serverCertificateLength); + +/** + * \brief Get metrics associated with a DRM system. + * + * Some DRMs (e.g. WideVine) offer metric data that can be used for any + * analyses. This function retrieves the metric data of the passed in + * system. It is up to the callee to interpret the baniary data correctly. + * \param system Instance of \ref OpenCDMAccessor. + * \param bufferLength Actual buffer length of the buffer parameter, on return + * it holds the number of bytes actually written in it. + * \param buffer Buffer length of buffer that can hold the metric data. + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_get_metric_system_data(struct OpenCDMSystem* system, + uint32_t* bufferLength, + uint8_t* buffer); + +/** + * \brief Create DRM session (for actual decrypting of data). + * + * Creates an instance of \ref OpenCDMSession using initialization data. + * \param system Instance of \ref OpenCDMAccessor. + * \param keySystem DRM system to create the session for. + * \param licenseType DRM specifc signed integer selecting License Type (e.g. + * "Limited Duration" for PlayReady). + * \param initDataType Type of data passed in \ref initData. + * \param initData Initialization data. + * \param initDataLength Length (in bytes) of initialization data. + * \param CDMData CDM data. + * \param CDMDataLength Length (in bytes) of \ref CDMData. + * \param callbacks the instance of \ref OpenCDMSessionCallbacks with callbacks to be called on events. + * \param userData the user data to be passed back to the \ref OpenCDMSessionCallbacks callbacks. + * \param session Output parameter that will contain pointer to instance of \ref OpenCDMSession. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_construct_session(struct OpenCDMSystem* system, const LicenseType licenseType, + const char initDataType[], const uint8_t initData[], const uint16_t initDataLength, + const uint8_t CDMData[], const uint16_t CDMDataLength, OpenCDMSessionCallbacks* callbacks, void* userData, + struct OpenCDMSession** session); + +/** + * Destructs an \ref OpenCDMSession instance. + * \param system \ref OpenCDMSession instance to desctruct. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_destruct_session(struct OpenCDMSession* session); + +/** + * Loads the data stored for a specified OpenCDM session into the CDM context. + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_load(struct OpenCDMSession* session); + +/** + * Process a key message response. + * \param session \ref OpenCDMSession instance. + * \param keyMessage Key message to process. + * \param keyLength Length of key message buffer (in bytes). + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_update(struct OpenCDMSession* session, + const uint8_t keyMessage[], + const uint16_t keyLength); + +/** + * Removes all keys/licenses related to a session. + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_remove(struct OpenCDMSession* session); + +/** + * Retrieves DRM session specific metadata of a session. + * \param session \ref OpenCDMSession instance. +* \param metadata, buffer to write metadata into, always 0 terminated (also when not large enough to hold all data) except when metadata is + * Null of course. Null allowed to retrieve required size needed for this buffer in metadataSize to be able to allocate required buffer + * for subsequent call to opencdm_session_metadata + * \param metadataSize, in: size of metadata buffer, out: required size to hold all data available when return value is ERROR_MORE_DATA_AVAILBALE, + * , number of characters written into metadata (incl 0 terminator) otherwise. Note in case metadata could not hold all data but was not of zero + * length it is filled up to the maximum size (still zero terminated) but also ERROR_MORE_DATA_AVAILBALE is returned with the required size needed + * to hold all data + * \return Zero on success, non-zero on error. ERROR_MORE_DATA_AVAILBALE when the buffer was not large enough to hold all the data available. + + */ +EXTERNAL OpenCDMError opencdm_session_metadata(const struct OpenCDMSession* session, + char metadata[], + uint16_t* metadataSize); + +/** + * Let CDM know playback stopped and reset output protection + * \param session \ref OpenCDMSession instance. + * \return Zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_resetoutputprotection(struct OpenCDMSession* session); + +/** + * Gets Session ID for a session. + * \param session \ref OpenCDMSession instance. + * \return Session ID, valid as long as \ref session is valid. + */ +EXTERNAL const char* opencdm_session_id(const struct OpenCDMSession* session); + +/** + * Checks if a session has a specific keyid. Will check both BE/LE + * \param session \ref OpenCDMSession instance. + * \param length Length of key ID buffer (in bytes). + * \param keyId Key ID. + * \return 1 if keyID found else 0. + */ +EXTERNAL uint32_t opencdm_session_has_key_id(struct OpenCDMSession* session, + const uint8_t length, const uint8_t keyId[]); + +/** + * Returns status of a particular key assigned to a session. + * \param session \ref OpenCDMSession instance. + * \param keyId Key ID. + * \param length Length of key ID buffer (in bytes). + * \return key status. + */ +EXTERNAL KeyStatus opencdm_session_status(const struct OpenCDMSession* session, + const uint8_t keyId[], const uint8_t length); + +/** + * Returns error for key (if any). + * \param session \ref OpenCDMSession instance. + * \param keyId Key ID. + * \param length Length of key ID buffer (in bytes). + * \return Key error (zero if no error, non-zero if error). + */ +EXTERNAL uint32_t opencdm_session_error(const struct OpenCDMSession* session, + const uint8_t keyId[], const uint8_t length); + +/** + * Returns system error. This reference general system, instead of specific key. + * \param session \ref OpenCDMSession instance. + * \return System error code, zero if no error. + */ +EXTERNAL OpenCDMError opencdm_session_system_error(const struct OpenCDMSession* session); + +/** + * Gets buffer ID for a session. + * \param session \ref OpenCDMSession instance. + * \return Buffer ID, valid as long as \ref session is valid. + */ +EXTERNAL const char* opencdm_session_buffer_id(const struct OpenCDMSession* session); + +/** + * Closes a session. + * \param session \ref OpenCDMSession instance. + * \return zero on success, non-zero on error. + */ +EXTERNAL OpenCDMError opencdm_session_close(struct OpenCDMSession* session); + +/** + * \brief Performs decryption. + * + * This method accepts encrypted data and will typically decrypt it + * out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system + * allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param encrypted Buffer containing encrypted data. If applicable, decrypted + * data will be stored here after this call returns. + * \param encryptedLength Length of encrypted data buffer (in bytes). + * \param encScheme CENC Schemes as defined in EncryptionScheme enum + * \param pattern Encryption pattern containing number of Encrypted and Clear blocks. + * \param IV Initial vector (IV) used during decryption. Can be NULL, in that + * case and IV of all zeros is assumed. + * \param IVLength Length of IV buffer (in bytes). + * \param keyID keyID to use for decryption + * \param keyIDLength Length of keyID buffer (in bytes). + * \param initWithLast15 Whether decryption context needs to be initialized with + * last 15 bytes. Currently this only applies to PlayReady DRM. + * \return Zero on success, non-zero on error. + */ +#ifdef __cplusplus +EXTERNAL OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, + const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, + uint32_t initWithLast15 = 0); + +#else +EXTERNAL OpenCDMError opencdm_session_decrypt(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const EncryptionScheme encScheme, + const EncryptionPattern pattern, + const uint8_t* IV, uint16_t IVLength, + const uint8_t* keyId, const uint16_t keyIdLength, + uint32_t initWithLast15); +#endif // __cplusplus + +/** + * \brief Get metrics associated with a DRM session. + * + * Some DRMs (e.g. WideVine) offer metric data that can be used for any + * analyses. This function retrieves the metric data of the passed in + * system. It is up to the callee to interpret the baniary data correctly. + * \param session Instance of \ref OpenCDMSession. + * \param bufferLength Actual buffer length of the buffer parameter, on return + * it holds the number of bytes actually written in it. + * \param buffer Buffer length of buffer that can hold the metric data. + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_get_metric_session_data(struct OpenCDMSession* session, + uint32_t* bufferLength, + uint8_t* buffer); + +/** + * \brief Performs decryption. + * + * This method accepts encrypted data and will typically decrypt it + * out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system + * allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param encrypted Buffer containing encrypted data. If applicable, decrypted + * data will be stored here after this call returns. + * \param encryptedLength Length of encrypted data buffer (in bytes). + * \param sampleInfo Per Sample information needed to decrypt this sample + * \param streamProperties Provides info about current stream + * \return Zero on success, non-zero on error. + */ + +EXTERNAL OpenCDMError opencdm_session_decrypt_v2(struct OpenCDMSession* session, + uint8_t encrypted[], + const uint32_t encryptedLength, + const SampleInfo* sampleInfo, + const MediaProperties* streamProperties); + +/** + * @brief Close the cached open connection if it exists. + * + */ +EXTERNAL void opencdm_dispose(); + +#ifdef __cplusplus +} +#endif + +#endif // OPEN_CDM_H diff --git a/middleware/test/utests/ocdm/open_cdm_adapter.h b/middleware/test/utests/ocdm/open_cdm_adapter.h new file mode 100644 index 000000000..d4300d3ac --- /dev/null +++ b/middleware/test/utests/ocdm/open_cdm_adapter.h @@ -0,0 +1,96 @@ + /* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2020 Metrological + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __OPEN_CDM_ADAPTER_H +#define __OPEN_CDM_ADAPTER_H + +#include "open_cdm.h" + +struct _GstBuffer; +typedef struct _GstBuffer GstBuffer; + +struct _GstCaps; +typedef struct _GstCaps GstCaps; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Performs decryption based on adapter implementation. + * + * This method accepts encrypted data and will typically decrypt it out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * \param session \ref OpenCDMSession instance. + * \param buffer Gstreamer buffer containing encrypted data and related meta data. If applicable, decrypted data will be stored here after this call returns. + * \param subSample Gstreamer buffer containing subsamples size which has been parsed from protection meta data. + * \param subSampleCount count of subsamples + * \param IV Gstreamer buffer containing initial vector (IV) used during decryption. + * \param keyID Gstreamer buffer containing keyID to use for decryption + * + * This method handles the Subsample mapping by consolidating all the encrypted data into one buffer before decrypting. This means the Subsample mappings are + * not passed on to the DRM implementation side. + * + * For CBCS support, EncryptionScheme and EncryptionPattern information can be added as part of the ProtectionMeta in the given format below + * "cipher-mode" G_TYPE_STRING (One of the Four Character Code (FOURCC) Protection schemes as defined in https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1:en) + * "crypt_byte_block" G_TYPE_UINT (Present only if cipher-mode is "cbcs") + * "skip_byte_block" G_TYPE_UINT (Present only cipher-mode is "cbcs") + + * \return Zero on success, non-zero on error. + */ + EXTERNAL OpenCDMError opencdm_gstreamer_session_decrypt(struct OpenCDMSession* session, GstBuffer* buffer, GstBuffer* subSample, const uint32_t subSampleCount, + GstBuffer* IV, GstBuffer* keyID, uint32_t initWithLast15); + +/** + * \brief Performs decryption based on adapter implementation. + * + * This version 3 method accepts encrypted data and will typically decrypt it out-of-process (for security reasons). The actual data copying is performed + * using a memory-mapped file (for performance reasons). If the DRM system allows access to decrypted data (i.e. decrypting is not + * performed in a TEE), the decryption is performed in-place. + * This version assumes all data required is attached as metadata to the buffer. Specification for this data is as follows: + * + * Typically, the caller would parse the protection information for a video/audio frame from its input data and use this information to populate the + * GstStructure info field, which is then encapsulated in a GstProtectionMeta object and attached to the corresponding GstBuffer using the + * gst_buffer_add_protection_meta function. + * + * gst_structure [application/x-cenc] + * "iv" GST_TYPE_BUFFER + * "kid" GST_TYPE_BUFFER + * "subsample_count" G_TYPE_UINT + * "subsamples" GST_TYPE_BUFFER + * "cipher-mode" G_TYPE_STRING (One of the Four Character Code (FOURCC) Protection schemes as defined in https://www.iso.org/obp/ui/#iso:std:iso-iec:23001:-7:ed-3:v1:en) + * "crypt_byte_block" G_TYPE_UINT (Present only if cipher-mode is "cbcs") + * "skip_byte_block" G_TYPE_UINT (Present only cipher-mode is "cbcs") + * + * This method passes on the subsample mapping to the DRM implementation and assumes that the DRM implementaion will handle the decryption based on subsample mapping. + * + * \param session \ref OpenCDMSession instance. + * \param buffer Gstreamer buffer containing encrypted data and related meta data. If applicable, decrypted data will be stored here after this call returns. + * \return Zero on success, non-zero on error. + */ + + EXTERNAL OpenCDMError opencdm_gstreamer_session_decrypt_buffer(struct OpenCDMSession* session, GstBuffer* buffer, GstCaps* caps); + + +#ifdef __cplusplus +} +#endif + +#endif // __OPEN_CDM_ADAPTER_H diff --git a/middleware/test/utests/run.sh b/middleware/test/utests/run.sh new file mode 100755 index 000000000..28a7c4b13 --- /dev/null +++ b/middleware/test/utests/run.sh @@ -0,0 +1,207 @@ +#!/bin/bash -e +# +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# This script will build and run microtests. +# Use option: -c to additionally build coverage tests +# Use option: -h to halt coverage tests on error + +if [[ -z "${MAKEFLAGS}" ]]; then + export MAKEFLAGS=-j$(nproc) +fi + +# If a test crashes or has AS trap, provide an error test report +error_report() +{ +cat << EOF > test_details.json +{ + "tests": 1, + "failures": 0, + "disabled": 0, + "errors": 1, + "timestamp": "`date`", + "time": "0s", + "name": "AllTests", + "testsuites": [ + { + "name": "$1", + "tests": 1, + "failures": 0, + "disabled": 0, + "errors": 1 + } + ] +} +EOF +} + +# "corrupt arc tag" +(find . -name "*.gcda" -print0 | xargs -0 rm) || true + +build_coverage=0 +halt_on_error=0 +rdke_build=0 + +while getopts "ceh" opt; do + case ${opt} in + c ) echo Do build coverage + build_coverage=1 + ;; + e ) echo RDK-E build + rdke_build=1 + ;; + h ) echo Halt on error + halt_on_error=1 + ;; + * ) + ;; + esac +done + +TEST_DIR=$PWD +PLAYERDIR=$(realpath ${TEST_DIR}/../..) + +PLAYER_BUILD_GCNO="" + +if [ "$build_coverage" -eq "1" ]; then + #Find where player .gcno files get put when player-cli is built via install-middleware.sh -c + A_GCNO=$(find ${PLAYERDIR}/build -name 'AampConfig*gcno' -print -quit) + + if [ -z "$A_GCNO" ]; then + echo "ERROR need to run 'install-middleware.sh -c' first to get baseline list of middleware files for coverage" + exit 1 + fi + PLAYER_BUILD_GCNO=$(dirname $A_GCNO) +fi + +# Build and run microtests: +set -e + +mkdir -p build + +cd build + +if [[ "$OSTYPE" == "darwin"* ]]; then + PKG_CONFIG_PATH=/Library/Frameworks/GStreamer.framework/Versions/1.0/lib/pkgconfig:${PLAYERDIR}/.libs/lib/pkgconfig:/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCOVERAGE_ENABLED=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_RDKE_TEST_RUN=$rdke_build ../ +elif [[ "$OSTYPE" == "linux"* ]]; then + echo "PLAYER DIR[${PLAYERDIR}]" + PKG_CONFIG_PATH=${PLAYERDIR}/.libs/lib/pkgconfig cmake --no-warn-unused-cli -DCMAKE_INSTALL_PREFIX=${PLAYERDIR}/.libs -DCMAKE_PLATFORM_UBUNTU=1 -DCOVERAGE_ENABLED=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_LIBRARY_PATH=${PLAYERDIR}/.libs/lib -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++ -DCMAKE_RDKE_TEST_RUN=$rdke_build -S../ -B$PWD -G "Unix Makefiles" + export LD_LIBRARY_PATH=${PLAYERDIR}/.libs/lib +else + #abort the script if its not macOS or linux + echo "Aborting unsupported OS detected" + echo $OSTYPE + exit 1 +fi +make +# Work around for cmake deprecation of --testdir option for version 3.21.0 and above +cmake_version=$(cmake --version | head -n 1 | awk '{print $3}') +major_version=$(echo "$cmake_version" | cut -d. -f1) +minor_version=$(echo "$cmake_version" | cut -d. -f2) +if [[ "$major_version" -gt 3 ]] || [[ "$major_version" -eq 3 && "$minor_version" -ge 21 ]]; then + CT_TESTDIR="" +else + CT_TESTDIR="--testdir build" + +fi + +if [ "$rdke_build" -eq "1" ]; then + echo "RDKE build" + + export GTEST_OUTPUT="json" + ctest -j 4 --output-on-failure --no-compress-output -T Test $CT_TESTDIR || true # Don't exit script if a test fails + + cd tests + + for test_dir in */; do + if [ -d "$test_dir" ] && [ "$test_dir" != "CMakeFiles/" ] && [ "$test_dir" != "tsb/" ]; then + if [ ! -f "$test_dir/test_detail.json" ]; then + echo "Missing: $test_dir/test_detail.json" + + # Create a fallback test_detail.json + cat < "$test_dir/test_detail.json" +{ + "tests": 1, + "failures": 1, + "disabled": 0, + "errors": 0, + "time": "0.0s", + "name": "${test_dir%/}", + "testsuites": [ + { + "name": "${test_dir%/}", + "tests": 1, + "failures": 1, + "disabled": 0, + "errors": 0, + "time": "0.0s", + "testsuite": [ + { + "name": "${test_dir%/}", + "status": "failed", + "time": "0.0s", + "classname": "${test_dir%/}", + "failure": { + "message": "Testing ended abruptly", + "type": "SEGFAULT (probably)" + } + } + ] + } + ] +} +EOF + fi + fi + done + + cd .. + + find . -name test_detail\*.json | xargs cat | jq -s '{test_cases_results: {tests: map(.tests) | add,failures: map(.failures) | add,disabled: map(.disabled) | add,errors: map(.errors) | add,time: ((map(.time | rtrimstr("s") | tonumber) | add) | tostring + "s"),name: .[0].name,testsuites: map(.testsuites[])}}' > L1Report.json + +else + ctest -j 4 --output-on-failure --no-compress-output -T Test $CT_TESTDIR --output-junit ctest-results.xml +fi + +if [ "$build_coverage" -eq "1" ]; then +#We are in utests/build + +LCOV=lcov + +#Get initial baseline of files from player-cli build +$LCOV --initial $IGNORE --directory ${PLAYER_BUILD_GCNO} -b $PLAYERDIR --capture --output-file baseline.info + +#Get a list of dirs which contain coverage data for player source files. +TEST_DIRS=$(find tests -name '*.dir' -type d | grep -v _coverage.dir ) +COMBINE="" +for DIR in $TEST_DIRS; do + info_file=$DIR/TEST.info + cmd="$LCOV --directory $DIR -b $TESTDIR --capture --output-file ${info_file}" + echo $cmd + $cmd + COMBINE=$COMBINE" -a $info_file" +done +HTML_OUT=$(realpath ../CombinedCoverage) +XML_OUT=$(realpath ../coverage.xml) +$LCOV $COMBINE -a baseline.info --output-file all.info.1 +$LCOV --remove all.info.1 --output-file all.info "*/aamp/tsb/test/*" "*/aamp/.libs/*" "*/aamp/test/*" "*/aamp/Linux/*" "*/aamp/subtec/subtecparser/*" "/usr/*" +genhtml --demangle-cpp -o ${HTML_OUT} all.info +# Generate coverage.xml +lcov_cobertura all.info -b ${PLAYERDIR} --demangle -o ${XML_OUT} || true +echo "Coverage written to ${HTML_OUT}" +fi diff --git a/middleware/test/utests/tests/Base64PLAYER/Base64PLAYERTests.cpp b/middleware/test/utests/tests/Base64PLAYER/Base64PLAYERTests.cpp new file mode 100755 index 000000000..f51285d11 --- /dev/null +++ b/middleware/test/utests/tests/Base64PLAYER/Base64PLAYERTests.cpp @@ -0,0 +1,26 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/Base64PLAYER/Base64Tests.cpp b/middleware/test/utests/tests/Base64PLAYER/Base64Tests.cpp new file mode 100644 index 000000000..332e92a83 --- /dev/null +++ b/middleware/test/utests/tests/Base64PLAYER/Base64Tests.cpp @@ -0,0 +1,334 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2020 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +#include +#include +#include + +//include the google test dependencies +#include + +// unit under test +#include "_base64.h" + +namespace base64Test { + + // Test Vectors + // Swap 'inp' and 'exp' when going from encode to decode, base64 is reversible + const unsigned char base64Inp1[] = "1234"; + const char base64Exp1[] = "MTIzNA=="; + const unsigned char base64Inp2[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" "+/"; + const char base64Exp2[] = "QUJDRA=="; + const char base64Exp3[] = "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw=="; + const unsigned char base64Inp4[] = "_"; // RFC 4648 §5 URL- and filename-safe standard + const char base64Exp4[] = "Xw=="; + const unsigned char base64Inp5[] = ""; + const char base64Exp5[] = ""; + const unsigned char base64Inp6[] = "light work."; + const char base64Exp6[] = "bGlnaHQgd29yay4="; + + TEST(_base64Suite, encode) + { + size_t size = 4; + char *result; + + // padded output + result = ::base64_Encode(base64Inp1, size); + EXPECT_EQ(memcmp(result, base64Exp1, strlen(base64Exp1)), 0) << "The base64 encode of " << base64Inp1 << ", size " << size << " is not correct"; + free(result); + + // all supported characters limited length + size = 4; + result = ::base64_Encode(base64Inp2, size); + EXPECT_EQ(memcmp(result, base64Exp2, strlen(base64Exp2)), 0) << "The base64 encode of " << base64Inp2 << ", size " << size << " is not correct"; + free(result); + + // all supported characters full length + size = strlen((char *)base64Inp2); + result = ::base64_Encode(base64Inp2, size); + EXPECT_EQ(memcmp(result, base64Exp3, strlen(base64Exp3)), 0) << "The base64 encode of " << base64Inp2 << ", size " << size << " is not correct"; + free(result); + + // unsupported character should return empty result? + size = 1; + result = ::base64_Encode(base64Inp4, size); + EXPECT_EQ(memcmp(result, base64Exp4, strlen(base64Exp4)), 0) << "The base64 encode of " << base64Inp4 << ", size " << size << " is not correct"; + free(result); + + // empty input + size = 0; + result = ::base64_Encode(base64Inp5, size); + EXPECT_EQ(memcmp(result, base64Exp5, size), 0) << "The base64 encode of " << base64Inp5 << ", size " << size << " is not correct"; + free(result); + + // NULL doesn't upset us if size is 0, crashes if > 0 + size = 0; + result = ::base64_Encode(NULL, size); + EXPECT_EQ(memcmp(result, "", size), 0) << "The base64 encode of NULL, size 1 is not correct"; + free(result); + + size = 0; + for (int i=7; i < 12; i++) + { + char exp[17]; // max expected output is 16 bytes + switch (i) { + case 7: + strncpy(exp, "bGlnaHQgdw==", sizeof(exp)); + break; + case 8: + strncpy(exp, "bGlnaHQgd28=", sizeof(exp)); + break; + case 9: + strncpy(exp, "bGlnaHQgd29y", sizeof(exp)); + break; + case 10: + strncpy(exp, "bGlnaHQgd29yaw==", sizeof(exp)); + break; + default: + case 11: + strncpy(exp, "bGlnaHQgd29yay4=", sizeof(exp)); + break; + } + size = i; + result = ::base64_Encode(base64Inp6, size); + EXPECT_EQ(memcmp(result, exp, strlen(exp)), 0) << "The base64 encode of " << base64Inp6 << ", size " << size << " is not correct"; + free(result); + } + } + + TEST(_base64Suite, decode) + { + size_t len = 0; + size_t size = 0; + char *result; + + // padded output used truncated input of length 4 + result = (char *)::base64_Decode(base64Exp1, &len); + int cmp = strncmp(result, (char*)base64Inp1, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp1 << " is not correct"; + EXPECT_EQ(len, strlen((char *)base64Inp1)) << "The base64 decode of " << base64Exp1 << " is not correct"; + memset(result, 0, len); + free(result); + + // all supported characters full length + result = (char *)::base64_Decode(base64Exp3, &len); + cmp = strncmp(result, (char*)base64Inp2, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp2 << " is not correct"; + EXPECT_EQ(len, strlen((char *)base64Inp2)) << "The base64 decode of " << base64Exp2 << " is not correct"; + memset(result, 0, len); + free(result); + + // unsupported character should return empty result? + result = (char *)::base64_Decode(base64Exp4, &len); + EXPECT_EQ(memcmp(result, base64Inp4, strlen((char *)base64Inp4)), 0) << "The base64 decode of " << base64Exp4 << " is not correct"; + EXPECT_EQ(len, strlen((char *)base64Inp4)) << "The base64 decode of " << base64Exp4 << " is not correct"; + memset(result, 0, len); + free(result); + + // Check for invalid sizes, should not trigger address sanitizer. + size = 1; + result = (char *)::base64_Decode(base64Exp4, &len, size); + EXPECT_EQ( len, 0 ); + free(result); + + size = 2; + result = (char *)::base64_Decode(base64Exp4, &len, size); + EXPECT_EQ( len, 1 ); + EXPECT_EQ(result[0], '_') << "The base64 decode of " << base64Exp4 << " is not correct"; + memset(result, 0, len); + free(result); + + size = 3; + result = (char *)::base64_Decode(base64Exp4, &len, size); + EXPECT_EQ( len, 1 ); + EXPECT_EQ(result[0], '_') << "The base64 decode of " << base64Exp4 << " is not correct"; + memset(result, 0, len); + free(result); + + // NULL crashes + //result = (char *)::base64_Decode(NULL, &len); + //EXPECT_STREQ(result, (char *)base64Inp2) << "The base64 decode of NULL is not correct"; + //free(result); + + result = (char *)::base64_Decode(base64Exp6, &len); + cmp = strncmp(result, (char*)base64Inp6, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp6 << " is not correct"; + EXPECT_EQ(len, strlen((char *)base64Inp6)) << "The base64 decode of " << base64Exp6 << " is not correct"; + memset(result, 0, len); + free(result); + + } + TEST(_base64Suite, decodeLen) + { + size_t len = 0; + size_t size = 4; + char *result; + + // Not checking returned len, checked in decode() + + // padded output + result = (char *)::base64_Decode(base64Exp1, &len, size); + int cmp = strncmp(result, (char *)base64Inp1, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp1 << " is not correct"; + memset(result, 0, len); + free(result); + + // all supported characters limited length + size = 3; + result = (char *)::base64_Decode(base64Exp2, &len, size); + cmp = strncmp(result, (char *)base64Inp2, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp2 << " is not correct"; + free(result); + + // all supported characters full length + size = sizeof(base64Exp3); + result = (char *)::base64_Decode(base64Exp3, &len, size); + cmp = strncmp(result, (char *)base64Inp2, len); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << base64Exp3 << " is not correct"; + memset(result, 0, len); + free(result); + + // empty input + size = 0; + result = (char *)::base64_Decode(base64Exp5, &len, size); + EXPECT_EQ(len, 0) << "The base64 decode of " << base64Exp5 << " is not correct"; + memset(result, 0, len); + free(result); + + // NULL doesn't upset us if size is 0, crashes if > 0 + size = 0; + result = (char *)::base64_Decode(NULL, &len, size); + EXPECT_EQ(memcmp(result, base64Inp5, strlen((char *)base64Inp5)), 0) << "The base64 decode of NULL is not correct"; + memset(result, 0, len); + free(result); + + size = 0; + for (int i=7; i < 12; i++) + { + char exp[17]; // max expected output is 16 bytes + switch (i) { + case 7: + strncpy(exp, "bGlnaHQgdw==", sizeof(exp)); + break; + case 8: + strncpy(exp, "bGlnaHQgd28=", sizeof(exp)); + break; + case 9: + strncpy(exp, "bGlnaHQgd29y", sizeof(exp)); + break; + case 10: + strncpy(exp, "bGlnaHQgd29yaw==", sizeof(exp)); + break; + default: + case 11: + strncpy(exp, "bGlnaHQgd29yay4=", sizeof(exp)); + break; + } + size = strlen(exp); + result = (char *)::base64_Decode(exp, &len, size); + int cmp = strncmp(result, (char *)base64Inp6, i); + EXPECT_EQ(cmp, 0) << "The base64 decode of " << exp << " is not correct"; + memset(result, 0, len); + free(result); + } + } + +TEST(_base64Suite, bad ) +{ + const char *badBase64 = "!@#*("; + size_t srcLen = strlen(badBase64); + size_t len = 999; + unsigned char *result = ::base64_Decode( badBase64, &len, srcLen); + EXPECT_TRUE( result==NULL ); + EXPECT_EQ( len, 0 ); +} + +TEST(_base64Suite, encodedecode ) +{ + struct + { + const char *base64; + const char *expected; + size_t expectedLen; + bool oldSyntax; + } testData[] = + { + {"",""}, + {"Zg==","f"}, + {"Zm8=","fo"}, + {"Zm9v","foo"}, + {"Zm9vYg==","foob"}, + {"Zm9vYmE=","fooba"}, + {"Zm9vYmFy","foobar"}, + { + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5MCEiwqMkJV4mKigpQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVp8XDw+PywuLzo7QCd+I3t9W10tXw==", + "abcdefghijklmnopqrstuvwxyz01234567890!\"£$%^&*()ABCDEFGHIJKLMNOPQRSTUVWXYZ|\\<>?,./:;@'~#{}[]-_" + }, + { "bGlnaHQgd29yay4=", "light work." }, + + { "AAAASnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAACoIARIQL38/m5MtSFqBGT0XY2KM4yIUTkJDVTA0Mzg3NjU0MDA1NjMwMDc=", + "\000\000\000Jpssh\000\000\000\000\355\357\213\251y\326J\316\243\310'\334\325\035!\355\000\000\000*\010\001\022\020/?\233\223-HZ\201\031=\027cb\214\343\042\024NBCU0438765400563007", + 74 // binary data size - can't use strlen + }, + { "AAAASnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAACoIARIQL38-m5MtSFqBGT0XY2KM4yIUTkJDVTA0Mzg3NjU0MDA1NjMwMDc=", + "\000\000\000Jpssh\000\000\000\000\355\357\213\251y\326J\316\243\310'\334\325\035!\355\000\000\000*\010\001\022\020/?\233\223-HZ\201\031=\027cb\214\343\042\024NBCU0438765400563007", + 74, // binary data size - can't use strlen + true // old syntax, with "-" instead of "/" + }, + }; + + for( int i=0; i +#include +#include +#include +#include +#include +#include "MockDrmHelper.h" + + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::_; + +std::shared_ptr drmHelper; + +class DrmHLSTests : public ::testing::Test +{ + public: + protected: + void SetUp() override + { + drmHelper = std::make_shared(); + } + + void TearDown() override + { + drmHelper = nullptr; + } +}; + +extern std::shared_ptr ProcessContentProtection( std::string attrName, bool param , bool isRequired); + +TEST_F(DrmHLSTests, ProcessContentProtection) +{ + { /* Widevine */ + std::string attrName = "#EXT-X-KEY:METHOD=SAMPLE-AES-CTR, KEYFORMAT=\"urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed\",KEYFORMATVERSIONS=\"1\",URI=\"data:text/plain;base64,AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIgZTI1ZmFkYjQ4YmZiNDkyMjljZTBhNGFmZGZlMDUxOTcaB3NsaW5ndHYiBUhHVFZEKgVTRF9IRA==\",KEYID=0xe25fadb48bfb49229ce0a4afdfe05197"; + + EXPECT_CALL(*drmHelper, isDecryptClearSamplesRequired()).WillOnce(Return(false)); + + auto rc = ProcessContentProtection(attrName,drmHelper->getPropagateUriParam(),drmHelper->isDecryptClearSamplesRequired()); + ASSERT_NE( rc, nullptr ); + + } + + { /* PlayReady */ + std::string attrName = "#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI=\"data:text/plain;charset=UTF-16;base64,BgIAAAEAAQD8ATwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AbgA0AEkARABBAEsATwAxAHMARwByAGcAegBpAHkAOAA4AFgAcgBqAGYAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgB1AGkAbwA4AFcAVQBwAFQANAA0ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=\",KEYFORMAT=\"com.microsoft.playready\",KEYFORMATVERSIONS=\"1\""; + + EXPECT_CALL(*drmHelper, isDecryptClearSamplesRequired()).WillOnce(Return(false)); + auto rc = ProcessContentProtection(attrName,drmHelper->getPropagateUriParam(),drmHelper->isDecryptClearSamplesRequired()); + + // getting nullptr here + // comments elsewhere indicate PlayReady helper currently supports DASH only, so known issue + // ASSERT_NE( rc, nullptr ); + } +} diff --git a/middleware/test/utests/tests/DrmOcdm/DrmHelperTests.cpp b/middleware/test/utests/tests/DrmOcdm/DrmHelperTests.cpp new file mode 100644 index 000000000..fd86ee508 --- /dev/null +++ b/middleware/test/utests/tests/DrmOcdm/DrmHelperTests.cpp @@ -0,0 +1,823 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "_base64.h" +#include "DrmHelper.h" + +#define MultiChar_Constant(Text) \ + ( (static_cast(Text[0]) << 24) | \ + (static_cast(Text[1]) << 16) | \ + (static_cast(Text[2]) << 8) | \ + (static_cast(Text[3])) ) +//#Conversion of text in to decimal value +struct CreateHelperTestData +{ + DrmMethod method; + MediaFormat mediaFormat; + std::string uri; + std::string keyFormat; + std::string systemUUID; + std::vector expectedKeyPayload; +}; +#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0])) + +DrmInfo createDrmInfo(DrmMethod method, MediaFormat mediaFormat, const std::string& uri = "", + const std::string& keyFormat = "", const std::string& systemUUID = "", + const std::string& initData = "") +{ + DrmInfo drmInfo; + + drmInfo.method = method; + drmInfo.mediaFormat = mediaFormat; + drmInfo.keyURI = uri; + drmInfo.keyFormat = keyFormat; + drmInfo.systemUUID = systemUUID; + drmInfo.initData = initData; + + return drmInfo; +} + +class DrmHelperTests : public ::testing::Test +{ +protected: + void SetUp() override + { + } + + void TearDown() override + { + } + +public: +}; + +TEST_F(DrmHelperTests, TestDrmIds) +{ + std::vector expectedIds({ + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", // ClearKey + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", // Widevine + "9a04f079-9840-4286-ab92-e65be0885f95" // PlayReady + }); + std::sort(expectedIds.begin(), expectedIds.end()); + + std::vector actualIds; + DrmHelperEngine::getInstance().getSystemIds(actualIds); + std::sort(actualIds.begin(), actualIds.end()); + + ASSERT_EQ(expectedIds, actualIds); +} + +TEST_F(DrmHelperTests, TestCreateClearKeyHelper) +{ + const std::vector testData = { + // Valid KEYFORMAT, HLS + {eMETHOD_AES_128, + eMEDIAFORMAT_HLS_MP4, + "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + {'1'}}, + + // Valid KEYFORMAT, DASH + {eMETHOD_AES_128, + eMEDIAFORMAT_DASH, + "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + {}}, // For DASH, the key should come from the PSSH, so we won't check that here + + // Textual identifier rather than UUID + {eMETHOD_AES_128, + eMEDIAFORMAT_HLS_MP4, + "file.key", + "org.w3.clearkey", + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + {'1'}}, + + }; + DrmInfo drmInfo; + + for (auto& test_data : testData) + { + drmInfo = createDrmInfo(eMETHOD_AES_128, test_data.mediaFormat, test_data.uri, + test_data.keyFormat, test_data.systemUUID); + + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(clearKeyHelper != nullptr); + ASSERT_EQ("org.w3.clearkey", clearKeyHelper->ocdmSystemId()); + ASSERT_EQ(true, clearKeyHelper->isClearDecrypt()); + ASSERT_EQ(false, clearKeyHelper->isHdcp22Required()); + ASSERT_EQ(0, clearKeyHelper->getDrmCodecType()); + ASSERT_EQ(false, clearKeyHelper->isExternalLicense()); + ASSERT_EQ(5000U, clearKeyHelper->licenseGenerateTimeout()); + ASSERT_EQ(5000U, clearKeyHelper->keyProcessTimeout()); + + if (test_data.expectedKeyPayload.size() != 0) + { + std::vector keyID; + clearKeyHelper->getKey(keyID); + ASSERT_EQ(test_data.expectedKeyPayload, keyID); + } + } +} + +TEST_F(DrmHelperTests, TestClearKeyHelperHlsInitDataCreation) +{ + DrmInfo drmInfo = createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_HLS_MP4, "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + std::vector initData; + clearKeyHelper->createInitData(initData); +} + +TEST_F(DrmHelperTests, TestClearKeyHelperParsePssh) +{ + DrmInfo drmInfo = createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + // For DASH the init data should have come from the PSSH, so when asked to create + // the init data, the helper should just return that + std::vector psshData = { + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, + 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, + 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01, 0xfe, 0xed, 0xf0, 0x0d, 0xee, 0xde, 0xad, + 0xbe, 0xef, 0xf0, 0xba, 0xad, 0xf0, 0x0d, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00}; + + ASSERT_TRUE(clearKeyHelper->parsePssh(psshData.data(), (uint32_t)psshData.size())); + + std::vector initData; + clearKeyHelper->createInitData(initData); + ASSERT_EQ(psshData, initData); + + // KeyId should have been extracted from the PSSH + std::vector keyID; + const std::vector expectedKeyID = {0xfe, 0xed, 0xf0, 0x0d, 0xee, 0xde, 0xad, 0xbe, + 0xef, 0xf0, 0xba, 0xad, 0xf0, 0x0d, 0xd0, 0x0d}; + clearKeyHelper->getKey(keyID); + ASSERT_EQ(expectedKeyID, keyID); +} + +TEST_F(DrmHelperTests, TestClearKeyHelperGenerateLicenseRequest) +{ + DrmInfo drmInfo = createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_HLS_MP4, "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"); + drmInfo.manifestURL = "http://stream.example/hls/playlist.m3u8"; + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + ChallengeInfo challengeInfo; + challengeInfo.url = "http://challengeinfourl.example"; + LicenseRequest licenseRequest; + + // No ClearKey license URL in the license request, expect the URL to be + // constructed from the information in the DrmInfo + clearKeyHelper->generateLicenseRequest(challengeInfo, licenseRequest); + ASSERT_EQ(LicenseRequest::POST, licenseRequest.method); + ASSERT_EQ("http://stream.example/hls/file.key", licenseRequest.url); + ASSERT_EQ("", licenseRequest.payload); + + // Setting a ClearKey license URL in the license request, expect + // that to take precedence + const std::string fixedCkLicenseUrl = "http://cklicenseserver.example"; + licenseRequest.url = fixedCkLicenseUrl; + clearKeyHelper->generateLicenseRequest(challengeInfo, licenseRequest); + ASSERT_EQ(fixedCkLicenseUrl, licenseRequest.url); + + // Clearing ClearKey license URL in the license request and creating a + // helper with no key URI in the DrmInfo. Should use the URI from the challenge + licenseRequest.url.clear(); + DrmInfo drmInfoNoKeyUri = createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_HLS_MP4, "", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"); + std::shared_ptr clearKeyHelperNoKeyUri = + DrmHelperEngine::getInstance().createHelper(drmInfoNoKeyUri); + clearKeyHelperNoKeyUri->generateLicenseRequest(challengeInfo, licenseRequest); + ASSERT_EQ(challengeInfo.url, licenseRequest.url); +} + +TEST_F(DrmHelperTests, TestClearKeyHelperTransformHlsLicenseResponse) +{ + struct TransformLicenseResponseTestData + { + std::vector keyResponse; + std::string expectedEncodedKey; + }; + + DrmInfo drmInfo = createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_HLS_MP4, "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + const std::vector testData{ + // Empty response - should lead to empty string + {{}, {""}}, + // Most basic case - 1 maps to AQ + {{0x1}, {"AQ"}}, + // Should lead to a string containing every possible base64url character + {{0x00, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8b, 0x30, 0xd3, 0x8f, + 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96, 0x9b, 0x71, 0xd7, 0x9f, + 0x82, 0x18, 0xa3, 0x92, 0x59, 0xa7, 0xa2, 0x9a, 0xab, 0xb2, 0xdb, 0xaf, + 0xc3, 0x1c, 0xb3, 0xd3, 0x5d, 0xb7, 0xe3, 0x9e, 0xbb, 0xf3, 0xdf, 0xbf}, + {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"}}}; + + for (auto& testCase : testData) + { + std::shared_ptr drmData = std::make_shared( reinterpret_cast(testCase.keyResponse.data()), testCase.keyResponse.size()); + clearKeyHelper->transformLicenseResponse(drmData); + } +} + +TEST_F(DrmHelperTests, TestTransformDashLicenseResponse) +{ + // Unlike HLS (where we do expect a ClearKey license to be transformed), + // for DASH we expect the response to be given back untouched + std::vector drmInfoList; + + // ClearKey + drmInfoList.push_back(createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", + "urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b")); + + // PlayReady + drmInfoList.push_back(createDrmInfo(eMETHOD_NONE, eMEDIAFORMAT_DASH, "", "", + "9a04f079-9840-4286-ab92-e65be0885f95")); + + for (auto& drmInfo : drmInfoList) + { + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr clearKeyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + char licenseResponse[] = {'D', 'A', 'S', 'H', 'L', 'I', 'C'}; + std::shared_ptr drmData = + std::make_shared(licenseResponse, sizeof(licenseResponse)); + + clearKeyHelper->transformLicenseResponse(drmData); + ASSERT_EQ(sizeof(licenseResponse), drmData->getDataLength()); + ASSERT_EQ(std::string("DASHLIC"), drmData->getData()); + } +} + +TEST_F(DrmHelperTests, TestCreatePlayReadyHelper) +{ + const std::vector testData = { + // Valid UUID + {eMETHOD_AES_128, + eMEDIAFORMAT_DASH, // Note: PlayReady helper currently supports DASH only + "file.key", + "", + "9a04f079-9840-4286-ab92-e65be0885f95", + {}}, // For DASH, the key should come from the PSSH, so we won't check that here + + // Valid UUID, no method (method not required) + {eMETHOD_NONE, + eMEDIAFORMAT_DASH, // Note: PlayReady helper currently supports DASH only + "file.key", + "", + "9a04f079-9840-4286-ab92-e65be0885f95", + {}}, // For DASH, the key should come from the PSSH, so we won't check that here + + // Textual identifier rather than UUID + {eMETHOD_AES_128, + eMEDIAFORMAT_DASH, // Note: PlayReady helper currently supports DASH only + "file.key", + "com.microsoft.playready", + "", + {}} // For DASH, the key should come from the PSSH, so we won't check that here + }; + DrmInfo drmInfo; + + for (auto& test_data : testData) + { + drmInfo = createDrmInfo(test_data.method, test_data.mediaFormat, test_data.uri, + test_data.keyFormat, test_data.systemUUID); + + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + + std::shared_ptr playReadyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(playReadyHelper != nullptr); + ASSERT_EQ("com.microsoft.playready", playReadyHelper->ocdmSystemId()); + ASSERT_EQ(false, playReadyHelper->isClearDecrypt()); + ASSERT_EQ(eDRM_PlayReady, playReadyHelper->getDrmCodecType()); + ASSERT_EQ(false, playReadyHelper->isExternalLicense()); + ASSERT_EQ(5000U, playReadyHelper->licenseGenerateTimeout()); + ASSERT_EQ(5000U, playReadyHelper->keyProcessTimeout()); + + // TODO: HDCP checks + } +} + +TEST_F(DrmHelperTests, TestCreatePlayReadyHelperNegative) +{ + const std::vector testData = { + // Valid UUID but HLS media format, which isn't supported for the PlayReady helper + {eMETHOD_NONE, + eMEDIAFORMAT_HLS, + "file.key", + "", + "9a04f079-9840-4286-ab92-e65be0885f95", + {}}}; + DrmInfo drmInfo; + + for (auto& test_data : testData) + { + drmInfo = createDrmInfo(test_data.method, test_data.mediaFormat, test_data.uri, + test_data.keyFormat, test_data.systemUUID); + + ASSERT_FALSE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + + std::shared_ptr playReadyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(playReadyHelper == nullptr); + } +} + +TEST_F(DrmHelperTests, TestWidevineHelperParsePsshDrmMetaData) +{ + struct + { + const char *psshData; + const char *expectedKey[4]; + uint32_t protectionScheme; + } testData[] = + { + { "AAAANXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABUyBnBvbGljeTjAxAdQlZrvOioCU0Q=", // psshData + {}, + 0 + // various deprecated fields: + //Policy: 'policy' + //Track Type: 'SD' + //Crypto Period Index: 123456 + //Crypto Period: 204 weeks, 27 days, 21 hours, 33 minutes, 9 seconds + }, + { "AAAAJnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAZI89yVmwY=", // psshData + {}, + MultiChar_Constant("cens") + }, + { "AAAAJnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAZIscaJmwY=", // psshData + {}, + MultiChar_Constant("cbc1") + }, + { "AAAAPnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB4iFnNoYWthX2NlYzJmNjRhYTc4OTBhMTFI49yVmwY=", // psshData + { + "0x73,0x68,0x61,0x6B,0x61,0x5F,0x63,0x65,0x63,0x32,0x66,0x36,0x34,0x61,0x61,0x37,0x38,0x39,0x30,0x61,0x31,0x31" + }, MultiChar_Constant("cenc") + // Version 0, AES-CTR full sample encryption + // no key ids! + // Content ID = shaka_cec2f64aa7890a11 + }, + { "AAAANHBzc2gBAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAEttsSNMB9I6rt3G6eorJBCAAAAAA==", // psshData + { + "0x2D,0xB6,0xC4,0x8D,0x30,0x1F,0x48,0xEA,0xBB,0x77,0x1B,0xA7,0xA8,0xAC,0x90,0x42" + }, 0 + // Version 1, single KeyID + }, + { "AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEC22xI0wH0jqu3cbp6iskEJI49yVmwY=", // psshData + { + "0x2D,0xB6,0xC4,0x8D,0x30,0x1F,0x48,0xEA,0xBB,0x77,0x1B,0xA7,0xA8,0xAC,0x90,0x42" + },MultiChar_Constant("cenc") + // Version 0, 'cenc' + }, + { "AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEC22xI0wH0jqu3cbp6iskEJI88aJmwY=", // psshData + { + "0x2D,0xB6,0xC4,0x8D,0x30,0x1F,0x48,0xEA,0xBB,0x77,0x1B,0xA7,0xA8,0xAC,0x90,0x42" + },MultiChar_Constant("cbcs") + // Version 0, 'cbcs' + }, + { "AAAARHBzc2gBAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAIAAAAAAAAAAAAAAAAAAAAAEREREREREREREREREREREQAAAAA=", // psshData + { + "0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00", + "0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11" + },0 + // Version 1, two KeyIDs + }, + {"AAAAP3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAB8SEFYWbwTmlTrikzbYc0PLv+IaBWV6ZHJtSPPGiZsG", // psshData + { + "0x56,0x16,0x6f,0x04,0xe6,0x95,0x3a,0xe2,0x93,0x36,0xd8,0x73,0x43,0xcb,0xbf,0xe2" + },MultiChar_Constant("cbcs") + // Protection Scheme: 63 62 63 73 (cbcs) + // Provider: ezdrm + }, + {"AAAASnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAACoIARIQL38/m5MtSFqBGT0XY2KM4yIUTkJDVTA0Mzg3NjU0MDA1NjMwMDc=", // psshData + { + "0x2f,0x7f,0x3f,0x9b,0x93,0x2d,0x48,0x5a,0x81,0x19,0x3d,0x17,0x63,0x62,0x8c,0xe3" + },0 + // includes 'author id' + // algorithm: AES-CTR full sample encryption + // Content ID: NBCU0438765400563007 + }, + {"AAAASnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAACoIARIQ7zHnTYhMQv+k86p90K3+rCIUTkJDVTkwMDAxOTI5MTUxMzAwMDM=", // psshData + { + "0xef,0x31,0xe7,0x4d,0x88,0x4c,0x42,0xff,0xa4,0xf3,0xaa,0x7d,0xd0,0xad,0xfe,0xac" + },0 + // algorithm: AES-CTR full sample encryption + // Content ID: NBCU9000192915130003 + }, + {"AAAAfXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAF0SJDhlY2ZiOWZkLTNhODktNTNlMy05YjgwLTg4OTBmM2IyMzNmYiI1aW5kZW1hbmQuY29tSU5UTDAyMTgyMjIwMDA0MTQ5OTMtSU5NVjAyMTgyMjIwMDA0MTQ5OTM=", // psshData + { + "0x38,0x65,0x63,0x66,0x62,0x39,0x66,0x64,0x2D,0x33,0x61,0x38,0x39,0x2D,0x35,0x33,0x65,0x33,0x2D,0x39,0x62,0x38,0x30,0x2D,0x38,0x38,0x39,0x30,0x66,0x33,0x62,0x32,0x33,0x33,0x66,0x62" + },0 + // Content ID: indemand.comINTL0218222000414993-INMV0218222000414993 + // Key ID is 36 bytes long instead of required 16 + }, + {"AAAAMnBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABISEG3fTdGQ1VJtmPL1WTLb1a8=", // psshData + { + "0x6d,0xdf,0x4d,0xd1,0x90,0xd5,0x52,0x6d,0x98,0xf2,0xf5,0x59,0x32,0xdb,0xd5,0xaf" + },0 + }, + {"AAAA93Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAANcSJDQ3OTZiNjY3LWZkZjYtYjI0My04MTM4LTNmM2VmZmQzN2E0NRIkNjdlZGIwZjQtMzIyYS1hNDgyLWZlNmQtZThiNjZiYWFhOGU4EiRhZWYyZjE3YS0xOWUwLWQ2MTctMGI4Ny02MTdmNjQ5OWNlZjQSJGM2NzE5YWJjLTY4YzctMWYxZC0xOWRiLTUxMjU5YWY2MDJmZSI9dXBmYWl0aGFuZGZhbWlseS5jb21VUFRMMDAwMDAwMDAwODM3Mzg2MS1VUE1WMDAwMDAwMDAwODM3Mzg2MQ==", // psshData + { + "0x34,0x37,0x39,0x36,0x62,0x36,0x36,0x37,0x2D,0x66,0x64,0x66,0x36,0x2D,0x62,0x32,0x34,0x33,0x2D,0x38,0x31,0x33,0x38,0x2D,0x33,0x66,0x33,0x65,0x66,0x66,0x64,0x33,0x37,0x61,0x34,0x35", + "0x36,0x37,0x65,0x64,0x62,0x30,0x66,0x34,0x2D,0x33,0x32,0x32,0x61,0x2D,0x61,0x34,0x38,0x32,0x2D,0x66,0x65,0x36,0x64,0x2D,0x65,0x38,0x62,0x36,0x36,0x62,0x61,0x61,0x61,0x38,0x65,0x38", + "0x61,0x65,0x66,0x32,0x66,0x31,0x37,0x61,0x2D,0x31,0x39,0x65,0x30,0x2D,0x64,0x36,0x31,0x37,0x2D,0x30,0x62,0x38,0x37,0x2D,0x36,0x31,0x37,0x66,0x36,0x34,0x39,0x39,0x63,0x65,0x66,0x34", + "0x63,0x36,0x37,0x31,0x39,0x61,0x62,0x63,0x2D,0x36,0x38,0x63,0x37,0x2D,0x31,0x66,0x31,0x64,0x2D,0x31,0x39,0x64,0x62,0x2D,0x35,0x31,0x32,0x35,0x39,0x61,0x66,0x36,0x30,0x32,0x66,0x65" + },0 + // Content ID: upfaithandfamily.comUPTL0000000008373861-UPMV0000000008373861 + // Key ID is 36 bytes long instead of required 16 + }, + {"AAAAOHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABgSEABFDiYaE6QUtKoKcLv0s6hI49yVmwY=", // psshData + { + "0x00,0x45,0x0e,0x26,0x1a,0x13,0xa4,0x14,0xb4,0xaa,0x0a,0x70,0xbb,0xf4,0xb3,0xa8" + },MultiChar_Constant("cenc") + // Protection Scheme: 63 65 6E 63 (cenc) + // AES-CTR full sample encryption + } + }; + + for( int i=0; i widevineHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(widevineHelper != nullptr); + + bool rc = widevineHelper->parsePssh(psshDataPtr, (uint32_t)psshDataLen); + if( testData[i].expectedKey[0] ) + { // at least one keyid expected + ASSERT_TRUE(rc); + } + + std::vector initData; + widevineHelper->createInitData(initData); + for( int j=0; j> keyIDs; + widevineHelper->getKeys(keyIDs); + + printf( "protection scheme=0x%08x\n", widevineHelper->getProtectionScheme() ); + ASSERT_EQ( widevineHelper->getProtectionScheme(), testData[i].protectionScheme ); + + for( int iKey=0; iKey<4; iKey++ ) + { + const char *expected = testData[i].expectedKey[iKey]; + if( expected ) + { + int count = (int)(strlen(expected)+1)/5; + ASSERT_EQ( keyIDs[iKey].size(), count ); + for( int j=0; jgetDrmMetaData(); + ASSERT_EQ("", contentMetadata); + + std::string metaData("content meta data"); + widevineHelper->setDrmMetaData(metaData); + contentMetadata = widevineHelper->getDrmMetaData(); + ASSERT_EQ(metaData, contentMetadata); + + free( psshDataPtr ); + } +} + +TEST_F(DrmHelperTests, TestCreateWidevineHelper) +{ + const std::vector testData = { + {eMETHOD_NONE, + eMEDIAFORMAT_DASH, + "file.key", + "", + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + {}}, + + {eMETHOD_AES_128, + eMEDIAFORMAT_DASH, + "file.key", + "", + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + {}}, + + // Textual identifier rather than UUID + {eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "com.widevine.alpha", "", {}}}; + DrmInfo drmInfo; + + for (auto& test_data : testData) + { + drmInfo = createDrmInfo(test_data.method, test_data.mediaFormat, test_data.uri, + test_data.keyFormat, test_data.systemUUID); + + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + + std::shared_ptr widevineHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(widevineHelper != nullptr); + ASSERT_EQ("com.widevine.alpha", widevineHelper->ocdmSystemId()); + ASSERT_EQ(false, widevineHelper->isClearDecrypt()); + ASSERT_EQ(false, widevineHelper->isHdcp22Required()); + ASSERT_EQ(eDRM_WideVine, widevineHelper->getDrmCodecType()); + ASSERT_EQ(false, widevineHelper->isExternalLicense()); + ASSERT_EQ(5000U, widevineHelper->licenseGenerateTimeout()); + ASSERT_EQ(5000U, widevineHelper->keyProcessTimeout()); + } +} + +TEST_F(DrmHelperTests, TestCreateWidevineHelperNegative) +{ + const std::vector testData = { + // Valid UUID but HLS media format, which isn't supported for the Widevine helper + {eMETHOD_NONE, + eMEDIAFORMAT_HLS, + "file.key", + "", + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + {}}}; + DrmInfo drmInfo; + + for (auto& test_data : testData) + { + drmInfo = createDrmInfo(test_data.method, test_data.mediaFormat, test_data.uri, + test_data.keyFormat, test_data.systemUUID); + + ASSERT_FALSE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + + std::shared_ptr widevineHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + ASSERT_TRUE(widevineHelper == nullptr); + } +} + +TEST_F(DrmHelperTests, TestPlayReadyHelperParsePssh) +{ + DrmInfo drmInfo = createDrmInfo(eMETHOD_NONE, eMEDIAFORMAT_DASH, "", "", + "9a04f079-9840-4286-ab92-e65be0885f95"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr playReadyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + const std::string expectedMetadata = "testpolicydata"; + + std::ostringstream psshSs; + psshSs + << "" + << "" + << "16bytebase64enckeydata==" + << "" << expectedMetadata << "" + << "" + << ""; + const std::string psshStr = psshSs.str(); + + ASSERT_TRUE(playReadyHelper->parsePssh((const unsigned char*)psshStr.data(), (uint32_t)psshStr.size())); + + // Check keyId and metadata, both of which should be based on the PSSH + std::vector keyId; + playReadyHelper->getKey(keyId); + + const std::string expectedKeyId = "b5f2a6d7-dae6-eeb1-b87a-77247b275ab5"; + const std::string actualKeyId = std::string(keyId.begin(), keyId.begin() + keyId.size()); + + ASSERT_EQ(expectedKeyId, actualKeyId); + ASSERT_EQ(expectedMetadata, playReadyHelper->getDrmMetaData()); + // Ensure the helper doesn't set the meta data + playReadyHelper->setDrmMetaData("content meta data that should be ignored"); + ASSERT_EQ(expectedMetadata, playReadyHelper->getDrmMetaData()); + + // Dodgy PSSH data should lead to false return value + const std::string badPssh = "somerandomdatawhichisntevenxml"; + ASSERT_FALSE(playReadyHelper->parsePssh((const unsigned char*)badPssh.data(), (uint32_t)badPssh.size())); +} + +TEST_F(DrmHelperTests, TestPlayReadyHelperParsePsshNoPolicy) +{ + // As before but with no ckm:policy in the PSSH data. + // Should be OK but lead to empty metadata + DrmInfo drmInfo = createDrmInfo(eMETHOD_NONE, eMEDIAFORMAT_DASH, "", "", + "9a04f079-9840-4286-ab92-e65be0885f95"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr playReadyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + const std::string psshStr = + "" + "" + "16bytebase64enckeydata==" + "" + ""; + + ASSERT_TRUE(playReadyHelper->parsePssh((const unsigned char*)psshStr.data(), (uint32_t)psshStr.size())); + + // Check keyId and metadata, both of which should be based on the PSSH + std::vector keyId; + playReadyHelper->getKey(keyId); + + const std::string expectedKeyId = "b5f2a6d7-dae6-eeb1-b87a-77247b275ab5"; + const std::string actualKeyId = std::string(keyId.begin(), keyId.begin() + keyId.size()); + + ASSERT_EQ(expectedKeyId, actualKeyId); + // Not expecting any metadata + ASSERT_EQ("", playReadyHelper->getDrmMetaData()); +} + +TEST_F(DrmHelperTests, TestPlayReadyHelperGenerateLicenseRequest) +{ + DrmInfo drmInfo = createDrmInfo(eMETHOD_NONE, eMEDIAFORMAT_DASH, "", "", + "9a04f079-9840-4286-ab92-e65be0885f95"); + ASSERT_TRUE(DrmHelperEngine::getInstance().hasDRM(drmInfo)); + std::shared_ptr playReadyHelper = + DrmHelperEngine::getInstance().createHelper(drmInfo); + + ChallengeInfo challengeInfo; + challengeInfo.url = "http://challengeinfourl.example"; + std::string challengeData = "OCDM_CHALLENGE_DATA"; + + challengeInfo.data = + std::make_shared( challengeData.c_str(), challengeData.length()); + challengeInfo.accessToken = "ACCESS_TOKEN"; + + // No PSSH parsed. Expecting data from the provided challenge to be given back in the request + // info + LicenseRequest licenseRequest1; + playReadyHelper->generateLicenseRequest(challengeInfo, licenseRequest1); + ASSERT_EQ(challengeInfo.url, licenseRequest1.url); + ASSERT_EQ(challengeInfo.data->getData(), licenseRequest1.payload); + + // Parse a PSSH with a ckm:policy. This should cause generateLicenseRequest to return a JSON + // payload + LicenseRequest licenseRequest2; + const std::string psshStr = + "" + "" + "16bytebase64enckeydata==" + "policy" + "" + ""; + ASSERT_TRUE(playReadyHelper->parsePssh((const unsigned char*)psshStr.data(), (uint32_t)psshStr.size())); + + playReadyHelper->generateLicenseRequest(challengeInfo, licenseRequest2); + ASSERT_EQ(challengeInfo.url, licenseRequest2.url); + + // Finally, checking the license uri override works + LicenseRequest licenseRequest3; + const std::string fixedPrLicenseUrl = "http://prlicenseserver.example"; + licenseRequest3.url = fixedPrLicenseUrl; + + playReadyHelper->generateLicenseRequest(challengeInfo, licenseRequest3); + ASSERT_EQ(fixedPrLicenseUrl, licenseRequest3.url); +} + +TEST_F(DrmHelperTests, TestCompareHelpers) +{ + std::shared_ptr playreadyHelper = + DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "", + "9a04f079-9840-4286-ab92-e65be0885f95")); + ASSERT_TRUE(playreadyHelper != nullptr); + + std::shared_ptr widevineHelper = DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "", + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed")); + ASSERT_TRUE(widevineHelper != nullptr); + + std::shared_ptr clearKeyHelperHls = + DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_HLS_MP4, "file.key", "", + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b")); + ASSERT_TRUE(clearKeyHelperHls != nullptr); + + std::shared_ptr clearKeyHelperDash = + DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "", + "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b")); + ASSERT_TRUE(clearKeyHelperDash != nullptr); + + // All helpers should equal themselves + ASSERT_TRUE(widevineHelper->compare(widevineHelper)); + ASSERT_TRUE(playreadyHelper->compare(playreadyHelper)); + ASSERT_TRUE(clearKeyHelperHls->compare(clearKeyHelperHls)); + + // Different helper types, should not equal + ASSERT_FALSE(playreadyHelper->compare(widevineHelper) || + playreadyHelper->compare(clearKeyHelperHls)); + ASSERT_FALSE(widevineHelper->compare(playreadyHelper) || + widevineHelper->compare(clearKeyHelperHls)); + + // Same helper type but one is HLS and the other is DASH, so should not equal + ASSERT_FALSE(clearKeyHelperHls->compare(clearKeyHelperDash)); + + // Comparison against null helper, should not equal, should not cause a problem + std::shared_ptr nullHelper; + ASSERT_FALSE(clearKeyHelperHls->compare(nullHelper)); + + std::shared_ptr playreadyHelper2 = + DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "", + "9a04f079-9840-4286-ab92-e65be0885f95")); + ASSERT_TRUE(playreadyHelper2 != nullptr); + + const std::string pssh1 = + "" + "" + "16bytebase64enckeydata==" + "" + ""; + playreadyHelper->parsePssh((const unsigned char*)pssh1.data(), (uint32_t)pssh1.size()); + playreadyHelper2->parsePssh((const unsigned char*)pssh1.data(), (uint32_t)pssh1.size()); + + // Same key in the PSSH data, should equal + ASSERT_TRUE(playreadyHelper->compare(playreadyHelper2)); + + const std::string pssh2 = + "" + "" + "differentbase64keydata==" + "" + ""; + playreadyHelper2->parsePssh((const unsigned char*)pssh2.data(), (uint32_t)pssh2.size()); + + // Different key in the PSSH data, should not equal + ASSERT_FALSE(playreadyHelper->compare(playreadyHelper2)); + + // Create another PR helper, same details as PR helper 1 + std::shared_ptr playreadyHelper3 = + DrmHelperEngine::getInstance().createHelper( + createDrmInfo(eMETHOD_AES_128, eMEDIAFORMAT_DASH, "file.key", "", + "9a04f079-9840-4286-ab92-e65be0885f95")); + ASSERT_TRUE(playreadyHelper3 != nullptr); + + // But no PSSH parsed for the 3rd PR helper, so shouldn't be equal + ASSERT_FALSE(playreadyHelper->compare(playreadyHelper3)); + + // Parse the same PSSH as used for 1, now should be equal + playreadyHelper3->parsePssh((const unsigned char*)pssh1.data(), (uint32_t)pssh1.size()); + ASSERT_TRUE(playreadyHelper->compare(playreadyHelper3)); + + // Finally keep the same key but add in metadata. Now PR helpers 1 & 3 shouldn't be equal + const std::string pssh3 = + "" + "" + "16bytebase64enckeydata==" + "policy" + "" + ""; + playreadyHelper3->parsePssh((const unsigned char*)pssh3.data(), (uint32_t)pssh3.size()); + ASSERT_FALSE(playreadyHelper->compare(playreadyHelper3)); +} diff --git a/middleware/test/utests/tests/DrmOcdm/DrmSessionTests.cpp b/middleware/test/utests/tests/DrmOcdm/DrmSessionTests.cpp new file mode 100644 index 000000000..481679b7d --- /dev/null +++ b/middleware/test/utests/tests/DrmOcdm/DrmSessionTests.cpp @@ -0,0 +1,291 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "DrmSessionFactory.h" +#include "DrmSessionManager.h" +#include "ClearKeyHelper.h" +#include "open_cdm.h" +#include "aampMocks.h" +#include "Fakeopencdm.h" +#include "curlMocks.h" +#include "DrmTestUtils.h" +#include "MockOpenCdm.h" +#include "MockPrivateInstanceAAMP.h" +#include "PlayerUtils.h" +using ::testing::_; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; +class DrmSessionTests : public ::testing::Test +{ +protected: + //PrivateInstanceAAMP *mAamp = nullptr; + //TestUtilDrm *mUtils = nullptr; + + // The URL AAMP uses to fetch the session token + const std::string mSessionTokenUrl = "http://localhost:50050/authService/getSessionToken"; + // A fixed session token response - any tests which require a session token can use this, + // since the manager is singleton and will only ever fetch it once. Test order is + // non-deterministic, so it's not guaranteed which test will actually trigger the fetch + const std::string mSessionTokenResponse = "{\"token\":\"SESSIONTOKEN\", \"status\":0}"; + void SetUp() override + { + //MockAampReset(); + //MockCurlReset(); + MockOpenCdmReset(); + g_mockopencdm = new NiceMock(); + //g_mockPrivateInstanceAAMP = new NiceMock(); + //mAamp = new PrivateInstanceAAMP(gpGlobalConfig); + //mUtils = new TestUtilDrm(mAamp); + } + void TearDown() override + { + //delete mUtils; + //mUtils = nullptr; + + //delete mAamp; + //mAamp = nullptr; + + //delete g_mockPrivateInstanceAAMP; + //g_mockPrivateInstanceAAMP = nullptr; + + delete g_mockopencdm; + g_mockopencdm = nullptr; + + //MockAampReset(); + //MockCurlReset(); + MockOpenCdmReset(); + } +public: +}; +TEST_F(DrmSessionTests, TestClearKeyLicenseAcquisition) +{ + // Setup Curl and OpenCDM mocks. We expect that curl_easy_perform will be called to fetch + // the key and that OpenCDM will be called to construct the session and handle the fetched key. + std::string testKeyData = "TESTKEYDATA"; + //mUtils->setupCurlPerformResponse(testKeyData); + //mUtils->setupChallengeCallbacks(); + + // Begin test + DrmInfo drmInfo; + drmInfo.method = eMETHOD_AES_128; + drmInfo.manifestURL = "http://example.com/assets/test.m3u8"; + drmInfo.keyURI = "file.key"; + std::shared_ptr drmHelper = + std::make_shared(drmInfo); + // We expect the key data to be transformed by the helper before being passed to + // opencdm_session_update. Thus we call the helper ourselves here (with the data our mock Curl + // will return) so we know what to expect. Note: testing of the transformation is done in the + // helper tests, here we just want to ensure that whatever the helper returns is what OpenCDM + // gets. + const shared_ptr expectedDrmData = + make_shared(testKeyData.c_str(), testKeyData.size()); + drmHelper->transformLicenseResponse(expectedDrmData); + EXPECT_CALL(*g_mockopencdm, opencdm_session_update(OCDM_SESSION, + MemBufEq(expectedDrmData->getData().c_str(), + expectedDrmData->getDataLength()), + expectedDrmData->getDataLength())) + .WillOnce(Return(ERROR_NONE)); + DrmSession *drmSession = mUtils->createDrmSessionForHelper(drmHelper, "org.w3.clearkey"); + ASSERT_TRUE(drmSession != nullptr); + ASSERT_STREQ("org.w3.clearkey", drmSession->getKeySystem().c_str()); + const MockCurlOpts *curlOpts = MockCurlGetOpts(); + // Key URL should been constructed based on manifestURL and keyURI from the DrmInfo + ASSERT_STREQ("http://example.com/assets/file.key", curlOpts->url); + // POST is used + ASSERT_EQ(0L, curlOpts->httpGet); +} +TEST_F(DrmSessionTests, TestMultipleSessionsSameKey) +{ + std::string testKeyData = "TESTKEYDATA"; + mUtils->setupCurlPerformResponse(testKeyData); + mUtils->setupChallengeCallbacks(); + DrmInfo drmInfo; + drmInfo.method = eMETHOD_AES_128; + drmInfo.manifestURL = "http://example.com/assets/test.m3u8"; + drmInfo.keyURI = "file.key"; + std::shared_ptr drmHelper = + std::make_shared(drmInfo); + const shared_ptr expectedDrmData = + make_shared(testKeyData.c_str(), testKeyData.size()); + drmHelper->transformLicenseResponse(expectedDrmData); + // Only 1 session update called expected, since a single session should be shared + EXPECT_CALL(*g_mockopencdm, opencdm_session_update(OCDM_SESSION, + MemBufEq(expectedDrmData->getData().c_str(), + expectedDrmData->getDataLength()), + expectedDrmData->getDataLength())) + .WillOnce(Return(ERROR_NONE)); + // 1st time around - expecting standard session creation + DrmSession *drmSession1 = mUtils->createDrmSessionForHelper(drmHelper, "org.w3.clearkey"); + ASSERT_TRUE(drmSession1 != nullptr); + ASSERT_STREQ("org.w3.clearkey", drmSession1->getKeySystem().c_str()); + // 2nd time around - expecting the existing session will be shared, so no OCDM session created + AampDRMLicenseManager *sessionManager = mUtils->getSessionManager(); + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + EXPECT_CALL(*g_mockopencdm, opencdm_create_system).Times(0); + EXPECT_CALL(*g_mockopencdm, opencdm_construct_session).Times(0); + int err =-1; + DrmSession *drmSession2 = + sessionManager->createDrmSession(drmHelper, mAamp, event , (int)eMEDIATYPE_VIDEO); + ASSERT_EQ(drmSession1, drmSession2); + // Clear out the sessions. Now a new OCDM session is expected again + sessionManager->mDrmSessionManager->clearSessionData(); + EXPECT_CALL(*g_mockopencdm, opencdm_session_update(OCDM_SESSION, + MemBufEq(expectedDrmData->getData().c_str(), + expectedDrmData->getDataLength()), + expectedDrmData->getDataLength())) + .WillOnce(Return(ERROR_NONE)); + DrmSession *drmSession3 = mUtils->createDrmSessionForHelper(drmHelper, "org.w3.clearkey"); + ASSERT_TRUE(drmSession3 != nullptr); + ASSERT_STREQ("org.w3.clearkey", drmSession3->getKeySystem().c_str()); +} +TEST_F(DrmSessionTests, TestDashPlayReadySession) +{ + string prLicenseServerURL = "http://licenseserver.example/license"; + const std::string testKeyData = "TESTKEYDATA"; + const std::string testChallengeData = "PLAYREADY_CHALLENGE_DATA"; + // Setting a PlayReady license server URL in the global config. This + // should get used to request the license + gpGlobalConfig->SetConfigValue(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, + prLicenseServerURL); + // Setting up Curl callbacks for the session token and license requests + mUtils->setupCurlPerformResponses( + {{mSessionTokenUrl, mSessionTokenResponse}, {prLicenseServerURL, testKeyData}}); + mUtils->setupChallengeCallbacks(MockChallengeData("playready.example", testChallengeData)); + // PSSH string which will get passed to the helper for parsing, so needs to be in valid format + const std::string psshStr = + "" + "" + "16bytebase64enckeydata==" + "" + ""; + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + DrmSession *drmSession = mUtils->createDashDrmSession(testKeyData, psshStr, event); + ASSERT_TRUE(drmSession != nullptr); + ASSERT_STREQ("com.microsoft.playready", drmSession->getKeySystem().c_str()); + const TestCurlResponse *licenseResponse = mUtils->getCurlPerformResponse(prLicenseServerURL); + ASSERT_EQ(1, licenseResponse->callCount); + // Check the post data set on Curl, this should be the challenge data returned by OCDM + ASSERT_EQ(testChallengeData.size(), licenseResponse->opts.postFieldSize); + ASSERT_EQ(testChallengeData, licenseResponse->opts.postFields); +} +TEST_F(DrmSessionTests, TestDashPlayReadySessionNoCkmPolicy) +{ + string prLicenseServerURL = "http://licenseserver.example/license"; + std::string testKeyData = "TESTKEYDATA"; + // Setting a PlayReady license server URL in the global config. This + // should get used to request the license + gpGlobalConfig->SetConfigValue(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, + prLicenseServerURL); + // Setting up Curl callbacks for the session token and license requests + mUtils->setupCurlPerformResponses( + {{mSessionTokenUrl, mSessionTokenResponse}, {prLicenseServerURL, testKeyData}}); + mUtils->setupChallengeCallbacks(); + // PSSH string which will get passed to the helper for parsing, so needs to be in valid format + const std::string psshStr = + "" + "" + "16bytebase64enckeydata==" + "" + ""; + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + DrmSession *drmSession = mUtils->createDashDrmSession(testKeyData, psshStr, event); + ASSERT_TRUE(drmSession != nullptr); + ASSERT_STREQ("com.microsoft.playready", drmSession->getKeySystem().c_str()); + const MockCurlOpts *curlOpts = MockCurlGetOpts(); + // Check license URL from the global config was used + std::string url = gpGlobalConfig->GetConfigValue(eAAMPConfig_PRLicenseServerUrl); + ASSERT_STREQ(url.c_str(), curlOpts->url); + // Check the post data set on Curl. Since we didn't pass any metadata (ckm:policy) in the init + // data, we expect the challenge data returned by OCDM to just be used as-is + ASSERT_TRUE(curlOpts->postFieldSize > 0); + ASSERT_STREQ("OCDM_CHALLENGE_DATA", curlOpts->postFields); +} +TEST_F(DrmSessionTests, TestSessionBadChallenge) +{ + DrmInfo drmInfo; + drmInfo.method = eMETHOD_AES_128; + drmInfo.manifestURL = "http://example.com/assets/test.m3u8"; + drmInfo.keyURI = "file.key"; + std::shared_ptr drmHelper = + std::make_shared(drmInfo); + // Cause OpenCDM to return an empty challenge. This should cause an error + mUtils->setupChallengeCallbacks(MockChallengeData("", "")); + EXPECT_CALL(*g_mockopencdm, opencdm_create_system(StrEq("org.w3.clearkey"))) + .WillOnce(Return(OCDM_SYSTEM)); + EXPECT_CALL(*g_mockopencdm, opencdm_construct_session) + .WillOnce(DoAll(SetArgPointee<9>(OCDM_SESSION), Return(ERROR_NONE))); + int err = -1; + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + DrmSession *drmSession = + mUtils->getSessionManager()->createDrmSession(drmHelper, mAamp,event, (int)eMEDIATYPE_VIDEO); + ASSERT_EQ(nullptr, drmSession); + ASSERT_EQ(AAMP_TUNE_DRM_CHALLENGE_FAILED, event->getFailure()); +} +TEST_F(DrmSessionTests, TestSessionBadLicenseResponse) +{ + DrmInfo drmInfo; + drmInfo.method = eMETHOD_AES_128; + drmInfo.manifestURL = "http://example.com/assets/test.m3u8"; + drmInfo.keyURI = "file.key"; + std::shared_ptr drmHelper = + std::make_shared(drmInfo); + // Make curl return empty data for the key. This should cause an error + mUtils->setupCurlPerformResponses({{"http://example.com/assets/file.key", ""}}); + EXPECT_CALL(*g_mockopencdm, opencdm_create_system(StrEq("org.w3.clearkey"))) + .WillOnce(Return(OCDM_SYSTEM)); + EXPECT_CALL(*g_mockopencdm, opencdm_construct_session) + .WillOnce(DoAll(SetArgPointee<9>(OCDM_SESSION), Return(ERROR_NONE))); + mUtils->setupChallengeCallbacks(); + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + int err = -1; + DrmSession *drmSession = + mUtils->getSessionManager()->createDrmSession( drmHelper, mAamp,event , (int)eMEDIATYPE_VIDEO); + ASSERT_EQ(nullptr, drmSession); + ASSERT_EQ(AAMP_TUNE_LICENCE_REQUEST_FAILED, event->getFailure()); +} +TEST_F(DrmSessionTests, TestDashSessionBadPssh) +{ + std::string testKeyData = "TESTKEYDATA"; + // Pass a bad PSSH string. This should cause an error + const std::string psshStr = "bad data with no KID"; + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + void *ptr= static_cast(&event); + int err =-1; + DrmSession *drmSession = mUtils->getSessionManager()->mDrmSessionManager->createDrmSession(err, + "9a04f079-9840-4286-ab92-e65be0885f95", eMEDIAFORMAT_DASH, + (const unsigned char *)psshStr.c_str(), psshStr.length(), eMEDIATYPE_VIDEO, mAamp, ptr); + ASSERT_EQ(nullptr, drmSession); + AAMPTuneFailure val = mUtils->getSessionManager()->MapDrmToAampTuneFailure((DrmTuneFailure)err); +printf("val = %d", val); + if(err != -1) + { + event->setFailure(val); + } + ASSERT_EQ(AAMP_TUNE_CORRUPT_DRM_METADATA, event->getFailure()); +} diff --git a/middleware/test/utests/tests/DrmOcdm/DrmTestsRun.cpp b/middleware/test/utests/tests/DrmOcdm/DrmTestsRun.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/DrmOcdm/DrmTestsRun.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/DrmOcdm/DrmUtilsTests.cpp b/middleware/test/utests/tests/DrmOcdm/DrmUtilsTests.cpp new file mode 100644 index 000000000..dea99b26a --- /dev/null +++ b/middleware/test/utests/tests/DrmOcdm/DrmUtilsTests.cpp @@ -0,0 +1,112 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "DrmUtils.h" +#include "PlayerUtils.h" + +class DrmUtilsTests : public ::testing::Test +{ + +protected: + void SetUp() override + { + } + + void TearDown() override + { + } + +public: +}; + +TEST_F(DrmUtilsTests, TestGetAbsoluteKeyUri) +{ + struct GetAbsoluteKeyUriTestData + { + std::string manifestUrl; + std::string keyUri; + std::string expectedUri; + }; + + const std::vector testData = { + {"http://stream.example/manifest.m3u8", "basic1.key", "http://stream.example/basic1.key"}, + {"http://stream.example/manifest.m3u8", "basic2", "http://stream.example/basic2"}, + {"http://stream.example/manifest", "basic3", "http://stream.example/basic3"}, + {"http://stream.example/assets/hls/manifest.m3u8", "basic4.key", + "http://stream.example/assets/hls/basic4.key"}, + + // Relative path which refers to a parent directory. We copy ../ instances as-is, these can + // be resolved by curl + {"http://stream.example/asset/1080p/manifest.m3u8", "../relkey1.key", + "http://stream.example/asset/1080p/../relkey1.key"}, + {"http://stream.example/asset/1080p/../manifest.m3u8", "../relkey2.key", + "http://stream.example/asset/1080p/../../relkey2.key"}, + + // Port number included + {"http://stream.example:1234/manifest.m3u8", "port1.key", + "http://stream.example:1234/port1.key"}, + + // HTTPS + {"https://stream.example/manifest.m3u8", "secure1.key", + "https://stream.example/secure1.key"}, + + // Absolute path reference on key URI + {"http://stream.example/manifest.m3u8", "/absref1.key", + "http://stream.example/absref1.key"}, + {"http://stream.example/assets/hls/manifest.m3u8", "/absref2.key", + "http://stream.example/absref2.key"}, + + // Absolute key URIs + {"http://stream.example/manifest.m3u8", "http://key.example/abs1.key", + "http://key.example/abs1.key"}, + {"", "http://key.example/abs2.key", "http://key.example/abs2.key"}, + + // Keys with arguments + {"http://stream.example/manifest.m3u8", "arg1.key?a=test", + "http://stream.example/arg1.key?a=test"}, + {"http://stream.example/manifest.m3u8", "arg2.key?a=http://", + "http://stream.example/arg2.key?a=http://"}, + + // Only domain present on manifest URL (not normally expected, but should be handled OK) + {"http://stream.example", "domonly1.key", "http://stream.example/domonly1.key"}, + {"http://stream.example/", "domonly2.key", "http://stream.example/domonly2.key"}, + {"http://stream.example", "/domonly3.key", "http://stream.example/domonly3.key"}, + + // Other protocols for the key (HTTP expected, but this utility is expected to be protocol + // agnostic) + {"http://example/manifest.m3u8", "file://prot1.key", "file://prot1.key"}, + // Still a valid scheme (- . and + are allowed) + {"http://example/manifest.m3u8", "a-b.c+d://prot2.key", "a-b.c+d://prot2.key"}, + // Not a valid scheme - will default to relative + {"http://example/manifest.m3u8", "#abc!://prot3.key", "http://example/#abc!://prot3.key"}, + // Capitalised protocol accepted + {"http://example/manifest.m3u8", "HTTP://key.example/prot4.key", + "HTTP://key.example/prot4.key"}, + }; + + for (auto test : testData) + { + std::string keyURI; + ResolveURL(keyURI, test.manifestUrl, test.keyUri.c_str(), false); + ASSERT_EQ(test.expectedUri, keyURI); + } +} diff --git a/middleware/test/utests/tests/DrmSecureClient/CMakeLists.txt b/middleware/test/utests/tests/DrmSecureClient/CMakeLists.txt new file mode 100644 index 000000000..c2bfa5a29 --- /dev/null +++ b/middleware/test/utests/tests/DrmSecureClient/CMakeLists.txt @@ -0,0 +1,112 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +pkg_check_modules(UUID REQUIRED uuid) + +set(PLAYER_ROOT "../../../..") +set(UTESTS_ROOT "../..") +set(DRM_ROOT ${UTESTS_ROOT}/drm) +set(SEC_CLIENT_ROOT ${PLAYER_ROOT}/../secclient) +set(EXEC_NAME DrmSecureClient) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DREALTEKCE=1") + +# The Secure Client header files must exist in aamp/../secclient/ for these tests to build and run +file(GLOB SEC_CLIENT_HEADERS "${SEC_CLIENT_ROOT}/*") + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${UUID_INCLUDE_DIRS}) + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/drm ${PLAYER_ROOT}/drm/helper ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/middleware/subtitle ${PLAYER_ROOT}/downloader ${PLAYER_ROOT}/isobmff ${PLAYER_ROOT}/middleware/subtec/subtecparser ${PLAYER_ROOT}/middleware/playerjsonobject ${PLAYER_ROOT}/middleware/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${DRM_ROOT}) +include_directories(${DRM_ROOT}/ocdm) +include_directories(${PLAYER_ROOT}/../secclient) +include_directories(${DRM_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/externals/contentsecuritymanager) +include_directories(${PLAYER_ROOT}/playerLogManager) + +message(GSTREAMER_INCLUDE_DIRS=${GSTREAMER_INCLUDE_DIRS}) + +set(TEST_SOURCES DrmTestsRun.cpp + DrmSessionTests.cpp) + +if(SEC_CLIENT_HEADERS) + message("Found Secure Client headers - building ${EXEC_NAME}") + + set(MOCK_SOURCES ${DRM_ROOT}/mocks/curlMocks.c + ${PLAYER_ROOT}test/utests/drm/mocks/gstMocks.c + ${DRM_ROOT}/mocks/pthreadMocks.c + ${DRM_ROOT}/mocks/openSslMocks.c + ${DRM_ROOT}/mocks/Fakeopencdm.cpp + ${DRM_ROOT}/mocks/Fakesecclient.cpp) + + set(PLAYER_SOURCES ${PLAYER_ROOT}/externals/contentsecuritymanager/ThunderAccessPlayer.cpp + ${PLAYER_ROOT}/externals/contentsecuritymanager/ContentSecurityManager.cpp + ${PLAYER_ROOT}/drm/DrmSessionFactory.cpp + ${PLAYER_ROOT}/drm/helper/DrmHelper.cpp + ${PLAYER_ROOT}/drm/helper/DrmHelperFactory.cpp + ${PLAYER_ROOT}/drm/DrmSessionManager.cpp + ${PLAYER_ROOT}/drm/DrmSession.cpp + ${PLAYER_ROOT}/drm/ocdm/opencdmsessionadapter.cpp + ${PLAYER_ROOT}/drm/ocdm/opencdmsessionadapter.cpp + ${PLAYER_ROOT}/drm/ocdm/OcdmBasicSessionAdapter.cpp + ${PLAYER_ROOT}/drm/ocdm/OcdmGstSessionAdapter.cpp + ${PLAYER_ROOT}/drm/HlsOcdmBridge.cpp + ${PLAYER_ROOT}/drm/helper/ClearKeyHelper.cpp + ${PLAYER_ROOT}/drm/helper/WidevineDrmHelper.cpp + ${PLAYER_ROOT}/drm/helper/PlayReadyHelper.cpp) + + add_definitions(-DUSE_SHARED_MEMORY) + add_definitions(-DUSE_OPENCDM -DUSE_OPENCDM_ADAPTER) + add_definitions(-DUSE_THUNDER_OCDM_API_0_2) + add_definitions(-DUSE_SECCLIENT) + + add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${MOCK_SOURCES} + ${PLAYER_SOURCES}) +else() + get_filename_component(SEC_CLIENT_ROOT_ABS ${SEC_CLIENT_ROOT} ABSOLUTE) + message(WARNING "Secure Client headers not present in ${SEC_CLIENT_ROOT_ABS}, so skipping ${EXEC_NAME}.") + + add_executable(${EXEC_NAME} + ${TEST_SOURCES}) +endif() + + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + + +if (CMAKE_XCODE_BUILD_SYSTEM) +# XCode schema target +xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} ${LIBCJSON_LINK_LIBRARIES} ${UUID_LINK_LIBRARIES} pthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/DrmSecureClient/DrmSessionTests.cpp b/middleware/test/utests/tests/DrmSecureClient/DrmSessionTests.cpp new file mode 100644 index 000000000..20e58be3d --- /dev/null +++ b/middleware/test/utests/tests/DrmSecureClient/DrmSessionTests.cpp @@ -0,0 +1,152 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef USE_SECCLIENT +#include "aampDrmSessionFactory.h" +#include "DrmSessionManager.h" +#include "AampClearKeyHelper.h" +#include "open_cdm.h" + +#include "aampMocks.h" + +#include "Fakeopencdm.h" +#include "curlMocks.h" + +#include "DrmTestUtils.h" + +#include "MockOpenCdm.h" +#include "MockSecureClient.h" +#include "MockPrivateInstanceAAMP.h" +#endif // USE_SECCLIENT + +using ::testing::_; +using ::testing::DoAll; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; + + +class AampDrmSessionTests : public ::testing::Test +{ +#ifdef USE_SECCLIENT +protected: + PrivateInstanceAAMP *mAamp = nullptr; + AampLogManager mLogging; + TestUtilDrm *mUtils = nullptr; + + // The URL AAMP uses to fetch the session token + const std::string mSessionTokenUrl = "http://localhost:50050/authService/getSessionToken"; + // A fixed session token response - any tests which require a session token can use this, + // since the manager is singleton and will only ever fetch it once. Test order is + // non-deterministic, so it's not guaranteed which test will actually trigger the fetch + const std::string mSessionTokenResponse = "{\"token\":\"SESSIONTOKEN\", \"status\":0}"; + + void SetUp() override + { + MockAampReset(); + MockCurlReset(); + MockOpenCdmReset(); + + g_mockopencdm = new NiceMock(); + g_mocksecclient = new NiceMock(); + g_mockPrivateInstanceAAMP = new NiceMock(); + mAamp = new PrivateInstanceAAMP(gpGlobalConfig); + mUtils = new TestUtilDrm(mAamp); + } + + void TearDown() override + { + delete mUtils; + mUtils = nullptr; + + delete mAamp; + mAamp = nullptr; + + delete g_mockPrivateInstanceAAMP; + g_mockPrivateInstanceAAMP = nullptr; + + delete g_mocksecclient; + g_mocksecclient = nullptr; + + delete g_mockopencdm; + g_mockopencdm = nullptr; + + MockAampReset(); + MockCurlReset(); + MockOpenCdmReset(); + } + +public: +#endif // USE_SECCLIENT +}; + +TEST_F(AampDrmSessionTests, TestDashPlayReadySessionSecClient) +{ +#ifdef USE_SECCLIENT + std::string prLicenseServerURL = "http://licenseserver.example/license"; + std::string expectedServiceHostUrl = "licenseserver.example"; + + // Setting a PlayReady license server URL in the global config. This + // should get used to request the license + gpGlobalConfig->SetConfigValue(AAMP_APPLICATION_SETTING, eAAMPConfig_PRLicenseServerUrl, + prLicenseServerURL); + + // Setting up Curl repsonse for the session token + mUtils->setupCurlPerformResponses({{mSessionTokenUrl, mSessionTokenResponse}}); + + mUtils->setupChallengeCallbacks(); + + // PSSH string which will get passed to the helper for parsing, so needs to be in valid format + const std::string psshStr = + "" + "" + "16bytebase64enckeydata==" + "policy" + "" + ""; + + std::string expectedKeyData = "TESTSECKEYDATA"; + size_t respLength = expectedKeyData.length(); + const char *respPtr = expectedKeyData.c_str(); + + // The license should be acquired using the secure client, rather than curl + EXPECT_CALL(*g_mocksecclient, + SecClient_AcquireLicense(StrEq(expectedServiceHostUrl.c_str()), _, _)) + .WillOnce(DoAll(SetArgPointee<1>(const_cast(respPtr)), SetArgPointee<2>(respLength), + Return(0))); + + DrmMetaDataEventPtr event = mUtils->createDrmMetaDataEvent(); + AampDrmSession *drmSession = mUtils->createDashDrmSession(expectedKeyData, psshStr, event); + ASSERT_TRUE(drmSession != nullptr); + ASSERT_STREQ("com.microsoft.playready", drmSession->getKeySystem().c_str()); +#else + GTEST_SKIP() << "Skipping test due to Secure Client headers not present"; +#endif // USE_SECCLIENT +} diff --git a/middleware/test/utests/tests/DrmSecureClient/DrmTestsRun.cpp b/middleware/test/utests/tests/DrmSecureClient/DrmTestsRun.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/DrmSecureClient/DrmTestsRun.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/DrmUrlTests/CMakeLists.txt b/middleware/test/utests/tests/DrmUrlTests/CMakeLists.txt new file mode 100755 index 000000000..7c9751db5 --- /dev/null +++ b/middleware/test/utests/tests/DrmUrlTests/CMakeLists.txt @@ -0,0 +1,76 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../..") +set(UTESTS_ROOT "../..") +set(DRM_ROOT ${UTESTS_ROOT}/drm) +set(EXEC_NAME DrmUrlTests) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DREALTEKCE=1") + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/test/aampcli ${PLAYER_ROOT}/drm ${PLAYER_ROOT}/drm/helper ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/middleware/subtitle ${PLAYER_ROOT}/downloader ${PLAYER_ROOT}/isobmff ${PLAYER_ROOT}/middleware/subtec/subtecparser ${PLAYER_ROOT}/middleware/playerjsonobject ${PLAYER_ROOT}/middleware/subtec/libsubtec ${PLAYER_ROOT}/middleware/externals/contentsecuritymanager) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories(${PLAYER_ROOT}/middleware) +include_directories(${PLAYER_ROOT}/middleware/externals) +include_directories(${PLAYER_ROOT}/middleware/playerLogManager) +include_directories(${PLAYER_ROOT}/middleware/baseConversion) +include_directories(${PLAYER_ROOT}/middleware/drm) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${UTESTS_ROOT}/mocks) +include_directories(${DRM_ROOT}) +include_directories(${PLAYER_ROOT}/drm) +include_directories(${DRM_ROOT}/ocdm) +include_directories(${DRM_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/../base16_64) + + +message(GSTREAMER_INCLUDE_DIRS=${GSTREAMER_INCLUDE_DIRS}) + +set(TEST_SOURCES DrmTestsRun.cpp + DrmSessionTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp) + +add_definitions(-DAAMP_SIMULATOR_BUILD) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes ${OS_LD_FLAGS} -pthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${LIBCJSON_LINK_LIBRARIES} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/DrmUrlTests/DrmSessionTests.cpp b/middleware/test/utests/tests/DrmUrlTests/DrmSessionTests.cpp new file mode 100644 index 000000000..405a6a7b4 --- /dev/null +++ b/middleware/test/utests/tests/DrmUrlTests/DrmSessionTests.cpp @@ -0,0 +1,211 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "DrmJsonObject.h" + + +/** + * @brief Get formatted URL of license server + * + * @param[in] url URL of license server + * @return formatted url for secclient license acquisition. + */ +std::string playerGetFormattedLicenseServerURL( const std::string &url) +{ + size_t startpos = 0; + size_t len = url.length(); + if( url.rfind( "https://",0)==0 ) + { + startpos = 8; + } + else if( url.rfind( "http://",0)==0 ) + { + startpos = 7; + } + if( startpos!=0 ) + { + size_t endpos = url.find('/', startpos); + if( endpos != std::string::npos ) + { + len = endpos - startpos; + } + } + return url.substr(startpos, len); +} + +class DrmSessionTests : public ::testing::Test { +protected: + + void SetUp() override { + + } + + void TearDown() override { + + } +}; + +TEST_F(DrmSessionTests, HandlesRandomString) { + std::string url = "6f4dssfg564"; + std::string expected = "6f4dssfg564"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesRandomString2) { + std::string url = "super/bad"; + std::string expected = "super/bad"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesHttpUrl) { + std::string url = "http://example.com/path/to/resource"; + std::string expected = "example.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesHttpsUrl) { + std::string url = "http://example.com/path/to/resource"; + std::string expected = "example.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesHttpsUrl2) { + std::string url = "https://example.com"; + std::string expected = "example.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesHttpsUrlWithWWW) { + std::string url = "www.drmexample.com/path/to/resource"; + std::string expected = "www.drmexample.com/path/to/resource"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithNoScheme) { + std::string url = "drmexample.com/path/to/resource"; + std::string expected = "drmexample.com/path/to/resource"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithTrailingSlash) { + std::string url = "https://example.com"; + std::string expected = "example.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithMultipleSlashes) { + std::string url = "https://drmexample.com//path/to/resource"; + std::string expected = "drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithoutPath) { + std::string url = "https://drmexample.com"; + std::string expected = "drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithoutPathWithWWW) { + std::string url = "www.drmexample.com"; + std::string expected = "www.drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithOnlyScheme) { + std::string url = "https://"; + std::string expected = ""; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesEmptyUrl) { + std::string url = ""; + std::string expected = ""; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithPort) { + std::string url = "https://drmexample.com:8080"; + std::string expected = "drmexample.com:8080"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithPortWithWWW) { + std::string url = "www.drmexample.com:8080"; + std::string expected = "www.drmexample.com:8080"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithPortWithPath) { + std::string url = "https://drmexample.com:8080/path"; + std::string expected = "drmexample.com:8080"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithPortWithPathWithWWW) { + std::string url = "www.drmexample.com:8080/path"; + std::string expected = "www.drmexample.com:8080/path"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithQueryParameters) { + std::string url = "https://drmexample.com/path?query=param"; + std::string expected = "drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, HandlesUrlWithFragment) { + std::string url = "https://drmexample.com/path#fragment"; + std::string expected = "drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, UrlEncodingInDomain) { + std::string url = "https://drmexample.com%20with%20space"; + std::string expected = "drmexample.com%20with%20space"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; + +TEST_F(DrmSessionTests, UrlEncodingInPath) { + std::string url = "http://drmexample.com/path/to/%20resource"; + std::string expected = "drmexample.com"; + std::string result = playerGetFormattedLicenseServerURL(url); + EXPECT_EQ(result, expected); +}; diff --git a/middleware/test/utests/tests/DrmUrlTests/DrmTestsRun.cpp b/middleware/test/utests/tests/DrmUrlTests/DrmTestsRun.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/DrmUrlTests/DrmTestsRun.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/GstHandlerControlTests/CMakeLists.txt b/middleware/test/utests/tests/GstHandlerControlTests/CMakeLists.txt new file mode 100644 index 000000000..753e2940b --- /dev/null +++ b/middleware/test/utests/tests/GstHandlerControlTests/CMakeLists.txt @@ -0,0 +1,62 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME GstHandlerControlTests) + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/subtitle) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/playerJsonobject) +include_directories(${PLAYER_ROOT}/playerLogManager) + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LibXml2_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${LIBCJSON_INCLUDE_DIRS}) + + +set(TEST_SOURCES GstHandlerControlTests.cpp + GstHandlerControlPlayerTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/GstHandlerControl.cpp ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes -pthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlPlayerTests.cpp b/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlPlayerTests.cpp new file mode 100644 index 000000000..f51285d11 --- /dev/null +++ b/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlPlayerTests.cpp @@ -0,0 +1,26 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlTests.cpp b/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlTests.cpp new file mode 100644 index 000000000..541cc1637 --- /dev/null +++ b/middleware/test/utests/tests/GstHandlerControlTests/GstHandlerControlTests.cpp @@ -0,0 +1,195 @@ + +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include +#include "GstHandlerControl.h" + +class FunctionalTests : public ::testing::Test { +public: + GstHandlerControl* mControl; + void SetUp() override + { + mControl = new GstHandlerControl; + } + + void TearDown() override + { + delete mControl; + } +}; + +TEST_F(FunctionalTests, instancesRunning) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + + std::vector helpers; + constexpr size_t MAX_INSTANCES = 10; + for(int i=1; i<=MAX_INSTANCES; i++) + { + helpers.push_back(mControl->getScopeHelper()); + ASSERT_EQ(i, mControl->instancesRunning()); + } + + while(helpers.size()) + { + helpers.pop_back(); + ASSERT_EQ(helpers.size(), mControl->instancesRunning()); + } +} + +TEST_F(FunctionalTests, returnStraightAway) +{ + ASSERT_FALSE(mControl->getScopeHelper().returnStraightAway()); + mControl->disable(); + ASSERT_TRUE(mControl->getScopeHelper().returnStraightAway()); + mControl->enable(); + ASSERT_FALSE(mControl->getScopeHelper().returnStraightAway()); + mControl->waitForDone(0, "test"); + ASSERT_TRUE(mControl->getScopeHelper().returnStraightAway()); + mControl->enable(); + ASSERT_FALSE(mControl->getScopeHelper().returnStraightAway()); +} + + +TEST_F(FunctionalTests, waitForDoneSingle) +{ + { + auto scopedHelper=mControl->getScopeHelper(); + ASSERT_FALSE(mControl->waitForDone(0, "test")); + } + + ASSERT_TRUE(mControl->waitForDone(0, "test")); +} + +TEST_F(FunctionalTests, waitForDoneMultiple) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + ASSERT_TRUE(mControl->waitForDone(0, "test")); + + std::vector helpers; + constexpr size_t MAX_INSTANCES = 10; + for(int i=1; i<=MAX_INSTANCES; i++) + { + helpers.push_back(mControl->getScopeHelper()); + ASSERT_FALSE(mControl->waitForDone(0, "test")); + } + + while(helpers.size()) + { + ASSERT_FALSE(mControl->waitForDone(0, "test")); + helpers.pop_back(); + } + + ASSERT_TRUE(mControl->waitForDone(0, "test")); +} + +TEST_F(FunctionalTests, moveAssignToDefault) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + { + GstHandlerControl::ScopeHelper helperA = mControl->getScopeHelper(); + ASSERT_EQ(1, mControl->instancesRunning()); + { + GstHandlerControl::ScopeHelper helperB; + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_TRUE(helperB.returnStraightAway()); + ASSERT_FALSE(helperA.returnStraightAway()); + + helperB = std::move(helperA); + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_TRUE(helperA.returnStraightAway()); + ASSERT_FALSE(helperB.returnStraightAway()); + } + } + + ASSERT_EQ(0, mControl->instancesRunning()); +} + +TEST_F(FunctionalTests, moveAssignOverwriteWithDefault) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + + GstHandlerControl::ScopeHelper helperC = mControl->getScopeHelper(); + ASSERT_EQ(1, mControl->instancesRunning()); + + GstHandlerControl::ScopeHelper helperD; + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_TRUE(helperD.returnStraightAway()); + ASSERT_FALSE(helperC.returnStraightAway()); + + helperC = std::move(helperD); + ASSERT_EQ(0, mControl->instancesRunning()); + ASSERT_TRUE(helperD.returnStraightAway()); + ASSERT_TRUE(helperC.returnStraightAway()); +} + +TEST_F(FunctionalTests, moveAssignOverwriteValidWithValid) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + { + GstHandlerControl::ScopeHelper helperE = mControl->getScopeHelper(); + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_FALSE(helperE.returnStraightAway()); + + + GstHandlerControl::ScopeHelper helperF = mControl->getScopeHelper(); + ASSERT_EQ(2, mControl->instancesRunning()); + ASSERT_FALSE(helperE.returnStraightAway()); + ASSERT_FALSE(helperF.returnStraightAway()); + + helperE = std::move(helperF); + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_FALSE(helperE.returnStraightAway()); + ASSERT_TRUE(helperF.returnStraightAway()); + } + + ASSERT_EQ(0, mControl->instancesRunning()); +} + + +TEST_F(FunctionalTests, moveConstruct) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + + GstHandlerControl::ScopeHelper helperA = mControl->getScopeHelper(); + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_FALSE(helperA.returnStraightAway()); + + { + GstHandlerControl::ScopeHelper helperB{std::move(helperA)}; + ASSERT_EQ(1, mControl->instancesRunning()); + ASSERT_TRUE(helperA.returnStraightAway()); + ASSERT_FALSE(helperB.returnStraightAway()); + } + + ASSERT_EQ(0, mControl->instancesRunning()); + ASSERT_TRUE(helperA.returnStraightAway()); +} + +TEST_F(FunctionalTests, defaultConstruct) +{ + ASSERT_EQ(0, mControl->instancesRunning()); + + { + GstHandlerControl::ScopeHelper helper; + ASSERT_EQ(0, mControl->instancesRunning()); + } + + ASSERT_EQ(0, mControl->instancesRunning()); +} diff --git a/middleware/test/utests/tests/GstPlayer/CMakeLists.txt b/middleware/test/utests/tests/GstPlayer/CMakeLists.txt new file mode 100644 index 000000000..0a99cf983 --- /dev/null +++ b/middleware/test/utests/tests/GstPlayer/CMakeLists.txt @@ -0,0 +1,74 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME PlayerGstPlayer) + +include_directories(${PLAYER_ROOT}) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${UTESTS_ROOT}/fakes) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMERBASE_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${UTESTS_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${PLAYER_ROOT}/../test/gstTestHarness) + +message(GSTREAMER_INCLUDE_DIRS=${GSTREAMER_INCLUDE_DIRS}) + +set(TEST_SOURCES PauseOnPlaybackTests.cpp + FunctionalTests.cpp + GstPlayerTests.cpp + ) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/InterfacePlayerRDK.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/externals/PlayerExternalsInterface.cpp + ${PLAYER_ROOT}/externals/PlayerExternalUtils.cpp) + +set(FAKE_SOURCES ${UTESTS_ROOT}/fakes/FakeGStreamer.cpp + ${UTESTS_ROOT}fakes/FakeGLib.cpp) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES} + ${FAKE_SOURCES}) +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes -lpthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES} ${GSTREAMER_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/GstPlayer/FunctionalTests.cpp b/middleware/test/utests/tests/GstPlayer/FunctionalTests.cpp new file mode 100644 index 000000000..90f653fa1 --- /dev/null +++ b/middleware/test/utests/tests/GstPlayer/FunctionalTests.cpp @@ -0,0 +1,636 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "InterfacePlayerRDK.h" +#include "InterfacePlayerPriv.h" +#include "GstUtils.h" +#include "MockGStreamer.h" +#include "MockGLib.h" +#include "MockGstHandlerControl.h" +#include "MockPlayerUtils.h" + +#define GST_NORMAL_PLAY_RATE 1 + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::_; +using ::testing::Address; +using ::testing::DoAll; +using ::testing::SetArgPointee; +using ::testing::NotNull; +using ::testing::SaveArgPointee; +using ::testing::SaveArg; +using ::testing::Pointer; +using ::testing::Matcher; + +class GstPlayerTests : public ::testing::Test +{ + +protected: + bool isPipelineSetup = false; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstBus bus = {}; + GstQuery query = {}; + InterfacePlayerRDK *mInterfaceGstPlayer; + + void SetUp() override + { + g_mockPlayerUtils = new MockPlayerUtils(); + g_mockGStreamer = new NiceMock(); + g_mockGLib = new MockGLib(); + g_mockGstHandlerControl= new MockGstHandlerControl(); + } + + void TearDown() override + { + delete g_mockGstHandlerControl; + g_mockGstHandlerControl= nullptr; + + delete g_mockGLib; + g_mockGLib = nullptr; + + delete g_mockGStreamer; + g_mockGStreamer = nullptr; + + delete g_mockPlayerUtils; + g_mockPlayerUtils = nullptr; + + } + +public: + /* Table with different parameter sets to be passed into mAAMPGstPlayer->Configure(...) */ + typedef struct + { + GstStreamOutputFormat auxFormat; + bool bESChangeStatus; + bool forwardAudioToAux; + bool setReadyAfterPipelineCreation; + bool enableRectangleProperty; + bool usingWesteros; + bool usingRialto; + } Config_Params; + + static gboolean ProgressCallbackOnTimeout(gpointer user_data) + { + return FALSE; + } + + void ConstructAMPGstPlayer() + { + std::string debug_level{"test_level"}; + gboolean reset{TRUE}; + + mInterfaceGstPlayer = new InterfacePlayerRDK(); + + //init callback to avoid bad_function_call error + mInterfaceGstPlayer->TearDownCallback([this](bool status, int mediaType) { + if (status) { + }}); + mInterfaceGstPlayer->StopCallback([this](bool status) + { + printf("StopCallback status: %d\n", status); + }); + +/* mInterfaceGstPlayer->busMessageCallback([this](BusEventData data) + { + printf("busMessageCallback called\n"); + }); +*/ + mInterfaceGstPlayer->RegisterBusEvent([this](const BusEventData& event) { + printf("busMessageCallback called\n"); + }); + } + + void DestroyAMPGstPlayer() + { + if (isPipelineSetup) + { + // AAMPGstPlayer::DestroyPipeline() + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(&gst_element_pipeline)) + .Times(1); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(&bus)) + .Times(1); + EXPECT_CALL(*g_mockGStreamer, gst_mini_object_unref(GST_MINI_OBJECT_CAST(&query))) + .Times(1); /* AKA gst_query_unref()*/ + } + + delete mInterfaceGstPlayer; + mInterfaceGstPlayer = nullptr; + } + + void SetupPipeline(Config_Params *setup) + { + GstElement gst_element_bin = {.object = {.name = (gchar *)"bin"}}; + GstElement gst_element_audsrvsink = {.object = {.name = (gchar *)"audosrv"}}; + GstElement westeros_video_sink = {.object = {.name = (gchar *)"westerossink0"}}; + GstElement rialto_video_sink = {.object = {.name = (gchar *)"rialtomsevideosink0"}}; + GstElement brcm_video_sink = {.object = {.name = (gchar *)"brcmvideosink0"}}; + GstElement audio_sink = {.object = {.name = (gchar *)"amlhalasink0"}}; + GstElement *p_video_sink = nullptr; + GstElement *p_audio_sink = &audio_sink; + GstPipeline *pipeline = GST_PIPELINE(&gst_element_pipeline); + + GstBusFunc bus_message_func = nullptr; + GstBusSyncHandler bus_sync_func = nullptr; + + isPipelineSetup = true; + if (setup->usingRialto) + { + p_video_sink = &rialto_video_sink; + } + else if (setup->usingWesteros) + { + p_video_sink = &westeros_video_sink; + } + else + { + p_video_sink = &brcm_video_sink; + } + + mInterfaceGstPlayer->m_gstConfigParam->useWesterosSink = setup->usingWesteros; + mInterfaceGstPlayer->m_gstConfigParam->useRialtoSink = setup->usingRialto; + mInterfaceGstPlayer->m_gstConfigParam->enableRectPropertyCfg = setup->enableRectangleProperty; + + // CreatePipeline() + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq("testPipeline"))) + .WillOnce(Return(&gst_element_pipeline)); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(pipeline)) + .WillOnce(Return(&bus)); + + // Save the bus_message function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_add_watch(&bus, NotNull(), mInterfaceGstPlayer)) + .WillOnce(DoAll( + SaveArgPointee<1>(&bus_message_func), + Return(0))); + + // Save the bus_sync_handler function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_set_sync_handler(&bus, NotNull(), mInterfaceGstPlayer, NULL)) + .WillOnce(SaveArgPointee<1>(&bus_sync_func)); + + EXPECT_CALL(*g_mockGStreamer, gst_query_new_position(GST_FORMAT_TIME)) + .WillOnce(Return(&query)); + // End CreatePipeline() + + if (setup->setReadyAfterPipelineCreation) + { + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_READY), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_READY)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + } + else + { + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + } + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(_, NULL)) + .WillRepeatedly(Return(&gst_element_bin)); + if (setup->forwardAudioToAux) + { + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("audsrvsink"), NULL)) + .WillOnce(Return(&gst_element_audsrvsink)); + } + + if (setup->usingRialto) + { + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("rialtomsevideosink"), NULL)) + .WillRepeatedly(Return(p_video_sink)); + // IsGstreamerSubsEnabled fake impl returns false and hence EXPECT_CALL for subtitle pipeline is not required + } + else if (setup->usingWesteros) + { + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("westerossink"), NULL)) + .WillRepeatedly(Return(p_video_sink)); + } + else + { + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("brcmvideosink"), NULL)) + .WillRepeatedly(Return(p_video_sink)); + } + + EXPECT_CALL(*g_mockGStreamer, gst_bin_add(GST_BIN(pipeline), NotNull())) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PLAYING)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_VIDEO_ES_H264, + GST_FORMAT_AUDIO_ES_AAC, + setup->auxFormat, + GST_FORMAT_SUBTITLE_WEBVTT, + setup->bESChangeStatus, + setup->forwardAudioToAux, + setup->setReadyAfterPipelineCreation, + false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + ASSERT_TRUE(bus_sync_func != nullptr); + ASSERT_TRUE(bus_message_func != nullptr); + + GstMessage sync_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(p_video_sink) }; + + EXPECT_CALL(*g_mockGstHandlerControl, isEnabled()) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&sync_message),NotNull(),NotNull(),_)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED))); + + if (setup->enableRectangleProperty) + { + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("rectangle"), Matcher(_))).Times(1); + } + else + { + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("rectangle"), Matcher(_))).Times(0); + } + if (setup->usingRialto) + { + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("zoom-mode"), Matcher(_))).Times(0); + } + else + { + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("zoom-mode"), Matcher(GST_VIDEO_ZOOM_NONE))).Times(1); + } + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("show-video-window"), Matcher(true))).Times(1); + + EXPECT_CALL(*g_mockGStreamer, gst_object_replace(NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<0>(GST_OBJECT(p_video_sink)), + Return(1))); + + // Call the bus_sync_handler function with video sink READY -> PAUSED + bus_sync_func(&bus, &sync_message, mInterfaceGstPlayer); + + sync_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(p_audio_sink) }; + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&sync_message),NotNull(),NotNull(),_)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED))); + + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("volume"), Matcher(1.0))).Times(1); + + EXPECT_CALL(*g_mockGStreamer, gst_object_replace(NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<0>(GST_OBJECT(p_audio_sink)), + Return(1))); + + // Call the bus_sync_handler function with audio sink READY -> PAUSED + bus_sync_func(&bus, &sync_message, mInterfaceGstPlayer); + + GstMessage bus_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(&gst_element_pipeline) }; + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&bus_message),NotNull(),NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED), + SetArgPointee<3>(GST_STATE_NULL))); + + // Call the bus_message function + bus_message_func(&bus, &bus_message, mInterfaceGstPlayer); + } + +}; + +TEST_F(GstPlayerTests, Constructor) +{ + ConstructAMPGstPlayer(); + DestroyAMPGstPlayer(); +} + + +// typedef struct +// { +// GstStreamOutputFormat auxFormat; +// bool bESChangeStatus; +// bool forwardAudioToAux; +// bool setReadyAfterPipelineCreation; +// bool enableRectangleProperty; +// bool usingWesteros; +// bool usingRialto; +// } Config_Params; + +static GstPlayerTests::Config_Params tbl[] = { + // focus on Rialto only + {GST_FORMAT_INVALID, false, false, false, false, false, true }, +// {GST_FORMAT_INVALID, false, false, false, false, true, false }, +// {GST_FORMAT_INVALID, false, false, false, false, true, true }, +// {GST_FORMAT_INVALID, false, false, false, true, true, false }, +// {GST_FORMAT_AUDIO_ES_AC3, true, true, true, false, true, false } +}; + +// Parameter test class, for running same tests with different settings + +class GstPlayerTestsP : public GstPlayerTests, + public testing::WithParamInterface +{ +}; + +TEST_P(GstPlayerTestsP, Configure) +{ + int idx = GetParam(); + ASSERT_TRUE(idx < (sizeof(tbl) / sizeof(tbl[0]))); + GstPlayerTests::Config_Params *setup = &tbl[idx]; + + ConstructAMPGstPlayer(); + + SetupPipeline(setup); + + DestroyAMPGstPlayer(); +} + + +TEST_P(GstPlayerTestsP, SetAudioVolume) +{ + int idx = GetParam(); + ASSERT_TRUE(idx < (sizeof(tbl) / sizeof(tbl[0]))); + GstPlayerTests::Config_Params *setup = &tbl[idx]; + + // Setup + ConstructAMPGstPlayer(); + SetupPipeline(setup); + + // Code under test + + // Muted, and volume not set (note this is not the case for non-rialto AMLOGIC builds) + int volume = 0; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("mute"), Matcher(true))).Times(1); + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("volume"), Matcher(_))).Times(0); + mInterfaceGstPlayer->SetAudioVolume(volume); + mInterfaceGstPlayer->SetVolumeOrMuteUnMute(); + + // Unmuted and volume set to 100.0 + volume = 100; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("mute"), Matcher(false))).Times(1); + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("volume"), Matcher(volume / 100.0))).Times(1); + mInterfaceGstPlayer->SetAudioVolume(volume); + mInterfaceGstPlayer->SetVolumeOrMuteUnMute(); + + // No change to mute, and volume set to 50.0 + volume = 50; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("mute"), Matcher(_))).Times(0); + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("volume"), Matcher(volume / 100.0))).Times(1); + mInterfaceGstPlayer->SetAudioVolume(volume); + mInterfaceGstPlayer->SetVolumeOrMuteUnMute(); + + //Tidy Up + DestroyAMPGstPlayer(); +} + +TEST_P(GstPlayerTestsP, SetVideoMute) +{ + // Setup + ConstructAMPGstPlayer(); + SetupPipeline(&tbl[0]); + + bool mute = true; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("show-video-window"), Matcher(!mute))).Times(1); + mInterfaceGstPlayer->SetVideoMute(mute); + + mute = false; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("show-video-window"), Matcher(!mute))).Times(1); + mInterfaceGstPlayer->SetVideoMute(mute); + + //Tidy Up + DestroyAMPGstPlayer(); +} + +// focus on Rialto only +INSTANTIATE_TEST_SUITE_P(GstPlayer,GstPlayerTestsP, testing::Values(0)); +//INSTANTIATE_TEST_SUITE_P(GstPlayer,GstPlayerTestsP, testing::Values(0,1,2,3)); + +TEST_F(GstPlayerTests, TimerAdd) +{ + // Setup + gpointer user_data = nullptr; + int repeatTimeout = 100; + guint taskId = 0; + GstElement dummyelement; + mInterfaceGstPlayer = new InterfacePlayerRDK(); + ConstructAMPGstPlayer(); + + // Expectations + + // Code under test - Callback Pointer = Null, user_data = Null + EXPECT_CALL(*g_mockGLib, g_timeout_add(_, _, _)) .Times(0); + mInterfaceGstPlayer->TimerAdd(nullptr, repeatTimeout, taskId, user_data, "TimerAdd"); + EXPECT_EQ(0,taskId); + + // Code under test - user_data = Null + EXPECT_CALL(*g_mockGLib, g_timeout_add(_, _, _)) .Times(0); + mInterfaceGstPlayer->TimerAdd(ProgressCallbackOnTimeout, repeatTimeout, taskId, user_data, "TimerAdd"); + EXPECT_EQ(0,taskId); + + user_data = &dummyelement; + taskId = 1; + + // Code under test - taskId = 1 timer already added + EXPECT_CALL(*g_mockGLib, g_timeout_add(_, _, _)) .Times(0); + mInterfaceGstPlayer->TimerAdd(ProgressCallbackOnTimeout, repeatTimeout, taskId, user_data, "TimerAdd"); + EXPECT_EQ(1,taskId); + + taskId = 0; + + // Code under test - Success Path + EXPECT_CALL(*g_mockGLib, g_timeout_add(_, _, _)) .WillOnce(Return(1)); + mInterfaceGstPlayer->TimerAdd(ProgressCallbackOnTimeout, repeatTimeout, taskId, user_data, "TimerAdd"); + EXPECT_EQ(1,taskId); + + //Tidy Up + DestroyAMPGstPlayer(); +} + + +TEST_F(GstPlayerTests, TimerRemove) +{ + // Setup + guint taskId = 0; + + ConstructAMPGstPlayer(); + + // Expectations + + mInterfaceGstPlayer = new InterfacePlayerRDK(); + EXPECT_CALL(*g_mockGLib, g_source_remove(_)) .Times(0); + + // Code under test - taskId = 0 timer not added to be removed + mInterfaceGstPlayer->TimerRemove(taskId, "TimerRemove"); + EXPECT_EQ(0,taskId); + + taskId = 1; + + // Code under test - Success Path + EXPECT_CALL(*g_mockGLib, g_source_remove(_)) .WillOnce(Return(TRUE)); + mInterfaceGstPlayer->TimerRemove(taskId, "TimerRemove"); + EXPECT_EQ(0,taskId); +} + + +TEST_F(GstPlayerTests, SetAudioVolume_NoSink) +{ + // Setup + ConstructAMPGstPlayer(); + + // Code under test + + // No sink, so no call to set volume or mute expected + int volume = 0; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("mute"), Matcher(_))).Times(0); + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("volume"), Matcher(_))).Times(0); + mInterfaceGstPlayer->SetAudioVolume(volume); + + volume = 100; + mInterfaceGstPlayer->SetAudioVolume(volume); + + volume = 50; + mInterfaceGstPlayer->SetAudioVolume(volume); + + //Tidy Up + DestroyAMPGstPlayer(); +} + +TEST_F(GstPlayerTests, SetVideoMute_NoSink) +{ + // Setup + ConstructAMPGstPlayer(); + + bool mute = true; + EXPECT_CALL(*g_mockGLib, g_object_set(NotNull(), StrEq("show-video-window"), Matcher(_))).Times(0); + mInterfaceGstPlayer->SetVideoMute(mute); + + //Tidy Up + DestroyAMPGstPlayer(); +} + +extern void MonitorAV( InterfacePlayerRDK *_this ); + +TEST_F(GstPlayerTests, MonitorAV ) +{ + const int avpos[][2] = + { + { 280, 243}, + { 540, 503}, + { 780, 743},// ok + { 1040, 1003},// ok + { 1280, 1243},// ok + { 1540, 1503},// ok + { 1780, 1743},// ok + { 2040, 2003},// ok + { 2280, 2263},// ok + { 2540, 2503},// ok + { 2800, 2763},// ok + { 3040, 3003},// ok + { 3300, 3263},// ok + { 3540, 3503},// ok + { 3940, 5653},// video-only + { 5140, 5893},// video-only + { 6180, 6153},// ok + { 6420, 6393},// ok + { 6680, 6653},// ok + { 6920, 6893},// ok + { 7180, 7153},// ok + { 7420, 7413},// ok + { 7680, 7653},// ok + { 7940, 7913},// ok + { 8180, 8153},// ok + { 8440, 8413},// ok + { 8680, 8653},// ok + { 8940, 8913},// ok + { 9180, 9153},// ok + { 9440, 9413},// ok + // av gap + {11560,11520},// ok + {11820,11780},// ok + {12060,12020},// ok + {12320,12280},// ok + {12560,12520},// ok + {12820,12780},// ok + {13080,13040},// ok + {13320,13280},// ok + {13580,13540},// ok + {13820,13780},// ok + {14080,14040},// ok + {14320,14280},// ok + {14580,14540},// ok + {14820,14780},// ok + {15080,15040},// ok + {15320,15280},// ok + {15380,15540},// audio only + {15380,15780},// audio only + {15380,16040},// audio only + {15380,16280},// audio only + {15380,16540},// audio only + {15380,16780},// audio only + {15380,17040},// audio only + {17320,17300},// ok + {17580,17540},// ok + {17840,17800},// ok + {18080,18040},// ok + {18340,18300},// ok + {18580,18540},// ok + {18840,18800},// ok + {19080,19040},// ok + {19220,19321},// bad avsync + {19220,19321},// stalled av + {19220,19321},// stalled av + {19220,19321},// stalled av + {19220,19321},// stalled av + {19220,19321},// stalled av + }; + ConstructAMPGstPlayer(); + SetupPipeline(&tbl[0]); + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillRepeatedly(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PLAYING), + Return(GST_STATE_CHANGE_SUCCESS))); + for( int idx=0; idx(G_GINT64_CONSTANT(1000000)*avpos[idx][eGST_MEDIATYPE_VIDEO]), + Return(TRUE))) + .WillOnce(DoAll( + SetArgPointee<2>(G_GINT64_CONSTANT(1000000)*avpos[idx][eGST_MEDIATYPE_AUDIO]), + Return(TRUE))); + MonitorAV(mInterfaceGstPlayer); + } + DestroyAMPGstPlayer(); +} diff --git a/middleware/test/utests/tests/GstPlayer/GstPlayerTests.cpp b/middleware/test/utests/tests/GstPlayer/GstPlayerTests.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/GstPlayer/GstPlayerTests.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/GstPlayer/PauseOnPlaybackTests.cpp b/middleware/test/utests/tests/GstPlayer/PauseOnPlaybackTests.cpp new file mode 100644 index 000000000..c0a1eef04 --- /dev/null +++ b/middleware/test/utests/tests/GstPlayer/PauseOnPlaybackTests.cpp @@ -0,0 +1,375 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "MockGStreamer.h" +#include "MockGLib.h" +#include "MockGstHandlerControl.h" +#include "MockPlayerScheduler.h" +#include "InterfacePlayerRDK.h" +#include "InterfacePlayerPriv.h" +#include "GstUtils.h" + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::_; +using ::testing::Address; +using ::testing::DoAll; +using ::testing::SetArgPointee; +using ::testing::NotNull; +using ::testing::SaveArgPointee; +using ::testing::SaveArg; +using ::testing::Pointer; + +class PauseOnPlaybackTests : public ::testing::Test +{ + +protected: + InterfacePlayerRDK *mInterfaceGstPlayer; + int cbResponse = 0; + void SetUp() override + { + g_mockGStreamer = new NiceMock();//new MockGStreamer(); + g_mockGLib = new NiceMock(); + g_mockGstHandlerControl = new MockGstHandlerControl(); + g_mockPlayerScheduler = new MockPlayerScheduler(); + mInterfaceGstPlayer = new InterfacePlayerRDK(); + + //init callback to avoid bad_function_call error + mInterfaceGstPlayer->TearDownCallback([this](bool status, int mediaType) { + if (status) { + cbResponse = 5; + }}); + mInterfaceGstPlayer->StopCallback([this](bool status) + { + printf("StopCallback status: %d\n", status); + }); + + /*mInterfaceGstPlayer->busMessageCallback([this](BusEventData data) + { + printf("busMessageCallback called\n"); + }); + */ + mInterfaceGstPlayer->RegisterBusEvent([this](const BusEventData& event) { + printf("busMessageCallback called\n"); + }); + } + + void TearDown() override + { + delete g_mockGLib; + g_mockGLib = nullptr; + + delete g_mockGStreamer; + g_mockGStreamer = nullptr; + + delete mInterfaceGstPlayer; + mInterfaceGstPlayer = nullptr; + + delete g_mockGstHandlerControl; + g_mockGstHandlerControl = nullptr; + + delete g_mockPlayerScheduler; + g_mockPlayerScheduler = nullptr; + } + +public: +}; + +// Test API SetPauseOnStartPlayback +// No external checks available to check anything occurs, however checked in +// test EnteredPausedSteHandler_ConfigurePauseOnPlayback that property is enabled +TEST_F(PauseOnPlaybackTests, SetPauseOnStartPlayback) +{ + mInterfaceGstPlayer->SetPauseOnStartPlayback(true); + mInterfaceGstPlayer->SetPauseOnStartPlayback(false); +} + +// Test configuration of pipeline with PauseOnPlayback enabled +// Checks that the state set is GST_STATE_PAUSED +TEST_F(PauseOnPlaybackTests, EnteredPausedSteHandler_ConfigurePauseOnPlayback) +{ + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstElement gst_element_bin = {.object = {.name = (gchar *)"bin"}}; + GstBus bus = {}; + GstPipeline *pipeline = GST_PIPELINE(&gst_element_pipeline); + + // CreatePipeline() + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq("testPipeline"))) + .WillOnce(Return(&gst_element_pipeline)); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(pipeline)) + .WillOnce(Return(&bus)); + + // End CreatePipeline() + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("playbin"), NULL)) + .WillRepeatedly(Return(&gst_element_bin)); + + EXPECT_CALL(*g_mockGStreamer, gst_bin_add(GST_BIN(pipeline), NotNull())) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + // Enable PauseOnPlayback + mInterfaceGstPlayer->SetPauseOnStartPlayback(true); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_VIDEO_ES_H264, GST_FORMAT_AUDIO_ES_AAC, GST_FORMAT_INVALID, GST_FORMAT_SUBTITLE_WEBVTT, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); +} + +// Test configuration of pipeline with PauseOnPlayback not enabled +// Checks that the state set is GST_STATE_PLAYING +TEST_F(PauseOnPlaybackTests, EnteredPausedSteHandler_ConfigureNormalPlayback) +{ + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstElement gst_element_bin = {.object = {.name = (gchar *)"bin"}}; + GstBus bus = {}; + GstPipeline *pipeline = GST_PIPELINE(&gst_element_pipeline); + + // CreatePipeline() + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq("testPipeline"))) + .WillOnce(Return(&gst_element_pipeline)); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(pipeline)) + .WillOnce(Return(&bus)); + // End CreatePipeline() + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("playbin"), NULL)) + .WillRepeatedly(Return(&gst_element_bin)); + + EXPECT_CALL(*g_mockGStreamer, gst_bin_add(GST_BIN(pipeline), NotNull())) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PLAYING)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_VIDEO_ES_H264, GST_FORMAT_AUDIO_ES_AAC, GST_FORMAT_INVALID, GST_FORMAT_SUBTITLE_WEBVTT, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); +} + +// Test bus_message callback when PauseOnPlayback has been enabled, and sink +// has the property frame-step-on-preroll +// Due to g_object_set being a variadic function it is not possible/simple to mock, +// and therefore confirm that the property is set. However it is checked that +// step event is sent. +// The task FirstFrameCallback is not exected as this would be triggered by the +// "first-video-frame-callback" callback from gstreamer when the frame is displayed +TEST_F(PauseOnPlaybackTests, bus_messsage_FrameStepPropertyAvailable) +{ + // this feature covered in L3 test, but not implemented on all SoCs + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstElement gst_element_video_sink = {.object = {.name = (gchar *)"brcmvideosink0"}}; + GstElement gst_element_bin = {.object = {.name = (gchar *)"bin"}}; + GstBus bus = {}; + GstPipeline *pipeline = GST_PIPELINE(&gst_element_pipeline); + GstBusFunc bus_message_func = nullptr; + GstBusSyncHandler bus_sync_func = nullptr; + InterfacePlayerPriv* privatePlayer = nullptr; + privatePlayer = mInterfaceGstPlayer->GetPrivatePlayer(); + privatePlayer->gstPrivateContext->video_sink = &gst_element_video_sink; + + // Expectations + // CreatePipeline() + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq("testPipeline"))) + .WillOnce(Return(&gst_element_pipeline)); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(pipeline)) + .WillOnce(Return(&bus)); + + // Save the bus_message function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_add_watch(&bus, NotNull(), mInterfaceGstPlayer)) + .WillOnce(DoAll( + SaveArgPointee<1>(&bus_message_func), + Return(0))); + + // Save the bus_sync_handler function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_set_sync_handler(&bus, NotNull(), mInterfaceGstPlayer, NULL)) + .WillOnce(SaveArgPointee<1>(&bus_sync_func)); + // End CreatePipeline() + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("playbin"), NULL)) + .WillRepeatedly(Return(&gst_element_bin)); + + EXPECT_CALL(*g_mockGStreamer, gst_bin_add(GST_BIN(pipeline), NotNull())) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + mInterfaceGstPlayer->SetPauseOnStartPlayback(true); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_VIDEO_ES_H264, GST_FORMAT_AUDIO_ES_AAC, GST_FORMAT_INVALID, GST_FORMAT_SUBTITLE_WEBVTT, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + ASSERT_TRUE(bus_sync_func != nullptr); + ASSERT_TRUE(bus_message_func != nullptr); + + GstMessage sync_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(&gst_element_video_sink) }; + + EXPECT_CALL(*g_mockGstHandlerControl, isEnabled()) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&sync_message),NotNull(),NotNull(),_)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED))); + + EXPECT_CALL(*g_mockGStreamer, gst_object_replace(NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<0>(GST_OBJECT(&gst_element_video_sink)), + Return(1))); + + // Call the bus_sync_handler function + bus_sync_func(&bus, &sync_message, mInterfaceGstPlayer); + + GstMessage pipeline_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(&gst_element_pipeline) }; + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&pipeline_message),NotNull(),NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED), + SetArgPointee<3>(GST_STATE_NULL))); + + GParamSpec spec = {}; + // Property available + privatePlayer->gstPrivateContext->video_sink = &gst_element_video_sink; + EXPECT_CALL(*g_mockGLib, g_object_class_find_property(_, StrEq("frame-step-on-preroll"))) + .WillOnce(Return(reinterpret_cast(0x1))); + + // No simple solution to mock variadic functions, so cannot check calls to g_object_set + + EXPECT_CALL(*g_mockGStreamer, gst_event_new_step(GST_FORMAT_BUFFERS, 1, 1.0, FALSE, FALSE)) + .WillOnce(Return(nullptr)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_send_event(_,_)) + .WillOnce(Return(1)); + + // Call the bus_message function + bus_message_func(&bus, &pipeline_message, mInterfaceGstPlayer); +} + +// Test bus_message callback when PauseOnPlayback has been enabled, and sink +// doesn't have the property frame-step-on-preroll +// As the frame-step-on-preroll is not available, the test confirms that +// the task FirstFrameCallback is called instead upon reaching PAUSED state. +TEST_F(PauseOnPlaybackTests, bus_message_FrameStepPropertyNotAvailable) +{ + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstElement gst_element_video_sink = {.object = {.name = (gchar *)"rialtomsevideosink0"}}; + //GstElement gst_element_video_sink = {.object = {.name = (gchar *)"brcmvideosink0"}}; + GstElement gst_element_bin = {.object = {.name = (gchar *)"bin"}}; + GstBus bus = {}; + GstPipeline *pipeline = GST_PIPELINE(&gst_element_pipeline); + + GstBusFunc bus_message_func = nullptr; + GstBusSyncHandler bus_sync_func = nullptr; + + // Expectations + // CreatePipeline() + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq("testPipeline"))) + .WillOnce(Return(&gst_element_pipeline)); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(pipeline)) + .WillOnce(Return(&bus)); + + // Save the bus_message function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_add_watch(&bus, NotNull(), mInterfaceGstPlayer)) + .WillOnce(DoAll( + SaveArgPointee<1>(&bus_message_func), + Return(0))); + + // Save the bus_sync_handler function for later use + EXPECT_CALL(*g_mockGStreamer, gst_bus_set_sync_handler(&bus, NotNull(), mInterfaceGstPlayer, NULL)) + .WillOnce(SaveArgPointee<1>(&bus_sync_func)); + + // End CreatePipeline() + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_VOID_PENDING), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_factory_make(StrEq("playbin"), NULL)) + .WillRepeatedly(Return(&gst_element_bin)); + + EXPECT_CALL(*g_mockGStreamer, gst_bin_add(GST_BIN(pipeline), NotNull())) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + mInterfaceGstPlayer->SetPauseOnStartPlayback(true); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_VIDEO_ES_H264, GST_FORMAT_AUDIO_ES_AAC, GST_FORMAT_INVALID, GST_FORMAT_SUBTITLE_WEBVTT, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + ASSERT_TRUE(bus_sync_func != nullptr); + ASSERT_TRUE(bus_message_func != nullptr); + + GstMessage sync_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(&gst_element_video_sink) }; + + EXPECT_CALL(*g_mockGstHandlerControl, isEnabled()) + .WillRepeatedly(Return(true)); + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&sync_message),NotNull(),NotNull(),_)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED))); + + EXPECT_CALL(*g_mockGStreamer, gst_object_replace(NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<0>(GST_OBJECT(&gst_element_video_sink)), + Return(1))); + + // Call the bus_sync_handler function + bus_sync_func(&bus, &sync_message, mInterfaceGstPlayer); + + GstMessage pipeline_message = {.type = GST_MESSAGE_STATE_CHANGED, .src = GST_OBJECT(&gst_element_pipeline) }; + + EXPECT_CALL(*g_mockGStreamer, gst_message_parse_state_changed(Pointer(&pipeline_message),NotNull(),NotNull(),NotNull())) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_PAUSED), + SetArgPointee<3>(GST_STATE_NULL))); + + + // Call the bus_message function + bus_message_func(&bus, &pipeline_message, mInterfaceGstPlayer); +} diff --git a/middleware/test/utests/tests/GstUtilsTests/CMakeLists.txt b/middleware/test/utests/tests/GstUtilsTests/CMakeLists.txt new file mode 100644 index 000000000..2c026ed40 --- /dev/null +++ b/middleware/test/utests/tests/GstUtilsTests/CMakeLists.txt @@ -0,0 +1,65 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2023 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME GstUtilsTests) + +include_directories(${PLAYER_ROOT}) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LibXml2_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${PLAYER_ROOT}/test/utests/mocks) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${UTEST_ROOT}/fakes) + +set(TEST_SOURCES GstUtilsTests.cpp + GstUtilsPlayerTests.cpp) + +set(PLAYER_SOURCES + ${PLAYER_ROOT}/GstUtils.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp ) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes -lpthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/GstUtilsTests/GstUtilsPlayerTests.cpp b/middleware/test/utests/tests/GstUtilsTests/GstUtilsPlayerTests.cpp new file mode 100644 index 000000000..adccbc7e7 --- /dev/null +++ b/middleware/test/utests/tests/GstUtilsTests/GstUtilsPlayerTests.cpp @@ -0,0 +1,26 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2023 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/GstUtilsTests/GstUtilsTests.cpp b/middleware/test/utests/tests/GstUtilsTests/GstUtilsTests.cpp new file mode 100644 index 000000000..399bb36bd --- /dev/null +++ b/middleware/test/utests/tests/GstUtilsTests/GstUtilsTests.cpp @@ -0,0 +1,86 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2023 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include + +#include "GstUtils.h" +#include "MockGStreamer.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::StrEq; + +class GstUtilsTests : public ::testing::Test +{ +protected: + + void SetUp() override + { + g_mockGStreamer = new MockGStreamer(); + } + + void TearDown() override + { + delete g_mockGStreamer; + g_mockGStreamer = nullptr; + } + +public: + +}; + +TEST_F(GstUtilsTests, esMP3test) +{ + GstCaps dummycaps; + GstCaps *caps{&dummycaps}; + EXPECT_CALL(*g_mockGStreamer,gst_caps_new_simple(StrEq("audio/mpeg"),StrEq("mpegversion"), G_TYPE_INT, 1, NULL)).WillOnce(Return(caps)); + + EXPECT_TRUE(GetCaps(GST_FORMAT_AUDIO_ES_MP3)==caps); +} + +TEST_F(GstUtilsTests, GstCapsFormatsTest) +{ + GstCaps dummycapslist; + GstCaps *caps{&dummycapslist}; + GstStreamOutputFormat GstCapsFormats[16] = { + GST_FORMAT_INVALID, /**< Invalid format */ + GST_FORMAT_MPEGTS, /**< MPEG Transport Stream */ + GST_FORMAT_ISO_BMFF, /**< ISO Base Media File format */ + GST_FORMAT_AUDIO_ES_MP3, /**< MP3 Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_AAC, /**< AAC Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_AC3, /**< AC3 Audio Elementary Stream */ + GST_FORMAT_AUDIO_ES_EC3, /**< Dolby Digital Plus Elementary Stream */ + GST_FORMAT_AUDIO_ES_ATMOS, /**< ATMOS Audio stream */ + GST_FORMAT_AUDIO_ES_AC4, /**< AC4 Dolby Audio stream */ + GST_FORMAT_VIDEO_ES_H264, /**< MPEG-4 Video Elementary Stream */ + GST_FORMAT_VIDEO_ES_HEVC, /**< HEVC video elementary stream */ + GST_FORMAT_VIDEO_ES_MPEG2, /**< MPEG-2 Video Elementary Stream */ + GST_FORMAT_SUBTITLE_WEBVTT, /**< WebVTT subtitle Stream */ + GST_FORMAT_SUBTITLE_TTML, /**< WebVTT subtitle Stream */ + GST_FORMAT_SUBTITLE_MP4, /**< Generic MP4 stream */ + GST_FORMAT_UNKNOWN /**< Unknown Format */ + }; + + for(int i=0;i<16;i++){ + GetCaps(GstCapsFormats[i]); + ASSERT_FALSE(GetCaps(GstCapsFormats[i])); + } +} diff --git a/middleware/test/utests/tests/InterfacePlayerTests/CMakeLists.txt b/middleware/test/utests/tests/InterfacePlayerTests/CMakeLists.txt new file mode 100644 index 000000000..a3e31249d --- /dev/null +++ b/middleware/test/utests/tests/InterfacePlayerTests/CMakeLists.txt @@ -0,0 +1,72 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME InterfacePlayerTest) + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/drm ${PLAYER_ROOT}/drm/helper ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/downloader ${PLAYER_ROOT}/isobmff ${PLAYER_ROOT}/subtec/subtecparser ${PLAYER_ROOT}/subtec/libsubtec) + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMERBASE_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${PLAYER_ROOT}/../test/gstTestHarness) + +message(GSTREAMER_INCLUDE_DIRS=${GSTREAMER_INCLUDE_DIRS}) + +set(TEST_SOURCES InterfacePlayerFunctionTests.cpp + InterfacePlayerTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/InterfacePlayerRDK.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/externals/PlayerExternalsInterface.cpp + ${PLAYER_ROOT}/externals/PlayerExternalUtils.cpp) + +set(FAKE_SOURCES ${PLAYER_ROOT}/test/utests/fakes/FakeGStreamer.cpp + ${PLAYER_ROOT}/test/utests/fakes/FakePlayerScheduler.cpp) + + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES} + ${FAKE_SOURCES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes -lpthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES} ${GSTREAMER_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp new file mode 100644 index 000000000..cd34729ad --- /dev/null +++ b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp @@ -0,0 +1,2535 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "InterfacePlayerRDK.h" +#include "InterfacePlayerPriv.h" +#include "PlayerLogManager.h" +#include "MockGStreamer.h" +#include "MockGLib.h" +#include "MockGstHandlerControl.h" +#include "MockPlayerScheduler.h" +#include "MockGstUtils.h" +#include +#include + + +using ::testing::NiceMock; +using ::testing::StrictMock; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::Eq; +using ::testing::_; +using ::testing::Address; +using ::testing::DoAll; +using ::testing::SetArgPointee; +using ::testing::NotNull; +using ::testing::SaveArgPointee; +using ::testing::SaveArg; +using ::testing::Pointer; +using ::testing::Matcher; +using ::testing::AnyNumber; + +#define GST_NORMAL_PLAY_RATE 1 + +class InterfacePlayerTests : public ::testing::Test +{ + +protected: + bool isPipelineSetup = false; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"Pipeline"}}; + GstBus bus = {}; + GstQuery query = {}; + InterfacePlayerRDK *mInterfaceGstPlayer; + InterfacePlayerPriv *mInterfacePrivatePlayer; + + GstPlayerPriv* mPlayerContext; + Configs* mPlayerConfigParams; + int cbResponse = 0; + + void SetUp() override + { + g_mockGStreamer = new NiceMock(); + g_mockGLib = new NiceMock(); + g_mockPlayerScheduler = new NiceMock(); + + ConstructAMPGstPlayer(); + PlayerLogManager::lockLogLevel(false); + PlayerLogManager::disableLogRedirection = true; //required for mwlog output in utest + PlayerLogManager::setLogLevel(mLOGLEVEL_TRACE); + } + + void TearDown() override + { + delete g_mockGStreamer; + g_mockGStreamer = nullptr; + + DestroyAMPGstPlayer(); + + delete g_mockGLib; + g_mockGLib = nullptr; + + delete g_mockPlayerScheduler; + g_mockPlayerScheduler = nullptr; + } + +public: + void ConstructAMPGstPlayer() + { + mInterfaceGstPlayer = new InterfacePlayerRDK(); + mInterfacePrivatePlayer = mInterfaceGstPlayer->GetPrivatePlayer(); + + mPlayerContext = mInterfacePrivatePlayer->gstPrivateContext; + + mPlayerConfigParams = mInterfaceGstPlayer->m_gstConfigParam; + //init callback to avoid bad_function_call error + mInterfaceGstPlayer->TearDownCallback([this](bool status, int mediaType) { + if (status) { + cbResponse = 5; + }}); + mInterfaceGstPlayer->StopCallback([this](bool status) + { + printf("StopCallback status: %d\n", status); + }); + } + + void DestroyAMPGstPlayer() + { + delete mInterfaceGstPlayer; + mPlayerContext = nullptr; + mInterfaceGstPlayer = nullptr; + } + +}; + +TEST_F(InterfacePlayerTests, ConfigurePipeline_WithAudioForwardToAux) +{ + g_mockGStreamer = nullptr; + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, false, true, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + EXPECT_EQ(mPlayerContext->forwardAudioBuffers, true); +} + +TEST_F(InterfacePlayerTests, ConfigurePipeline_WithWesterosAndRealtoSink) +{ + g_mockGStreamer = nullptr; + mPlayerConfigParams->useWesterosSink = true; + EXPECT_EQ(mPlayerContext->using_westerossink, false); + + mPlayerConfigParams->useRialtoSink = true; + EXPECT_EQ(mPlayerContext->usingRialtoSink, false); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + EXPECT_EQ(mPlayerContext->using_westerossink, true); + EXPECT_EQ(mPlayerContext->usingRialtoSink, true); + +} + +TEST_F(InterfacePlayerTests, ConfigurePipeline_WithSubtitlesEnabled) +{ + g_mockGStreamer = nullptr; + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, false, false, false, true, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + EXPECT_EQ(mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].format, GST_FORMAT_INVALID); +} + +TEST_F(InterfacePlayerTests, ConfigurePipeline_WithBufferingEnabled) +{ + g_mockGStreamer = nullptr; + mPlayerContext->buffering_enabled = true; + mPlayerContext->rate = GST_NORMAL_PLAY_RATE; + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_MPEGTS, GST_FORMAT_INVALID, GST_FORMAT_INVALID, GST_FORMAT_INVALID, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + EXPECT_EQ(mPlayerContext->buffering_in_progress, true); + EXPECT_EQ(mPlayerContext->buffering_target_state, GST_STATE_PLAYING); +} + + +TEST_F(InterfacePlayerTests, ConfigurePipeline_StreamConfiguration) +{ + g_mockGStreamer = nullptr; + mPlayerContext->NumberOfTracks = 0; + mPlayerContext->rate = 1.0; + + EXPECT_EQ(mPlayerContext->NumberOfTracks, 0); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_ISO_BMFF, GST_FORMAT_AUDIO_ES_AC3, GST_FORMAT_AUDIO_ES_AC3, GST_FORMAT_SUBTITLE_MP4, false, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + EXPECT_EQ(mPlayerContext->NumberOfTracks, 3); + EXPECT_EQ(cbResponse, 5); //callback was called +} + +TEST_F(InterfacePlayerTests, ConfigurePipeline_ESChange) +{ + g_mockGStreamer = nullptr; + mPlayerContext->NumberOfTracks = 0; + mPlayerContext->rate = 1.0; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].format = GST_FORMAT_AUDIO_ES_AC3; + + EXPECT_EQ(mPlayerContext->NumberOfTracks, 0); + + mInterfaceGstPlayer->ConfigurePipeline(GST_FORMAT_ISO_BMFF, GST_FORMAT_AUDIO_ES_AC3, GST_FORMAT_AUDIO_ES_AC3, GST_FORMAT_SUBTITLE_MP4, true, false, false, false, 0, GST_NORMAL_PLAY_RATE, "testPipeline", 0, false, "testManifest"); + + EXPECT_EQ(mPlayerContext->NumberOfTracks, 2); + EXPECT_EQ(cbResponse, 5); +} + +TEST_F(InterfacePlayerTests, SetPauseOnStartPlayback) +{ + EXPECT_EQ(mPlayerContext->pauseOnStartPlayback, false); + mInterfaceGstPlayer->SetPauseOnStartPlayback(true); + EXPECT_EQ(mPlayerContext->pauseOnStartPlayback, true); + mInterfaceGstPlayer->SetPauseOnStartPlayback(false); + EXPECT_EQ(mPlayerContext->pauseOnStartPlayback, false); +} + +TEST_F(InterfacePlayerTests, SetEncryption) +{ + void* testEncryptPointer = reinterpret_cast(0x1234); // A dummy pointer for testing + void* testDrmSessionMgr = reinterpret_cast(0x5678); // Another dummy pointer for testing + + mInterfaceGstPlayer->setEncryption(testEncryptPointer, testDrmSessionMgr); + EXPECT_EQ(mInterfaceGstPlayer->mEncrypt, testEncryptPointer); + EXPECT_EQ(mInterfaceGstPlayer->mDRMSessionManager, testDrmSessionMgr); +} + +TEST_F(InterfacePlayerTests, SetPreferredDRM) +{ + const char* testDrmID1 = "Widevine"; + mInterfaceGstPlayer->SetPreferredDRM(testDrmID1); + EXPECT_STREQ(mInterfaceGstPlayer->mDrmSystem, testDrmID1); + //null check + const char* testDrmID2 = NULL; + mInterfaceGstPlayer->SetPreferredDRM(testDrmID2); + EXPECT_STREQ(mInterfaceGstPlayer->mDrmSystem, testDrmID1); +} + +TEST_F(InterfacePlayerTests, GstSetSeekPosition) +{ + double testPosition1 = 30.5; + mInterfaceGstPlayer->SetSeekPosition(testPosition1); + EXPECT_DOUBLE_EQ(mPlayerContext->seekPosition, testPosition1); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + EXPECT_TRUE(mPlayerContext->stream[i].pendingSeek); + } + + double testPosition2 = 100.0; + mInterfaceGstPlayer->SetSeekPosition(testPosition2); + EXPECT_DOUBLE_EQ(mPlayerContext->seekPosition, testPosition2); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + EXPECT_TRUE(mPlayerContext->stream[i].pendingSeek); + } + + // Test with a negative position + double testPosition3 = -5.0; + mInterfaceGstPlayer->SetSeekPosition(testPosition3); + EXPECT_DOUBLE_EQ(mPlayerContext->seekPosition, testPosition3); + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + EXPECT_TRUE(mPlayerContext->stream[i].pendingSeek); + } +} + +TEST_F(InterfacePlayerTests, GstTimerRemove) +{ + g_mockGLib = new StrictMock(); + + + guint testTaskID = 1234; + const char* testTimerName = "testTimer"; + + EXPECT_CALL(*g_mockGLib, g_source_remove(testTaskID)) + .WillOnce(Return(TRUE)); + mInterfaceGstPlayer->TimerRemove(testTaskID, testTimerName); + EXPECT_EQ(testTaskID, 0); + + mInterfaceGstPlayer->TimerRemove(testTaskID, testTimerName); // Task ID is 0 + +} + +TEST_F(InterfacePlayerTests, GstRemoveProbes) +{ + GstPad pad1 = {.object = {.name = (gchar *)"pad1"}}; + GstPad pad2 = {.object = {.name = (gchar *)"pad2"}}; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].demuxPad = &pad1; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].demuxProbeId = 1234; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].demuxPad = &pad2; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].demuxProbeId = 5678; + + EXPECT_CALL(*g_mockGStreamer, gst_pad_remove_probe(&pad1, 1234)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_remove_probe(&pad2, 5678)); + mInterfaceGstPlayer->RemoveProbes(); +} + +TEST_F(InterfacePlayerTests, GstDestroyPipeline) +{ + delete g_mockGStreamer; + g_mockGStreamer = new StrictMock(); + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(mPlayerContext->pipeline)); + + //deleting bus + GstBus gst_element_bus = {.object = {.name = (gchar *)"testbus"}}; + mPlayerContext->bus = &gst_element_bus; + EXPECT_CALL(*g_mockGStreamer, gst_bus_remove_watch(mPlayerContext->bus)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(mPlayerContext->bus)); + + //deleting task_pool + GstTaskPool gst_element_task_pool = {.object = {.name = (gchar *)"testtaskpool"}}; + mPlayerContext->task_pool = &gst_element_task_pool; + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(mPlayerContext->task_pool)); + + + //deleting positionQuery + GstQuery* gst_element_query = new GstQuery(); + mPlayerContext->positionQuery = gst_element_query; + EXPECT_CALL(*g_mockGStreamer, gst_mini_object_unref(NotNull())); // unable to mock gst_query_unref + + mInterfaceGstPlayer->DestroyPipeline(); + EXPECT_EQ(mPlayerContext->pipeline, nullptr); + EXPECT_EQ(mPlayerContext->bus, nullptr); + EXPECT_EQ(mPlayerContext->task_pool, nullptr); + EXPECT_EQ(mPlayerContext->positionQuery, nullptr); + +} + +TEST_F(InterfacePlayerTests, TearDownStreamTest_successvideo) +{ + + GstElement gst_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_sinkbin = {.object = {.name = (gchar *)"testbin"}}; + + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_VIDEO]; + stream->format = GST_FORMAT_VIDEO_ES_H264; + stream->eosReached = true; + stream->bufferUnderrun = true; + + mPlayerContext->pipeline = &gst_pipeline; + mPlayerContext->buffering_in_progress = true; + stream->sinkbin = &gst_sinkbin; + + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_sinkbin, _,_,0)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_sinkbin, GST_STATE_NULL)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + + mInterfaceGstPlayer->TearDownStream(eGST_MEDIATYPE_VIDEO); + EXPECT_EQ(stream->format, GST_FORMAT_INVALID); + EXPECT_EQ(stream->bufferUnderrun, false); + EXPECT_EQ(stream->eosReached, false); + EXPECT_EQ(mPlayerContext->buffering_in_progress, false); + EXPECT_EQ(stream->sinkbin, nullptr); + + +} + +TEST_F(InterfacePlayerTests, TearDownStreamTest_failvideo) +{ + + GstElement gst_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_sinkbin = {.object = {.name = (gchar *)"testbin"}}; + + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_VIDEO]; + stream->format = GST_FORMAT_VIDEO_ES_H264; + stream->eosReached = true; + stream->bufferUnderrun = true; + + mPlayerContext->pipeline = &gst_pipeline; + mPlayerContext->buffering_in_progress = true; + + mInterfaceGstPlayer->TearDownStream(eGST_MEDIATYPE_VIDEO); + EXPECT_EQ(stream->format, GST_FORMAT_INVALID); + EXPECT_EQ(stream->bufferUnderrun, false); + EXPECT_EQ(stream->eosReached, false); + EXPECT_EQ(mPlayerContext->buffering_in_progress, false); + EXPECT_EQ(stream->sinkbin, nullptr); +} + +TEST_F(InterfacePlayerTests, TearDownStreamTest_misc) +{ + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_AUDIO]; + stream->format = GST_FORMAT_VIDEO_ES_H264; //dummy value + mInterfaceGstPlayer->TearDownStream(eGST_MEDIATYPE_AUDIO); + stream = &mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE]; + stream->format = GST_FORMAT_VIDEO_ES_H264; //dummy value + mInterfaceGstPlayer->TearDownStream(eGST_MEDIATYPE_SUBTITLE); +} + +TEST_F(InterfacePlayerTests, GstStopTestTrue) +{ + mPlayerContext->syncControl.enable(); + mPlayerContext->aSyncControl.enable(); + GstBus gst_element_bus = {.object = {.name = (gchar *)"testbus"}}; + mPlayerContext->bus = &gst_element_bus; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + + mPlayerContext->firstProgressCallbackIdleTask.taskID = 100; + mPlayerContext->firstProgressCallbackIdleTask.taskIsPending = true; + + mPlayerContext->bufferingTimeoutTimerId = 200; + mPlayerContext->ptsCheckForEosOnUnderflowIdleTaskId = 300; + mPlayerContext->eosCallbackIdleTaskPending = true; + mPlayerContext->firstFrameCallbackIdleTaskPending = true; + + mPlayerConfigParams->eosInjectionMode = GstEOS_INJECTION_MODE_STOP_ONLY; + + //Expect_Calls + EXPECT_CALL(*g_mockGStreamer, gst_bus_remove_watch(mPlayerContext->bus)).Times(2); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(mPlayerContext->bus)); + EXPECT_CALL(*g_mockGLib, g_source_remove(200)); + EXPECT_CALL(*g_mockGLib, g_source_remove(300)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(mPlayerContext->pipeline)); + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _,_,0)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_NULL)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + + mInterfaceGstPlayer->Stop(true); + EXPECT_EQ(mPlayerContext->syncControl.isEnabled(),false); + EXPECT_EQ(mPlayerContext->aSyncControl.isEnabled(),false); + EXPECT_EQ(mPlayerContext->firstProgressCallbackIdleTask.taskID,0); + EXPECT_EQ(mPlayerContext->firstProgressCallbackIdleTask.taskIsPending,false); + EXPECT_EQ(mPlayerContext->bufferingTimeoutTimerId,PLAYER_TASK_ID_INVALID); + EXPECT_EQ(mPlayerContext->ptsCheckForEosOnUnderflowIdleTaskId,PLAYER_TASK_ID_INVALID); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId,PLAYER_TASK_ID_INVALID); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskPending,false); + EXPECT_EQ(mPlayerContext->firstFrameCallbackIdleTaskId,PLAYER_TASK_ID_INVALID); + EXPECT_EQ(mPlayerContext->firstFrameCallbackIdleTaskPending,false); +} + + +TEST_F(InterfacePlayerTests, TestResetGstEvents) +{ + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + mPlayerContext->stream[i].resetPosition = false; + mPlayerContext->stream[i].pendingSeek = true; + mPlayerContext->stream[i].eosReached = true; + mPlayerContext->stream[i].firstBufferProcessed = true; + } + + mInterfaceGstPlayer->ResetGstEvents(); + + for (int i = 0; i < GST_TRACK_COUNT; i++) + { + EXPECT_TRUE(mPlayerContext->stream[i].resetPosition); + EXPECT_FALSE(mPlayerContext->stream[i].pendingSeek); + EXPECT_FALSE(mPlayerContext->stream[i].eosReached); + EXPECT_FALSE(mPlayerContext->stream[i].firstBufferProcessed); + } +} + +TEST_F(InterfacePlayerTests, SetPendingSeekTrue) +{ + mInterfaceGstPlayer->SetPendingSeek(true); + for (int i = 0; i < GST_TRACK_COUNT; ++i) + { + EXPECT_TRUE(mPlayerContext->stream[i].pendingSeek); + } +} + +TEST_F(InterfacePlayerTests, SetPendingSeekFalse) +{ + mInterfaceGstPlayer->SetPendingSeek(false); + for (int i = 0; i < GST_TRACK_COUNT; ++i) + { + EXPECT_FALSE(mPlayerContext->stream[i].pendingSeek); + } +} + +TEST_F(InterfacePlayerTests, GetSetTrickTearDownTrue) { + mInterfaceGstPlayer->SetTrickTearDown(true); + EXPECT_TRUE(mInterfaceGstPlayer->GetTrickTeardown()); +} + +TEST_F(InterfacePlayerTests, GetSetTrickTearDownFalse) { + mInterfaceGstPlayer->SetTrickTearDown(false); + EXPECT_FALSE(mInterfaceGstPlayer->GetTrickTeardown()); +} + +TEST_F(InterfacePlayerTests, IdleTaskRemove_TaskExists) { + + GstTaskControlData taskDetails("TestTask"); + taskDetails.taskID = 1; + taskDetails.taskIsPending = true; + + bool result = mInterfaceGstPlayer->IdleTaskRemove(taskDetails); + + EXPECT_TRUE(result); + EXPECT_EQ(taskDetails.taskID, 0); + EXPECT_FALSE(taskDetails.taskIsPending); +} + +TEST_F(InterfacePlayerTests, IdleTaskRemove_TaskDoesNotExist) +{ + GstTaskControlData taskDetails("TestTask"); + taskDetails.taskID = 0; + taskDetails.taskIsPending = true; + + bool result = mInterfaceGstPlayer->IdleTaskRemove(taskDetails); + + EXPECT_FALSE(result); + EXPECT_EQ(taskDetails.taskID, 0); + EXPECT_FALSE(taskDetails.taskIsPending); +} + +TEST_F(InterfacePlayerTests, IsUsingRialtoSink_true) +{ + mPlayerContext->usingRialtoSink = true; + EXPECT_TRUE(mInterfaceGstPlayer->IsUsingRialtoSink()); +} + +TEST_F(InterfacePlayerTests, IsUsingRialtoSink_false) +{ + mPlayerContext->usingRialtoSink = false; + EXPECT_FALSE(mInterfaceGstPlayer->IsUsingRialtoSink()); +} + +TEST_F(InterfacePlayerTests, IsUsingRialtoSink_null) +{ + mPlayerContext = nullptr; + EXPECT_FALSE(mInterfaceGstPlayer->IsUsingRialtoSink()); + mPlayerContext = new GstPlayerPriv(); //to avoid segfault as null context not expected as such. causes crash at InterfacePlayerRDK::GstDestroyPipeline +} + +TEST_F(InterfacePlayerTests, GstFlush_PipelineNull) +{ + double position = 10.0; + int rate = 1; + bool shouldTearDown = false; + bool isAppSeek = false; + + EXPECT_FALSE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + +TEST_F(InterfacePlayerTests, GstFlush_PipelineNotPlayingOrPaused) +{ + double position = 10.0; + int rate = 1; + bool shouldTearDown = true; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(_, _, _, _, _, _, _, _)).Times(0); + + EXPECT_FALSE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + +TEST_F(InterfacePlayerTests, GstFlush_DisableAsyncForTrickplay) +{ + double position = 10.0; + int rate = 30; //trickplay + bool shouldTearDown = true; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_element_audio_sink = {.object = {.name = (gchar *)"testaudiosink"}}; + mPlayerContext->pipeline = &gst_element_pipeline; mPlayerContext->audio_sink = &gst_element_audio_sink; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].format = GST_FORMAT_ISO_BMFF; + mPlayerContext->rate = rate; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(&gst_element_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, position * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + .WillOnce(Return(TRUE)); + + EXPECT_TRUE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + + +TEST_F(InterfacePlayerTests, GstFlush_AudioDecoderNotReady) +{ + double position = 10.0; + int rate = 1; + bool shouldTearDown = true; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_element_audio_dec = {.object = {.name = (gchar *)"testaudiodec"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->audio_dec = &gst_element_audio_dec; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_audio_dec, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_READY), + SetArgPointee<2>(GST_STATE_NULL), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(_, _, _, _, _, _, _, _)).Times(0); + + EXPECT_FALSE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + +TEST_F(InterfacePlayerTests, GstFlush_Success) +{ + double position = 10.0; + int rate = 1; + bool shouldTearDown = false; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_element_audio_dec = {.object = {.name = (gchar *)"testaudiodec"}}; + GstElement gst_element_audio_sink = {.object = {.name = (gchar *)"testaudiosink"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->audio_dec = &gst_element_audio_dec; + mPlayerContext->audio_sink = &gst_element_audio_sink; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].format = GST_FORMAT_ISO_BMFF; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun = true; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun = true; + mPlayerContext->eosCallbackIdleTaskPending = true; + mPlayerContext->ptsCheckForEosOnUnderflowIdleTaskId = 300; + mPlayerContext->bufferingTimeoutTimerId = 200; + mPlayerContext->rate = rate; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_audio_dec, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(&gst_element_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, position * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + .WillOnce(Return(TRUE)); + + EXPECT_CALL(*g_mockGLib, g_source_remove(200)); + EXPECT_CALL(*g_mockGLib, g_source_remove(300)); + + EXPECT_TRUE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); + EXPECT_FALSE(mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun); + EXPECT_FALSE(mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId, PLAYER_TASK_ID_INVALID); + EXPECT_FALSE(mPlayerContext->eosCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->ptsCheckForEosOnUnderflowIdleTaskId, PLAYER_TASK_ID_INVALID); + EXPECT_EQ(mPlayerContext->bufferingTimeoutTimerId, PLAYER_TASK_ID_INVALID); + EXPECT_FALSE(mPlayerContext->eosSignalled); + EXPECT_EQ(mPlayerContext->numberOfVideoBuffersSent, 0); +} + +TEST_F(InterfacePlayerTests, GstFlush_SeekFailed) +{ + double position = 10.0; + int rate = 1; + bool shouldTearDown = false; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_element_audio_dec = {.object = {.name = (gchar *)"testaudiodec"}}; + GstElement gst_element_audio_sink = {.object = {.name = (gchar *)"testaudiosink"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->audio_dec = &gst_element_audio_dec; + mPlayerContext->audio_sink = &gst_element_audio_sink; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].format = GST_FORMAT_ISO_BMFF; + mPlayerContext->rate = rate; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_audio_dec, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(&gst_element_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, position * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + .WillOnce(Return(FALSE)); + + EXPECT_TRUE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); //FLUSH is true even if seek is failed , needs to be confirmed TODO. + +} + +TEST_F(InterfacePlayerTests, GstFlush_ProgressiveMediaFormat) +{ + double position = 10.0; + int rate = 2; // Trickplay rate + bool shouldTearDown = false; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerConfigParams->media = eGST_MEDIAFORMAT_PROGRESSIVE; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(&gst_element_pipeline, rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, position * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + .WillOnce(Return(TRUE)); + + EXPECT_TRUE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + +TEST_F(InterfacePlayerTests, GstFlush_ISOBMFFMediaPositionReset) +{ + double position = 10.0; + int rate = 2; // Trickplay rate + bool shouldTearDown = false; + bool isAppSeek = false; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"testpipeline"}}; + GstElement gst_element_audio_sink = {.object = {.name = (gchar *)"testaudiosink"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->audio_sink = &gst_element_audio_sink; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].format = GST_FORMAT_ISO_BMFF; + mPlayerContext->rate = rate; + mPlayerConfigParams->media = eGST_MEDIAFORMAT_DASH; + mPlayerContext->usingRialtoSink = true; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll( + SetArgPointee<1>(GST_STATE_PLAYING), + SetArgPointee<2>(GST_STATE_PAUSED), + Return(GST_STATE_CHANGE_SUCCESS))); + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek(&gst_element_pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0 * GST_SECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + .WillOnce(Return(TRUE)); //position reset to zero + + EXPECT_TRUE(mInterfaceGstPlayer->Flush(position, rate, shouldTearDown, isAppSeek)); +} + +TEST_F(InterfacePlayerTests, SignalConnect_Success) +{ + g_mockGstHandlerControl= new StrictMock(); + g_mockGLib = new StrictMock(); + + gpointer instance = reinterpret_cast(0x1234); + const gchar *detailed_signal = "test-signal"; + GCallback c_handler = reinterpret_cast(0x5678); + gpointer data = reinterpret_cast(0x9ABC); + + EXPECT_CALL(*g_mockGLib, g_signal_connect(instance, StrEq(detailed_signal), c_handler, data)) + .WillOnce(Return(1)); + + mInterfacePrivatePlayer->SignalConnect(instance, detailed_signal, c_handler, data); + + EXPECT_EQ(mPlayerContext->mCallBackIdentifiers.size(), 1); + EXPECT_EQ(mPlayerContext->mCallBackIdentifiers[0].instance, instance); +} + +TEST_F(InterfacePlayerTests, SignalConnect_Failure) +{ + g_mockGLib = new StrictMock(); + gpointer instance = reinterpret_cast(0x1234); + const gchar *detailed_signal = "test-signal"; + GCallback c_handler = reinterpret_cast(0x5678); + gpointer data = reinterpret_cast(0x9ABC); + + EXPECT_CALL(*g_mockGLib, g_signal_connect(instance, StrEq(detailed_signal), c_handler, data)) + .WillOnce(Return(0)); + + mInterfacePrivatePlayer->SignalConnect(instance, detailed_signal, c_handler, data); + + EXPECT_EQ(mPlayerContext->mCallBackIdentifiers.size(), 0); +} + +TEST_F(InterfacePlayerTests, InitializeSourceForPlayer_Video) +{ + g_mockGstUtils = new StrictMock(); + + void* playerInstance = mInterfaceGstPlayer; + void* source = reinterpret_cast(0x1234); + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + bool isFogEnabled = true; + + GstCaps caps = {}; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + mPlayerConfigParams->videoBufBytes = 500; + mPlayerConfigParams->useMp4Demux = false; + + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("need-data"), _, playerInstance)).WillOnce(Return(1)); + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("enough-data"), _, playerInstance)).WillOnce(Return(1)); + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("seek-data"), _, playerInstance)).WillOnce(Return(1)); + + EXPECT_CALL(*g_mockGStreamer, gst_app_src_set_caps(_, _)); + EXPECT_CALL(*g_mockGStreamer, gst_mini_object_unref(_)); + + EXPECT_CALL(*g_mockGstUtils, GetCaps(_)).WillOnce(Return(&caps)); + + mInterfaceGstPlayer->InitializeSourceForPlayer(playerInstance, source, mediaType); + + EXPECT_TRUE(stream->sourceConfigured); + + delete g_mockGstUtils; +} + +TEST_F(InterfacePlayerTests, InitializeSourceForPlayer_Audio_CapsNull) +{ + + g_mockGstUtils = new StrictMock(); + + void* playerInstance = mInterfaceGstPlayer; + void* source = reinterpret_cast(0x1234); + GstMediaType mediaType = eGST_MEDIATYPE_AUDIO; + bool isFogEnabled = true; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + mPlayerConfigParams->audioBufBytes = 500; + + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("need-data"), _, playerInstance)).WillOnce(Return(1)); + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("enough-data"), _, playerInstance)).WillOnce(Return(1)); + EXPECT_CALL(*g_mockGLib, g_signal_connect(source, StrEq("seek-data"), _, playerInstance)).WillOnce(Return(1)); + EXPECT_CALL(*g_mockGStreamer, gst_app_src_set_stream_type(GST_APP_SRC(source), GST_APP_STREAM_TYPE_SEEKABLE)); + + EXPECT_CALL(*g_mockGstUtils, GetCaps(_)).WillOnce(Return(nullptr)); + + mInterfaceGstPlayer->InitializeSourceForPlayer(playerInstance, source, mediaType); + + EXPECT_TRUE(stream->sourceConfigured); + + delete g_mockGstUtils; +} + +TEST_F(InterfacePlayerTests, SendGstEvents_PendingSeek) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->pendingSeek = true; + stream->source = &gst_element_pipeline; + mPlayerConfigParams->enableGstPosQuery = TRUE; + mPlayerConfigParams->enablePTSReStamp = TRUE; + mPlayerConfigParams->vodTrickModeFPS = 24; + + mPlayerContext->seekPosition = 10; + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek_simple(GST_ELEMENT(stream->source), GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, mPlayerContext->seekPosition * GST_SECOND)) + .WillOnce(Return(TRUE)); + + mInterfacePrivatePlayer->SendGstEvents(mediaType, pts,mPlayerConfigParams->enableGstPosQuery , mPlayerConfigParams->enablePTSReStamp, mPlayerConfigParams->vodTrickModeFPS); + + EXPECT_FALSE(stream->pendingSeek); +} + +TEST_F(InterfacePlayerTests, SendGstEvents_NoPendingSeek) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + mPlayerConfigParams->enablePTSReStamp = TRUE; + mPlayerConfigParams->vodTrickModeFPS = 24; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->pendingSeek = false; + stream->source = &gst_element_pipeline; + + mInterfacePrivatePlayer->SendGstEvents(mediaType, pts,mPlayerConfigParams->enableGstPosQuery , mPlayerConfigParams->enablePTSReStamp, mPlayerConfigParams->vodTrickModeFPS); + + EXPECT_FALSE(stream->pendingSeek); +} + +TEST_F(InterfacePlayerTests, SendGstEvents_ProtectionEventOtherTrack) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + mPlayerConfigParams->enablePTSReStamp = TRUE; + mPlayerConfigParams->vodTrickModeFPS = 24; + + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->source = &gst_element_pipeline; + mPlayerContext->protectionEvent[mediaType] = nullptr; + mPlayerContext->protectionEvent[eGST_MEDIATYPE_AUDIO] = reinterpret_cast(0x1234); + + GstPad pad = {}; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(GST_ELEMENT(stream->source), StrEq("src"))) + .WillRepeatedly(Return(&pad)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(_,_)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(_)).Times(2); + + mInterfacePrivatePlayer->SendGstEvents(mediaType, pts,mPlayerConfigParams->enableGstPosQuery , mPlayerConfigParams->enablePTSReStamp, mPlayerConfigParams->vodTrickModeFPS); + + EXPECT_FALSE(stream->resetPosition); +} + +TEST_F(InterfacePlayerTests, SendGstEvents_ProtectionEventSameTrack) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + mPlayerConfigParams->enablePTSReStamp = TRUE; + mPlayerConfigParams->vodTrickModeFPS = 24; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->source = &gst_element_pipeline; + mPlayerContext->protectionEvent[mediaType] = reinterpret_cast(0x1234); + + GstPad pad = {}; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(GST_ELEMENT(stream->source), StrEq("src"))) + .WillRepeatedly(Return(&pad)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(_,_)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(_)).Times(2); + + mInterfacePrivatePlayer->SendGstEvents(mediaType, pts,mPlayerConfigParams->enableGstPosQuery , mPlayerConfigParams->enablePTSReStamp, mPlayerConfigParams->vodTrickModeFPS); + + EXPECT_FALSE(stream->resetPosition); +} + +TEST_F(InterfacePlayerTests, SendQtDemuxOverrideEvent_EnablePTSReStampFalse) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + const void *ptr = nullptr; + size_t len = 0; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->source = &gst_element_pipeline; + mPlayerConfigParams->enablePTSReStamp = false; + mPlayerContext->rate = 2.0; + mPlayerConfigParams->vodTrickModeFPS = 30; + mInterfacePrivatePlayer->mPlayerName = "testPlayer"; + + GstPad pad = {}; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(GST_ELEMENT(stream->source), StrEq("src"))) + .WillRepeatedly(Return(&pad)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(_, _)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(_)).Times(1); + + gboolean result = mInterfacePrivatePlayer->SendQtDemuxOverrideEvent(mediaType, pts, mPlayerConfigParams->enablePTSReStamp , mPlayerConfigParams->vodTrickModeFPS , ptr, len); + + EXPECT_TRUE(result); +} + +TEST_F(InterfacePlayerTests, SendQtDemuxOverrideEvent_EnablePTSReStampTrue) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + const void *ptr = nullptr; + size_t len = 0; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->source = &gst_element_pipeline; + mPlayerConfigParams->enablePTSReStamp = true; + mPlayerContext->rate = 1.0; + mPlayerConfigParams->vodTrickModeFPS = 30; + mInterfacePrivatePlayer->mPlayerName = "testPlayer"; + + GstPad pad = {}; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(GST_ELEMENT(stream->source), StrEq("src"))) + .WillRepeatedly(Return(&pad)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(_, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(_)).Times(1); + + gboolean result = mInterfacePrivatePlayer->SendQtDemuxOverrideEvent(mediaType, pts, mPlayerConfigParams->enablePTSReStamp , mPlayerConfigParams->vodTrickModeFPS , ptr, len); + + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, ForwardAudioBuffersToAux_True) +{ + mPlayerContext->forwardAudioBuffers = true; + mPlayerContext->stream[eGST_MEDIATYPE_AUX_AUDIO].format = GST_FORMAT_ISO_BMFF; + + EXPECT_TRUE(mInterfaceGstPlayer->ForwardAudioBuffersToAux()); + + mPlayerContext->forwardAudioBuffers = false; + mPlayerContext->stream[eGST_MEDIATYPE_AUX_AUDIO].format = GST_FORMAT_ISO_BMFF; + + EXPECT_FALSE(mInterfaceGstPlayer->ForwardAudioBuffersToAux()); + + mPlayerContext->forwardAudioBuffers = true; + mPlayerContext->stream[eGST_MEDIATYPE_AUX_AUDIO].format = GST_FORMAT_INVALID; + + EXPECT_FALSE(mInterfaceGstPlayer->ForwardAudioBuffersToAux()); +} + +TEST_F(InterfacePlayerTests, GetVideoRectangle) +{ + std::string expectedRectangle = "0,0,1920,1080"; + strncpy(mPlayerContext->videoRectangle, expectedRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + EXPECT_EQ(mInterfaceGstPlayer->GetVideoRectangle(), expectedRectangle); +} + +TEST_F(InterfacePlayerTests, GstSetSubtitlePtsOffset_WithSubtitleSink) +{ + GstElement subtitle_sink = {}; + mPlayerContext->subtitle_sink = &subtitle_sink; + std::uint64_t pts_offset = 1000; + mInterfaceGstPlayer->SetSubtitlePtsOffset(pts_offset); +} + +TEST_F(InterfacePlayerTests, GstSetSubtitlePtsOffset_WithoutSubtitleSink) +{ + mPlayerContext->subtitle_sink = nullptr; + mInterfaceGstPlayer->SetSubtitlePtsOffset(1000); +} + +TEST_F(InterfacePlayerTests, GstResetFirstFrame) +{ + mPlayerContext->firstFrameReceived = true; + mInterfaceGstPlayer->ResetFirstFrame(); + EXPECT_FALSE(mPlayerContext->firstFrameReceived); +} + +TEST_F(InterfacePlayerTests, GstGetVideoPlaybackQuality_StatsNull) +{ + GstElement video_sink = {}; + GstStructure stats = {1}; + mPlayerContext->video_sink = &video_sink; + GstPlaybackQualityStruct* result = mInterfaceGstPlayer->GetVideoPlaybackQuality(); + EXPECT_EQ(result,nullptr); +} + +TEST_F(InterfacePlayerTests, GstGetPositionMilliseconds) +{ + mPlayerContext->pipeline = nullptr; + + long long result = mInterfaceGstPlayer->GetPositionMilliseconds(); + + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 0); + + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->positionQuery = nullptr; + + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 0); + + mPlayerContext->positionQuery = &query; + mPlayerContext->pipelineState = GST_STATE_READY; + mPlayerContext->paused = false; + + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 0); + + mPlayerContext->pipelineState = GST_STATE_PLAYING; + mPlayerContext->paused = false; + mPlayerContext->segmentStart = -1; + EXPECT_CALL(*g_mockGStreamer, gst_element_query(_,_)).WillRepeatedly(Return(FALSE)); + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 0); + + EXPECT_CALL(*g_mockGStreamer, gst_element_query(_,_)).Times(2).WillOnce(Return(TRUE)).WillOnce(Return(FALSE)); + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 0); + + mPlayerContext->segmentStart = 1; + mPlayerConfigParams->media = eGST_MEDIAFORMAT_PROGRESSIVE; + gint64 pos = 5000000; + EXPECT_CALL(*g_mockGStreamer, gst_query_parse_position(_,_,_)) + .WillOnce(DoAll(SetArgPointee<2>(pos), Return())); + EXPECT_CALL(*g_mockGStreamer, gst_element_query(_,_)).WillRepeatedly(Return(TRUE)); + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 4); // {(5000000/1000000) -1 } * 4 + + mPlayerContext->segmentStart = -1; + EXPECT_CALL(*g_mockGStreamer, gst_query_parse_position(_,_,_)) + .WillOnce(DoAll(SetArgPointee<2>(pos), Return())); + EXPECT_CALL(*g_mockGStreamer, gst_element_query(_,_)).Times(2).WillOnce(Return(FALSE)).WillOnce(Return(TRUE)); + EXPECT_EQ(mInterfaceGstPlayer->GetPositionMilliseconds(), 5); // (5000000/1000000) * 4 +} + +TEST_F(InterfacePlayerTests, GstGetDurationMilliseconds_PipelineNull) +{ + //PipelineNull + mPlayerContext->pipeline = nullptr; + long result = mInterfaceGstPlayer->GetDurationMilliseconds(); + EXPECT_EQ(result, 0); +} + +TEST_F(InterfacePlayerTests, GstGetDurationMilliseconds_PipelineNotPlayingOrPaused) +{ + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->pipelineState = GST_STATE_READY; + mPlayerContext->paused = false; + + long result = mInterfaceGstPlayer->GetDurationMilliseconds(); + + EXPECT_EQ(result, 0); +} + +TEST_F(InterfacePlayerTests, GstGetVideoSize_ValidRectangle) +{ + int width = 0; + int height = 0; + std::string videoRectangle = "10,20,1920,1080"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->GetVideoSize(width, height); + + EXPECT_EQ(width, 1920); + EXPECT_EQ(height, 1080); +} + +TEST_F(InterfacePlayerTests, GstGetVideoSize_InvalidRectangle) +{ + int width = 0; + int height = 0; + std::string videoRectangle = "10,20,0,0"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->GetVideoSize(width, height); + + EXPECT_EQ(width, 0); + EXPECT_EQ(height, 0); +} + +TEST_F(InterfacePlayerTests, GstGetVideoSize_InvalidRectangle2) +{ + int width = 0; + int height = 0; + std::string videoRectangle = "10,20,-12,-240000000000"; //negative values , overflow values + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->GetVideoSize(width, height); + + EXPECT_EQ(width, 0); + EXPECT_EQ(height, 0); +} + +TEST_F(InterfacePlayerTests, GstGetVideoSize_PartialRectangle) +{ + int width = 0; + int height = 0; + std::string videoRectangle = "10,20,1920"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->GetVideoSize(width, height); + + EXPECT_EQ(width, 0); + EXPECT_EQ(height, 0); +} + +TEST_F(InterfacePlayerTests, GstGetVideoSize_EmptyRectangle) +{ + int width = 0; + int height = 0; + std::string videoRectangle = ""; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->GetVideoSize(width, height); + + EXPECT_EQ(width, 0); + EXPECT_EQ(height, 0); +} + +TEST_F(InterfacePlayerTests, GstSetSubtitleMute_WithSubtitleSink) +{ + GstElement subtitle_sink = {}; + mPlayerContext->subtitle_sink = &subtitle_sink; + + EXPECT_CALL(*g_mockGLib, g_object_set(&subtitle_sink, StrEq("mute"), Matcher(true))); + mInterfaceGstPlayer->SetSubtitleMute(true); + EXPECT_TRUE(mPlayerContext->subtitleMuted); + + EXPECT_CALL(*g_mockGLib, g_object_set(&subtitle_sink, StrEq("mute"), Matcher(false))); + mInterfaceGstPlayer->SetSubtitleMute(false); + EXPECT_FALSE(mPlayerContext->subtitleMuted); +} + +TEST_F(InterfacePlayerTests, GstSetSubtitleMute_WithoutSubtitleSink) +{ + mPlayerContext->subtitle_sink = nullptr; + + EXPECT_CALL(*g_mockGLib, g_object_set(_, StrEq("mute"),Matcher(_))).Times(0); + mInterfaceGstPlayer->SetSubtitleMute(true); + EXPECT_TRUE(mPlayerContext->subtitleMuted); + + mInterfaceGstPlayer->SetSubtitleMute(false); + EXPECT_FALSE(mPlayerContext->subtitleMuted); +} + +TEST_F(InterfacePlayerTests, GstSetVideoRectangle_SameCoordinates) +{ + int x = 10, y = 20, w = 1920, h = 1080; + std::string videoRectangle = "10,20,1920,1080"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mInterfaceGstPlayer->SetVideoRectangle(x, y, w, h); + + EXPECT_STREQ(mPlayerContext->videoRectangle, videoRectangle.c_str()); +} + +TEST_F(InterfacePlayerTests, GstSetVideoRectangle_DifferentCoordinates) +{ + int x = 10, y = 20, w = 1920, h = 1080; + std::string videoRectangle = "0,0,1280,720"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mPlayerConfigParams->enableRectPropertyCfg = true; + GstElement video_sink = {}; + mPlayerContext->video_sink = &video_sink; + + EXPECT_CALL(*g_mockGLib, g_object_set(&video_sink, StrEq("rectangle"), Matcher(_))); + + mInterfaceGstPlayer->SetVideoRectangle(x, y, w, h); + + EXPECT_STREQ(mPlayerContext->videoRectangle, "10,20,1920,1080"); +} + +TEST_F(InterfacePlayerTests, GstSetVideoRectangle_EnableRectPropertyCfgFalse) +{ + int x = 10, y = 20, w = 1920, h = 1080; + std::string videoRectangle = "0,0,1280,720"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mPlayerConfigParams->enableRectPropertyCfg = false; + + mInterfaceGstPlayer->SetVideoRectangle(x, y, w, h); + + EXPECT_STREQ(mPlayerContext->videoRectangle, "10,20,1920,1080"); +} + +TEST_F(InterfacePlayerTests, GstSetVideoRectangle_VideoSinkNull) +{ + int x = 10, y = 20, w = 1920, h = 1080; + std::string videoRectangle = "0,0,1280,720"; + strncpy(mPlayerContext->videoRectangle, videoRectangle.c_str(), sizeof(mPlayerContext->videoRectangle) - 1); + + mPlayerConfigParams->enableRectPropertyCfg = true; + mPlayerContext->video_sink = nullptr; + + mInterfaceGstPlayer->SetVideoRectangle(x, y, w, h); + + EXPECT_STREQ(mPlayerContext->videoRectangle, "10,20,1920,1080"); +} + + +TEST_F(InterfacePlayerTests, StopBuffering_ForceStopTrue) +{ + bool isPlaying = false; + bool forceStop = true; + + GstElement video_dec = {}; + mPlayerContext->video_dec = &video_dec; + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PLAYING), SetArgPointee<2>(GST_STATE_PLAYING), Return(GST_STATE_CHANGE_SUCCESS))); + + + bool result = mInterfaceGstPlayer->StopBuffering(forceStop, isPlaying); + + EXPECT_TRUE(result); + EXPECT_TRUE(isPlaying); +} + + +TEST_F(InterfacePlayerTests, StopBuffering_EnoughData) +{ + bool isPlaying = false; + bool forceStop = false; + + GstElement video_dec = {}; + mPlayerContext->video_dec = &video_dec; + mPlayerConfigParams->framesToQueue = 5; + + EXPECT_CALL(*g_mockGLib, g_object_get(&video_dec, StrEq("queued_frames"),Matcher(_))) + .WillOnce(DoAll(SetArgPointee<2>(6), Return())); + + GstElement pipeline = {}; + mPlayerContext->pipeline = &pipeline; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(_, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PLAYING), SetArgPointee<2>(GST_STATE_NULL), Return(GST_STATE_CHANGE_SUCCESS))); + + bool result = mInterfaceGstPlayer->StopBuffering(forceStop, isPlaying); + + EXPECT_TRUE(result); + EXPECT_TRUE(isPlaying); +} + +TEST_F(InterfacePlayerTests, StopBuffering_NotEnoughData) +{ + bool isPlaying = false; + bool forceStop = false; + + GstElement video_dec = {}; + mPlayerContext->video_dec = &video_dec; + mPlayerConfigParams->framesToQueue = 5; + + EXPECT_CALL(*g_mockGLib, g_object_get(&video_dec, StrEq("queued_frames"),Matcher(_))) + .WillOnce(DoAll(SetArgPointee<2>(4), Return())); + + bool result = mInterfaceGstPlayer->StopBuffering(forceStop, isPlaying); + + EXPECT_FALSE(result); + EXPECT_FALSE(isPlaying); +} + +TEST_F(InterfacePlayerTests, StopBuffering_GstElementGetStateFailure) +{ + bool isPlaying = false; + bool forceStop = false; + + GstElement video_dec = {}; + mPlayerContext->video_dec = &video_dec; + mPlayerConfigParams->framesToQueue = 5; + + EXPECT_CALL(*g_mockGLib, g_object_get(&video_dec, StrEq("queued_frames"),Matcher(_))) + .WillOnce(DoAll(SetArgPointee<2>(6), Return())); + + GstElement pipeline = {}; + mPlayerContext->pipeline = &pipeline; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&pipeline, _, _, _)) + .WillOnce(Return(GST_STATE_CHANGE_FAILURE)); + + bool result = mInterfaceGstPlayer->StopBuffering(forceStop, isPlaying); + + EXPECT_FALSE(result); + EXPECT_FALSE(isPlaying); +} + +TEST_F(InterfacePlayerTests, WaitForSourceSetup_Success) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->sourceConfigured = false; + + std::thread setupThread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + { + std::lock_guard lock(mInterfaceGstPlayer->mSourceSetupMutex); + stream->sourceConfigured = true; + } + mInterfaceGstPlayer->mSourceSetupCV.notify_all(); + }); + + bool result = mInterfaceGstPlayer->WaitForSourceSetup(mediaType); + + EXPECT_TRUE(result); + EXPECT_TRUE(stream->sourceConfigured); + + setupThread.join(); +} + +TEST_F(InterfacePlayerTests, WaitForSourceSetup_Timeout) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->sourceConfigured = false; + + bool result = mInterfaceGstPlayer->WaitForSourceSetup(mediaType); + + EXPECT_FALSE(result); + EXPECT_FALSE(stream->sourceConfigured); +} + +TEST_F(InterfacePlayerTests, WaitForSourceSetup_PauseInjector) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->sourceConfigured = false; + mInterfaceGstPlayer->mPauseInjector = true; + + bool result = mInterfaceGstPlayer->WaitForSourceSetup(mediaType); + + EXPECT_FALSE(result); + EXPECT_FALSE(stream->sourceConfigured); +} + +TEST_F(InterfacePlayerTests, ForwardBuffersToAuxPipeline_WaitForSourceSetupFailed) +{ + GstBuffer buffer = {}; + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_AUX_AUDIO]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->sourceConfigured = false; + + EXPECT_CALL(*g_mockGStreamer, gst_buffer_copy_into(_, _, _, _, _)).Times(0); + EXPECT_CALL(*g_mockGStreamer, gst_app_src_push_buffer(_, _)).Times(0); + + // Run the method + mInterfacePrivatePlayer->ForwardBuffersToAuxPipeline(&buffer, true, mInterfaceGstPlayer); +} + +TEST_F(InterfacePlayerTests, ForwardBuffersToAuxPipeline_PushBufferFailed) +{ + GstBuffer buffer = {}; + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_AUX_AUDIO]; + stream->sourceConfigured = true; + stream->format = GST_FORMAT_ISO_BMFF; + stream->source = &gst_element_pipeline; + + GstBuffer fwdBuffer = {}; + //assert(false) in source code causes premature exit which causes expect_call to fail + ON_CALL(*g_mockGStreamer, gst_buffer_new()) + .WillByDefault(Return(&fwdBuffer)); + ON_CALL(*g_mockGStreamer, gst_buffer_copy_into(_,_,_,_,_)) + .WillByDefault(Return(TRUE)); + ON_CALL(*g_mockGStreamer, gst_app_src_push_buffer(_,_)) + .WillByDefault(Return(GST_FLOW_ERROR)); + + //catches the assert(false) in the function + EXPECT_DEATH(mInterfacePrivatePlayer->ForwardBuffersToAuxPipeline(&buffer,true,mInterfaceGstPlayer), "Assertion"); +} + +TEST_F(InterfacePlayerTests, HandleVideoBufferSent_SubsequentBuffer) +{ + mPlayerContext->numberOfVideoBuffersSent = 5; + bool result = mInterfaceGstPlayer->HandleVideoBufferSent(); + + EXPECT_FALSE(result); + EXPECT_EQ(mPlayerContext->numberOfVideoBuffersSent, 6); +} + +TEST_F(InterfacePlayerTests, SetPlayerName) +{ + std::string testName = "TestPlayer"; + mInterfaceGstPlayer->SetPlayerName(testName); + EXPECT_EQ(mInterfacePrivatePlayer->mPlayerName, testName); +} + +TEST_F(InterfacePlayerTests, PauseInjector_SetsPauseInjectorToTrue) +{ + mInterfaceGstPlayer->mPauseInjector = false; + mInterfaceGstPlayer->PauseInjector(); + EXPECT_TRUE(mInterfaceGstPlayer->mPauseInjector); +} + +TEST_F(InterfacePlayerTests, ResumeInjector_SetsPauseInjectorToFalseAndNotifiesAll) +{ + mInterfaceGstPlayer->mPauseInjector = true; + mInterfaceGstPlayer->ResumeInjector(); + EXPECT_FALSE(mInterfaceGstPlayer->mPauseInjector); +} + +TEST_F(InterfacePlayerTests, SendNewSegmentEvent_VideoMediaType) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime startPts = 1000; + GstClockTime stopPts = 2000; + mPlayerContext->stream[mediaType].format = GST_FORMAT_ISO_BMFF; + + GstPad gst_pad = {}; + GstEvent gst_event = {}; + + // Mock the necessary GStreamer functions and objects + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(_, StrEq("src"))) + .WillRepeatedly(Return(&gst_pad)); + EXPECT_CALL(*g_mockGStreamer, gst_segment_init(_, GST_FORMAT_TIME)) + .WillRepeatedly(DoAll(SetArgPointee<0>(GstSegment{}), Return())); + EXPECT_CALL(*g_mockGStreamer, gst_event_new_segment(_)) + .WillRepeatedly(Return(&gst_event)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(_, _)) + .WillOnce(Return(TRUE)).WillOnce(Return(FALSE)); // success and failure case + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(_)) + .Times(2); + + + mInterfacePrivatePlayer->SendNewSegmentEvent(mediaType, startPts, stopPts); //success + mInterfacePrivatePlayer->SendNewSegmentEvent(mediaType, startPts, stopPts); //failure +} + +TEST_F(InterfacePlayerTests, Queue_and_ClearProtectionEvent) +{ + std::string formatType = "cenc"; + const char *protSystemId = "test-system-id"; + const char initData[] = "test-init-data"; + size_t initDataSize = sizeof(initData); + int mediaType = eGST_MEDIATYPE_VIDEO; + GstBuffer gst_buffer = {}; + GstEvent gst_event = {}; + + // Mock the necessary GStreamer functions and objects + EXPECT_CALL(*g_mockGStreamer, gst_buffer_new_wrapped(NotNull(), initDataSize)) + .WillRepeatedly(Return(&gst_buffer)); + EXPECT_CALL(*g_mockGStreamer, gst_event_new_protection(StrEq(protSystemId), &gst_buffer, StrEq(formatType.c_str()))) + .WillRepeatedly(Return(&gst_event)); + + mInterfaceGstPlayer->QueueProtectionEvent(formatType, protSystemId, initData, initDataSize, mediaType); + + //call again to test GstPrivateContext->protectionEvent[type] != NULL + mInterfaceGstPlayer->QueueProtectionEvent(formatType, protSystemId, initData, initDataSize, mediaType); + + // Verify the protection event is queued + EXPECT_EQ(mPlayerContext->protectionEvent[mediaType], &gst_event); + EXPECT_NE(mPlayerContext->protectionEvent[mediaType],nullptr) ; + + //test InterfacePlayerRDK::ClearProtectionEvent() + mInterfaceGstPlayer->ClearProtectionEvent(); + EXPECT_EQ(mPlayerContext->protectionEvent[mediaType], nullptr); +} + +TEST_F(InterfacePlayerTests, Pause_Success) +{ + bool pause = true; + bool forceStopGstreamerPreBuffering = true; + + // Set pipeline to a non-null value + mPlayerContext->pipeline = &gst_element_pipeline; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(_, NotNull(), NotNull(), _)) + .WillRepeatedly(Return(GST_STATE_CHANGE_SUCCESS)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(_, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_ASYNC)); + + bool result = mInterfaceGstPlayer->Pause(pause, forceStopGstreamerPreBuffering); + + EXPECT_TRUE(result); + EXPECT_FALSE(mPlayerContext->buffering_in_progress); + EXPECT_EQ(mPlayerContext->buffering_target_state, GST_STATE_PAUSED); + EXPECT_TRUE(mPlayerContext->paused); + EXPECT_FALSE(mPlayerContext->pendingPlayState); +} + + +TEST_F(InterfacePlayerTests, Pause_PipelineNull) +{ + bool pause = true; + bool forceStopGstreamerPreBuffering = false; + + // Set pipeline to NULL + mPlayerContext->pipeline = NULL; + bool result = mInterfaceGstPlayer->Pause(pause, forceStopGstreamerPreBuffering); + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, Pause_Failure) +{ + bool pause = true; + bool forceStopGstreamerPreBuffering = false; + + // Set pipeline to a non-null value + mPlayerContext->pipeline = &gst_element_pipeline; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(_, NotNull(), NotNull(), _)) + .WillRepeatedly(Return(GST_STATE_CHANGE_SUCCESS)); + + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(_, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_FAILURE)); + + bool result = mInterfaceGstPlayer->Pause(pause, forceStopGstreamerPreBuffering); + + EXPECT_TRUE(result); +} + +TEST_F(InterfacePlayerTests, IsCacheEmpty_SourceNull) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->source = nullptr; + + bool result = mInterfaceGstPlayer->IsCacheEmpty(mediaType); + + EXPECT_TRUE(result); +} + +TEST_F(InterfacePlayerTests, IsCacheEmpty_CacheLevelNotEmpty) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->source = &gst_element_pipeline; + + EXPECT_CALL(*g_mockGStreamer, gst_app_src_get_current_level_bytes(GST_APP_SRC(stream->source))) + .WillOnce(Return(1000)); + + bool result = mInterfaceGstPlayer->IsCacheEmpty(mediaType); + + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, IsCacheEmpty_CacheLevelEmpty_BufferUnderrun) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->source = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun = true; + + EXPECT_CALL(*g_mockGStreamer, gst_app_src_get_current_level_bytes(GST_APP_SRC(stream->source))) + .WillOnce(Return(0)); + + bool result = mInterfaceGstPlayer->IsCacheEmpty(mediaType); + + EXPECT_TRUE(result); +} + +TEST_F(InterfacePlayerTests, IsCacheEmpty_CacheLevelEmpty_PTSChanged) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->source = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].bufferUnderrun = false; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].bufferUnderrun = false; + + EXPECT_CALL(*g_mockGStreamer, gst_app_src_get_current_level_bytes(GST_APP_SRC(stream->source))) + .WillOnce(Return(0)); + + bool result = mInterfaceGstPlayer->IsCacheEmpty(mediaType); + + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, ResetEOSSignalledFlag) +{ + mPlayerContext->eosSignalled = true; + mInterfaceGstPlayer->ResetEOSSignalledFlag(); + EXPECT_FALSE(mPlayerContext->eosSignalled); +} + +TEST_F(InterfacePlayerTests, PipelineConfiguredForMedia_True) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->sourceConfigured = true; + + bool result = mInterfaceGstPlayer->PipelineConfiguredForMedia(mediaType); + + EXPECT_TRUE(result); +} + +TEST_F(InterfacePlayerTests, PipelineConfiguredForMedia_False) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->sourceConfigured = false; + + bool result = mInterfaceGstPlayer->PipelineConfiguredForMedia(mediaType); + + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, GetBufferControlData_PausedState) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + GstElement sinkbin = {}; + stream->sinkbin = &sinkbin; + mPlayerContext->paused = false; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&sinkbin, NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PAUSED), SetArgPointee<2>(GST_STATE_NULL), Return(GST_STATE_CHANGE_SUCCESS))); + + bool result = mInterfaceGstPlayer->GetBufferControlData(mediaType); + + EXPECT_TRUE(result); + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&sinkbin, NotNull(), NotNull(), _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PLAYING), SetArgPointee<2>(GST_STATE_NULL), Return(GST_STATE_CHANGE_SUCCESS))); + + result = mInterfaceGstPlayer->GetBufferControlData(mediaType); + + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, IsStreamReadyTest) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + GstElement sinkbin = {}; + stream->sinkbin = &sinkbin; + stream->sourceConfigured = true; + + EXPECT_TRUE(mInterfaceGstPlayer->IsStreamReady(mediaType)); + + stream->sinkbin = nullptr; + stream->sourceConfigured = true; + EXPECT_FALSE(mInterfaceGstPlayer->IsStreamReady(mediaType)); + + sinkbin = {}; + stream->sinkbin = &sinkbin; + stream->sourceConfigured = false; + EXPECT_FALSE(mInterfaceGstPlayer->IsStreamReady(mediaType)); + + + stream->sinkbin = nullptr; + stream->sourceConfigured = false; + EXPECT_FALSE(mInterfaceGstPlayer->IsStreamReady(mediaType)); +} + +TEST_F(InterfacePlayerTests, SignalTrickModeDiscontinuity_TrickmodeSuccess) +{ + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_VIDEO]; + stream->source = &gst_element_pipeline; + mPlayerContext->rate = 2; // != NORMAL_PLAY_RATE + mPlayerConfigParams->vodTrickModeFPS = 30; + + GstPad gst_pad = {}; + GstEvent gst_event = {}; + GstStructure gst_structure = {}; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(_, StrEq("src"))) + .WillOnce(Return(&gst_pad)); + EXPECT_CALL(*g_mockGStreamer, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,_)) + .WillOnce(Return(&gst_event)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(&gst_pad,_)) + .WillOnce(Return(TRUE)); // Success case + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(&gst_pad)) + .Times(1); + + mInterfaceGstPlayer->SignalTrickModeDiscontinuity(); +} + +TEST_F(InterfacePlayerTests, SignalTrickModeDiscontinuity_TrickmodeFail) +{ + gst_media_stream* stream = &mPlayerContext->stream[eGST_MEDIATYPE_VIDEO]; + stream->source = &gst_element_pipeline; + mPlayerContext->rate = 2; // != NORMAL_PLAY_RATE + mPlayerConfigParams->vodTrickModeFPS = 30; + + GstPad gst_pad = {}; + GstEvent gst_event = {}; + GstStructure gst_structure = {}; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(_, StrEq("src"))) + .WillOnce(Return(&gst_pad)); + EXPECT_CALL(*g_mockGStreamer, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM,_)) + .WillOnce(Return(&gst_event)); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(&gst_pad,_)) + .WillOnce(Return(false)); // Failure case + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(&gst_pad)) + .Times(1); + + mInterfaceGstPlayer->SignalTrickModeDiscontinuity(); +} + +TEST_F(InterfacePlayerTests, EnableGstDebugLogging) +{ + std::string debugLevel = ""; + EXPECT_CALL(*g_mockGStreamer, gst_debug_set_threshold_from_string(_, _)).Times(0); + mInterfaceGstPlayer->EnableGstDebugLogging(debugLevel); + + debugLevel = "GST_LEVEL_DEBUG"; + EXPECT_CALL(*g_mockGStreamer, gst_debug_set_threshold_from_string(StrEq(debugLevel.c_str()), 1)).Times(1); + mInterfaceGstPlayer->EnableGstDebugLogging(debugLevel); +} + +TEST_F(InterfacePlayerTests, CheckDiscontinuity_FirstBufferNotProcessed) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + int streamFormat = GST_FORMAT_ISO_BMFF; + bool codecChange = false; + bool unblockDiscProcess = false; + bool shouldHaltBuffering = false; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->firstBufferProcessed = false; + + bool result = mInterfaceGstPlayer->CheckDiscontinuity(mediaType, streamFormat, codecChange, unblockDiscProcess, shouldHaltBuffering); + + EXPECT_FALSE(result); + EXPECT_FALSE(unblockDiscProcess); + EXPECT_FALSE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, CheckDiscontinuity_AudioDiscontinuity) +{ + int mediaType = eGST_MEDIATYPE_AUDIO; + int streamFormat = GST_FORMAT_ISO_BMFF; + bool codecChange = true; + bool unblockDiscProcess = false; + bool shouldHaltBuffering = false; + + mPlayerConfigParams->enablePTSReStamp = true; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->firstBufferProcessed = true; + + gst_media_stream* subtitleStream = &mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE]; + subtitleStream->source = &gst_element_pipeline; + + + bool result = mInterfaceGstPlayer->CheckDiscontinuity(mediaType, streamFormat, codecChange, unblockDiscProcess, shouldHaltBuffering); + + EXPECT_TRUE(result); + EXPECT_FALSE(unblockDiscProcess); + EXPECT_TRUE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, CheckDiscontinuity_EnablePTSReStamp_NoCodecChange) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + int streamFormat = GST_FORMAT_ISO_BMFF; + bool codecChange = false; + bool unblockDiscProcess = false; + bool shouldHaltBuffering = false; + + mPlayerConfigParams->enablePTSReStamp = true; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_ISO_BMFF; + stream->firstBufferProcessed = true; + + bool result = mInterfaceGstPlayer->CheckDiscontinuity(mediaType, streamFormat, codecChange, unblockDiscProcess, shouldHaltBuffering); + + EXPECT_TRUE(result); + EXPECT_TRUE(unblockDiscProcess); + EXPECT_FALSE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, TimerAdd_ValidFunctionAndUserData) +{ + GSourceFunc funcPtr = [](gpointer data) -> gboolean { return G_SOURCE_CONTINUE; }; + int repeatTimeout = 1000; + guint taskId = 0; + gpointer user_data = reinterpret_cast(0x1234); + const char* timerName = "TestTimer"; + + EXPECT_CALL(*g_mockGLib, g_timeout_add(repeatTimeout, funcPtr, user_data)) + .WillOnce(Return(1)); + + mInterfaceGstPlayer->TimerAdd(funcPtr, repeatTimeout, taskId, user_data, timerName); + + EXPECT_EQ(taskId, 1); +} + +TEST_F(InterfacePlayerTests, TimerAdd_TaskAlreadyAdded) +{ + GSourceFunc funcPtr = [](gpointer data) -> gboolean { return G_SOURCE_CONTINUE; }; + int repeatTimeout = 1000; + guint taskId = 1; // Task already added + gpointer user_data = reinterpret_cast(0x1234); + const char* timerName = "TestTimer"; + + mInterfaceGstPlayer->TimerAdd(funcPtr, repeatTimeout, taskId, user_data, timerName); + + EXPECT_EQ(taskId, 1); +} + +TEST_F(InterfacePlayerTests, TimerAdd_InvalidFunctionPointer) +{ + GSourceFunc funcPtr = nullptr; // Invalid function pointer + int repeatTimeout = 1000; + guint taskId = 0; + gpointer user_data = reinterpret_cast(0x1234); + const char* timerName = "TestTimer"; + + mInterfaceGstPlayer->TimerAdd(funcPtr, repeatTimeout, taskId, user_data, timerName); + + EXPECT_EQ(taskId, 0); +} + +TEST_F(InterfacePlayerTests, TimerAdd_InvalidUserData) +{ + GSourceFunc funcPtr = [](gpointer data) -> gboolean { return G_SOURCE_CONTINUE; }; + int repeatTimeout = 1000; + guint taskId = 0; + gpointer user_data = nullptr; // Invalid user data + const char* timerName = "TestTimer"; + + mInterfaceGstPlayer->TimerAdd(funcPtr, repeatTimeout, taskId, user_data, timerName); + + EXPECT_EQ(taskId, 0); +} + +TEST_F(InterfacePlayerTests, TimerIsRunning_TaskRunning) +{ + guint taskId = 1; // Task is running + bool result = mInterfaceGstPlayer->TimerIsRunning(taskId); + EXPECT_TRUE(result); + + taskId = PLAYER_TASK_ID_INVALID; // Task is not running + result = mInterfaceGstPlayer->TimerIsRunning(taskId); + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, IdleTaskClearFlags_Success) +{ + GstTaskControlData taskDetails("TestTask"); + taskDetails.taskID = 1; + taskDetails.taskIsPending = true; + + mInterfaceGstPlayer->IdleTaskClearFlags(taskDetails); + + EXPECT_FALSE(taskDetails.taskIsPending); + EXPECT_EQ(taskDetails.taskID, 0); +} + +TEST_F(InterfacePlayerTests, IdleTaskClearFlags_Fail) +{ + GstTaskControlData taskDetails("TestTask2"); + taskDetails.taskID = 0; + taskDetails.taskIsPending = true; + + mInterfaceGstPlayer->IdleTaskClearFlags(taskDetails); + + EXPECT_FALSE(taskDetails.taskIsPending); + EXPECT_EQ(taskDetails.taskID, 0); +} + +TEST_F(InterfacePlayerTests, IdleTaskAdd_TaskNotPending) +{ + + GstTaskControlData taskDetails("TestTask"); + BackgroundTask funcPtr = [](void* data) -> int { return 0;}; + + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)) + .WillOnce(Return(1)); + + + bool result = mInterfaceGstPlayer->IdleTaskAdd(taskDetails, funcPtr); + + EXPECT_TRUE(result); + EXPECT_TRUE(taskDetails.taskIsPending); + EXPECT_NE(taskDetails.taskID, 0); +} + +TEST_F(InterfacePlayerTests, IdleTaskAdd_TaskAlreadyPending) +{ + GstTaskControlData taskDetails("TestTask"); + taskDetails.taskID = 1; + taskDetails.taskIsPending = true; + BackgroundTask funcPtr = [](void* data) -> int { return 0;}; + + bool result = mInterfaceGstPlayer->IdleTaskAdd(taskDetails, funcPtr); + + EXPECT_FALSE(result); + EXPECT_TRUE(taskDetails.taskIsPending); + EXPECT_EQ(taskDetails.taskID, 1); +} + +TEST_F(InterfacePlayerTests, IdleTaskAdd_TaskFailedToAdd) +{ + GstTaskControlData taskDetails("TestTask"); + BackgroundTask funcPtr = [](void* data) -> int { return 0;}; + + // Simulate failure to add task + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)) + .WillOnce(Return(0)); + + bool result = mInterfaceGstPlayer->IdleTaskAdd(taskDetails, funcPtr); + + EXPECT_FALSE(result); + EXPECT_FALSE(taskDetails.taskIsPending); + EXPECT_EQ(taskDetails.taskID, 0); +} + +TEST_F(InterfacePlayerTests, FirstFrameCallback_SetAndInvoke) +{ + bool callbackInvoked = false; + GstMediaType expectedMediaType = eGST_MEDIATYPE_VIDEO; + bool notifyFirstBuffer = false; + bool decoderHandleNotified = false; + bool requireFirstVideoFrameDisplay = false; + bool audioOnly = false; + + // Set the callback using lambda + mInterfaceGstPlayer->FirstFrameCallback( + [&](int mediaType, bool notifyBuffer, bool decoderNotified, bool& requireFirstVideoFrame, bool& audio) { + callbackInvoked = true; + EXPECT_EQ(mediaType, static_cast(expectedMediaType)); + EXPECT_EQ(notifyBuffer, notifyFirstBuffer); + EXPECT_EQ(decoderNotified, decoderHandleNotified); + + requireFirstVideoFrame = true; + audio = true; + }); + + // Invoke the callback + mInterfaceGstPlayer->notifyFirstFrameCallback(expectedMediaType, notifyFirstBuffer, decoderHandleNotified, + requireFirstVideoFrameDisplay, audioOnly); + + // Validate callback was invoked and reference outputs are updated + EXPECT_TRUE(callbackInvoked); + EXPECT_TRUE(requireFirstVideoFrameDisplay); + EXPECT_TRUE(audioOnly); +} + +TEST_F(InterfacePlayerTests, StopCallback_SetAndInvoke) +{ + bool callbackInvoked = false; + bool status = true; + + mInterfaceGstPlayer->StopCallback([&](bool stat) { + callbackInvoked = true; + EXPECT_EQ(stat, status); + }); + + mInterfaceGstPlayer->stopCallback(status); + + EXPECT_TRUE(callbackInvoked); +} + +TEST_F(InterfacePlayerTests, TearDownCallback_SetAndInvoke) +{ + bool callbackInvoked = false; + bool status = true; + int mediaType = eGST_MEDIATYPE_VIDEO; + + mInterfaceGstPlayer->TearDownCallback([&](bool stat, int type) { + callbackInvoked = true; + EXPECT_EQ(stat, status); + EXPECT_EQ(type, mediaType); + }); + + mInterfaceGstPlayer->tearDownCb(status, mediaType); + + EXPECT_TRUE(callbackInvoked); +} + +TEST_F(InterfacePlayerTests, NotifyFirstFrame_VideoType_FirstFrameNotReceived) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + mPlayerContext->firstFrameReceived = false; + mPlayerContext->firstVideoFrameReceived = true; + mPlayerContext->NumberOfTracks = 1; + mPlayerContext->firstAudioFrameReceived = false; + mPlayerContext->decoderHandleNotified = false; + mInterfaceGstPlayer->PipelineSetToReady = true; + + bool callbackInvoked = false; + mInterfaceGstPlayer->FirstFrameCallback([&](int type, bool notifyBuffer, bool decoderNotified, bool& requireFirstVideoFrame, bool& audio) { + callbackInvoked = true; + EXPECT_EQ(type, eGST_MEDIATYPE_VIDEO); + EXPECT_TRUE(notifyBuffer); + EXPECT_TRUE(decoderNotified); + requireFirstVideoFrame = true; + audio = false; + }); + + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)).Times(3) + .WillOnce(Return(1)).WillOnce(Return(0)).WillOnce(Return(0)); + + mInterfaceGstPlayer->NotifyFirstFrame(mediaType); + + EXPECT_TRUE(callbackInvoked); + EXPECT_TRUE(mPlayerContext->firstFrameReceived); + EXPECT_TRUE(mPlayerContext->decoderHandleNotified); + EXPECT_TRUE(mPlayerContext->firstFrameCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->firstFrameCallbackIdleTaskId, 1); + EXPECT_FALSE(mInterfaceGstPlayer->PipelineSetToReady); +} + +TEST_F(InterfacePlayerTests, NotifyFirstFrame_AudioType_FirstFrameNotReceived) +{ + int mediaType = eGST_MEDIATYPE_AUDIO; + mPlayerContext->firstFrameReceived = false; + mPlayerContext->firstAudioFrameReceived = true; + mPlayerContext->NumberOfTracks = 1; + mPlayerContext->firstVideoFrameReceived = false; + mPlayerContext->decoderHandleNotified = false; + mInterfaceGstPlayer->PipelineSetToReady = true; + + bool callbackInvoked = false; + mInterfaceGstPlayer->FirstFrameCallback([&](int type, bool notifyBuffer, bool decoderNotified, bool& requireFirstVideoFrame, bool& audio) { + callbackInvoked = true; + EXPECT_EQ(type, eGST_MEDIATYPE_AUDIO); + EXPECT_TRUE(notifyBuffer); + EXPECT_TRUE(decoderNotified); + requireFirstVideoFrame = false; + audio = true; + }); + + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)) + .WillRepeatedly(Return(1)); + + mInterfaceGstPlayer->NotifyFirstFrame(mediaType); + + EXPECT_TRUE(callbackInvoked); + EXPECT_TRUE(mPlayerContext->firstFrameReceived); + EXPECT_TRUE(mPlayerContext->decoderHandleNotified); + EXPECT_TRUE(mPlayerContext->firstFrameCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->firstFrameCallbackIdleTaskId, 1); +} + +TEST_F(InterfacePlayerTests, TriggerEvent_CallbackExists) +{ + InterfaceCB event = InterfaceCB::idleCb; + bool callbackInvoked = false; + + mInterfaceGstPlayer->callbackMap[event] = [&]() { + callbackInvoked = true; + }; + + mInterfaceGstPlayer->TriggerEvent(event); + + EXPECT_TRUE(callbackInvoked); +} + +TEST_F(InterfacePlayerTests, TriggerEvent_CallbackDoesNotExist) +{ + InterfaceCB event = InterfaceCB::idleCb; + mInterfaceGstPlayer->TriggerEvent(event); +} + +TEST_F(InterfacePlayerTests, TriggerEventWithData_CallbackExists) +{ + InterfaceCB event = InterfaceCB::idleCb; + int expectedData = 42; + bool callbackInvoked = false; + + mInterfaceGstPlayer->setupStreamCallbackMap[event] = [&](int data) { + callbackInvoked = true; + EXPECT_EQ(data, expectedData); + }; + mInterfaceGstPlayer->TriggerEvent(event, expectedData); + + EXPECT_TRUE(callbackInvoked); +} + +TEST_F(InterfacePlayerTests, TriggerEventWithData_CallbackDoesNotExist) +{ + InterfaceCB event = InterfaceCB::idleCb; + int data = 42; + mInterfaceGstPlayer->TriggerEvent(event, data); +} + +extern bool gst_StartsWith( const char *inputStr, const char *prefix ); +TEST_F(InterfacePlayerTests, StartsWithTest) +{ + EXPECT_TRUE(gst_StartsWith("TestString", "Test")); +} + +#define DEFAULT_BUFFERING_MAX_CNT (100) +TEST_F(InterfacePlayerTests, CreatePipeline_Success) +{ + const char* pipelineName = "testPipeline"; + int pipelinePriority = 1; + + //force destroy old pipeline + GstElement gst_element_pipeline2 = {.object = {.name = (gchar *)pipelineName}}; + mPlayerContext->pipeline = &gst_element_pipeline2; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)pipelineName}}; + GstBus gst_element_bus = {.object = {.name = (gchar *)"testbus"}}; + GstTaskPool gst_element_task_pool = {.object = {.name = (gchar *)"testtaskpool"}}; + GstQuery* gst_element_query = new GstQuery(); + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq(pipelineName))) + .WillOnce(Return(&gst_element_pipeline)); + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(GST_PIPELINE(&gst_element_pipeline))) + .WillOnce(Return(&gst_element_bus)); + mPlayerContext->task_pool = &gst_element_task_pool; + EXPECT_CALL(*g_mockGStreamer, gst_bus_add_watch(&gst_element_bus, _, _)) + .WillOnce(Return(1)); + EXPECT_CALL(*g_mockGStreamer, gst_bus_set_sync_handler(&gst_element_bus, _, _, nullptr)); + EXPECT_CALL(*g_mockGStreamer, gst_query_new_position(GST_FORMAT_TIME)) + .WillOnce(Return(gst_element_query)); + + bool result = mInterfaceGstPlayer->CreatePipeline(pipelineName, pipelinePriority); + + EXPECT_TRUE(result); + EXPECT_EQ(mPlayerContext->pipeline, &gst_element_pipeline); + EXPECT_EQ(mPlayerContext->bus, &gst_element_bus);; + EXPECT_EQ(mPlayerContext->positionQuery, gst_element_query); + EXPECT_EQ(mPlayerContext->buffering_enabled, mPlayerConfigParams->gstreamerBufferingBeforePlay); + EXPECT_FALSE(mPlayerContext->buffering_in_progress); + EXPECT_EQ(mPlayerContext->buffering_timeout_cnt, DEFAULT_BUFFERING_MAX_CNT); + EXPECT_EQ(mPlayerContext->buffering_target_state, GST_STATE_NULL); + EXPECT_EQ(mPlayerContext->enableSEITimeCode, mPlayerConfigParams->seiTimeCode); +} + +TEST_F(InterfacePlayerTests, CreatePipeline_Failure_GetBusFailed) +{ + const char* pipelineName = "testPipeline"; + int pipelinePriority = 1; + + GstElement gst_element_pipeline = {.object = {.name = (gchar *)pipelineName}}; + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq(pipelineName))) + .WillOnce(Return(&gst_element_pipeline)); + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_get_bus(GST_PIPELINE(&gst_element_pipeline))) + .WillOnce(Return(nullptr)); + + bool result = mInterfaceGstPlayer->CreatePipeline(pipelineName, pipelinePriority); + + EXPECT_FALSE(result); + EXPECT_EQ(mPlayerContext->pipeline, &gst_element_pipeline); + EXPECT_EQ(mPlayerContext->bus, nullptr); +} + +TEST_F(InterfacePlayerTests, CreatePipeline_Failure_PipelineNewFailed) +{ + const char* pipelineName = "testPipeline"; + int pipelinePriority = 1; + + EXPECT_CALL(*g_mockGStreamer, gst_pipeline_new(StrEq(pipelineName))) + .WillOnce(Return(nullptr)); + + bool result = mInterfaceGstPlayer->CreatePipeline(pipelineName, pipelinePriority); + + EXPECT_FALSE(result); + EXPECT_EQ(mPlayerContext->pipeline, nullptr); + EXPECT_EQ(mPlayerContext->bus, nullptr); +} + +TEST_F(InterfacePlayerTests, SetAudioVolume) +{ + // Test with volume 0 + mInterfaceGstPlayer->SetAudioVolume(0); + EXPECT_DOUBLE_EQ(mPlayerContext->audioVolume, 0.0); + mInterfaceGstPlayer->SetAudioVolume(150); + EXPECT_DOUBLE_EQ(mPlayerContext->audioVolume, 1.5); + mInterfaceGstPlayer->SetAudioVolume(-50); + EXPECT_DOUBLE_EQ(mPlayerContext->audioVolume, -0.5); //expected? +} + +TEST_F(InterfacePlayerTests, SetVolumeOrMuteUnMute_Null) +{ + mInterfaceGstPlayer->SetVolumeOrMuteUnMute(); +} + +TEST_F(InterfacePlayerTests, SetVideoZoomTest) +{ + GstElement gst_element_video_sink = {.object = {.name = (gchar *)"video_sink"}}; + mPlayerContext->video_sink = &gst_element_video_sink; + mPlayerContext->usingRialtoSink = false; + + EXPECT_CALL(*g_mockGLib, g_object_set(&gst_element_video_sink, StrEq("zoom-mode"), Matcher(2))).Times(AnyNumber()); + + mInterfaceGstPlayer->SetVideoZoom(2); + EXPECT_EQ(mPlayerContext->zoom, static_cast(2)); + + mPlayerContext->usingRialtoSink = true; + mInterfaceGstPlayer->SetVideoZoom(2); +} + +TEST_F(InterfacePlayerTests, SetVideoMute_ValidVideoSink) +{ + GstElement gst_element_video_sink = {.object = {.name = (gchar *)"video_sink"}}; + mPlayerContext->video_sink = &gst_element_video_sink; + EXPECT_CALL(*g_mockGLib, g_object_set(&gst_element_video_sink, StrEq("show-video-window"), Matcher(0))); + mInterfaceGstPlayer->SetVideoMute(true); + EXPECT_TRUE(mPlayerContext->videoMuted); +} + +TEST_F(InterfacePlayerTests, SetVideoMute_InvalidVideoSink) +{ + mPlayerContext->video_sink = nullptr; + EXPECT_CALL(*g_mockGLib, g_object_set(_, _,Matcher(_))).Times(0); + mInterfaceGstPlayer->SetVideoMute(true); + EXPECT_TRUE(mPlayerContext->videoMuted); +} + +TEST_F(InterfacePlayerTests, SetTextStyle_Null) +{ + mPlayerContext->subtitle_sink = nullptr; + + std::string options = "ffffff"; + EXPECT_FALSE(mInterfaceGstPlayer->SetTextStyle(options)); +} + +TEST_F(InterfacePlayerTests, NotifyEOS_TaskSchedulingFailed) +{ + // Initial setup + mPlayerContext->eosSignalled = false; + mPlayerContext->eosCallbackIdleTaskPending = false; + mPlayerContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + + // Mock the scheduler to return an invalid task ID + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)) + .WillOnce(Return(PLAYER_TASK_ID_INVALID)); + + // Call the method + mInterfaceGstPlayer->NotifyEOS(); + + // Verify the expected outcomes + EXPECT_TRUE(mPlayerContext->eosSignalled); + EXPECT_FALSE(mPlayerContext->eosCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId, PLAYER_TASK_ID_INVALID); +} + +TEST_F(InterfacePlayerTests, NotifyEOS_FirstCall) +{ + mPlayerContext->eosSignalled = false; + mPlayerContext->eosCallbackIdleTaskPending = false; + mPlayerContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + + // Mock the scheduler to return a valid task ID + EXPECT_CALL(*g_mockPlayerScheduler, ScheduleTask(_)) + .WillOnce(Return(1234)); + + mInterfaceGstPlayer->NotifyEOS(); + + EXPECT_TRUE(mPlayerContext->eosSignalled); + EXPECT_TRUE(mPlayerContext->eosCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId, 1234); +} + +TEST_F(InterfacePlayerTests, NotifyEOS_TaskAlreadyPending) +{ + mPlayerContext->eosSignalled = false; + mPlayerContext->eosCallbackIdleTaskPending = true; + mPlayerContext->eosCallbackIdleTaskId = 1234; + + mInterfaceGstPlayer->NotifyEOS(); + + EXPECT_FALSE(mPlayerContext->eosSignalled); + EXPECT_TRUE(mPlayerContext->eosCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId, 1234); +} + +TEST_F(InterfacePlayerTests, NotifyEOS_AlreadySignalled) +{ + mPlayerContext->eosSignalled = true; + mPlayerContext->eosCallbackIdleTaskPending = false; + mPlayerContext->eosCallbackIdleTaskId = PLAYER_TASK_ID_INVALID; + + mInterfaceGstPlayer->NotifyEOS(); + + EXPECT_TRUE(mPlayerContext->eosSignalled); + EXPECT_FALSE(mPlayerContext->eosCallbackIdleTaskPending); + EXPECT_EQ(mPlayerContext->eosCallbackIdleTaskId, PLAYER_TASK_ID_INVALID); +} + +TEST_F(InterfacePlayerTests, NotifyFragmentCachingComplete_PendingPlayStateTrue) +{ + mPlayerContext->pendingPlayState = true; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + + // Mock the SetStateWithWarnings function to return GST_STATE_CHANGE_SUCCESS + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PLAYING)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + + mInterfaceGstPlayer->NotifyFragmentCachingComplete(); + EXPECT_FALSE(mPlayerContext->pendingPlayState); +} + +TEST_F(InterfacePlayerTests, NotifyFragmentCachingComplete_SetStateFailure) +{ + mPlayerContext->pendingPlayState = true; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + + // Mock the SetStateWithWarnings function to return GST_STATE_CHANGE_FAILURE + EXPECT_CALL(*g_mockGStreamer, gst_element_set_state(&gst_element_pipeline, GST_STATE_PLAYING)) + .WillOnce(Return(GST_STATE_CHANGE_FAILURE)); + + mInterfaceGstPlayer->NotifyFragmentCachingComplete(); + EXPECT_FALSE(mPlayerContext->pendingPlayState); +} + +TEST_F(InterfacePlayerTests, NotifyFragmentCachingComplete_PendingPlayStateFalse) +{ + mPlayerContext->pendingPlayState = false; + mInterfaceGstPlayer->NotifyFragmentCachingComplete(); + EXPECT_FALSE(mPlayerContext->pendingPlayState); +} + +TEST_F(InterfacePlayerTests, EndOfStreamReached_FirstBufferProcessedFalse) +{ + bool shouldHaltBuffering = false; + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_VIDEO_ES_H264; + stream->firstBufferProcessed = false; + + mInterfaceGstPlayer->EndOfStreamReached(mediaType, shouldHaltBuffering); + + EXPECT_TRUE(stream->eosReached); + EXPECT_FALSE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, EndOfStreamReached_FirstBufferProcessedTrue_NormalPlayRate) +{ + bool shouldHaltBuffering = false; + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_VIDEO_ES_H264; + stream->firstBufferProcessed = true; + mPlayerContext->rate = GST_NORMAL_PLAY_RATE; + mPlayerContext->stream[eGST_MEDIATYPE_AUDIO].eosReached = true; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = reinterpret_cast(0x1234); + + mInterfaceGstPlayer->EndOfStreamReached(mediaType, shouldHaltBuffering); + + EXPECT_TRUE(stream->eosReached); + EXPECT_TRUE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, EndOfStreamReached_FirstBufferProcessedTrue_TrickMode) +{ + bool shouldHaltBuffering = false; + int mediaType = eGST_MEDIATYPE_VIDEO; + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->format = GST_FORMAT_VIDEO_ES_H264; + stream->firstBufferProcessed = true; + mPlayerContext->rate = 2.0; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = reinterpret_cast(0x1234); + + mInterfaceGstPlayer->EndOfStreamReached(mediaType, shouldHaltBuffering); + + EXPECT_TRUE(stream->eosReached); + EXPECT_TRUE(shouldHaltBuffering); +} + +TEST_F(InterfacePlayerTests, InterfacePlayer_SetupStream_Success) //failure case todo +{ + GstMediaType streamId = eGST_MEDIATYPE_VIDEO; + std::string manifestUrl = "http://example.com/manifest.mpd"; + int retvalue = mInterfaceGstPlayer->InterfacePlayer_SetupStream(streamId, manifestUrl); + + EXPECT_EQ(retvalue, 0); +} + +TEST_F(InterfacePlayerTests, DisableDecoderHandleNotified) +{ + mPlayerContext->decoderHandleNotified = true; + mInterfaceGstPlayer->DisableDecoderHandleNotified(); + EXPECT_FALSE(mPlayerContext->decoderHandleNotified); +} + +TEST_F(InterfacePlayerTests, SignalSubtitleClock_PadPushFail) +{ + gint64 vPTS = 90000; // 1 second in 90KHz clock + bool state = false; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + GstElement gst_element_source = {.object = {.name = (gchar *)"source"}}; + GstPad gst_pad_src = {.object = {.name = (gchar *)"src"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].format = GST_FORMAT_SUBTITLE_MP4; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = &gst_element_source; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PLAYING), Return(GST_STATE_CHANGE_SUCCESS))); + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(&gst_element_source, StrEq("src"))) + .WillOnce(Return(&gst_pad_src)); + EXPECT_CALL(*g_mockGLib, g_type_check_instance_is_a(_,_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockGStreamer, gst_structure_new()) + .WillOnce(Return(reinterpret_cast(0x1234))); + EXPECT_CALL(*g_mockGStreamer, gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM, reinterpret_cast(0x1234))) + .WillOnce(Return(reinterpret_cast(0x5678))); + EXPECT_CALL(*g_mockGStreamer, gst_pad_push_event(&gst_pad_src, reinterpret_cast(0x5678))) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*g_mockGStreamer, gst_object_unref(&gst_pad_src)); + + bool result = mInterfaceGstPlayer->SignalSubtitleClock(vPTS, state); + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, SignalSubtitleClock_NonPlayingState) +{ + gint64 vPTS = 90000; // 1 second in 90KHz clock + bool state = false; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + GstElement gst_element_source = {.object = {.name = (gchar *)"source"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].format = GST_FORMAT_SUBTITLE_MP4; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = &gst_element_source; + + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PAUSED), Return(GST_STATE_CHANGE_SUCCESS))); + EXPECT_CALL(*g_mockGLib, g_type_check_instance_is_a(_,_)) + .WillRepeatedly(Return(true)); + + bool result = mInterfaceGstPlayer->SignalSubtitleClock(vPTS, state); + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, SignalSubtitleClock_Null) +{ + gint64 vPTS = 90000; // 1 second in 90KHz clock + bool state = false; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].format = GST_FORMAT_SUBTITLE_MP4; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = nullptr; + + //subtitle appsrc is NULL + bool result = mInterfaceGstPlayer->SignalSubtitleClock(vPTS, state); + EXPECT_FALSE(result); + GstElement gst_element_source = {.object = {.name = (gchar *)"source"}}; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = &gst_element_source; + result = mInterfaceGstPlayer->SignalSubtitleClock(vPTS, state); + EXPECT_FALSE(result); + + //subtitle appsrc is invalid + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].format = GST_FORMAT_SUBTITLE_MP4; + mPlayerContext->stream[eGST_MEDIATYPE_SUBTITLE].source = &gst_element_source; + + //sourceEleSrcPad is NULL + EXPECT_CALL(*g_mockGStreamer, gst_element_get_state(&gst_element_pipeline, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(GST_STATE_PLAYING), Return(GST_STATE_CHANGE_SUCCESS))); + EXPECT_CALL(*g_mockGLib, g_type_check_instance_is_a(_,_)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockGStreamer, gst_element_get_static_pad(&gst_element_source, StrEq("src"))) + .WillOnce(Return(nullptr)); + result = mInterfaceGstPlayer->SignalSubtitleClock(vPTS, state); + EXPECT_FALSE(result); + +} + +TEST_F(InterfacePlayerTests, FlushTrack_VideoType) +{ + int mediaType = eGST_MEDIATYPE_VIDEO; + double pos = 10.0; + double audioDelta = 0.0; + double subDelta = 3.0; + GstElement gst_element_source = {.object = {.name = (gchar *)"source"}}; + GstElement gst_element_pipeline = {.object = {.name = (gchar *)"pipeline"}}; + mPlayerContext->pipeline = &gst_element_pipeline; + mPlayerContext->stream[eGST_MEDIATYPE_VIDEO].source = &gst_element_source; + + EXPECT_CALL(*g_mockGStreamer, gst_element_seek_simple(&gst_element_source, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, (pos + subDelta) * GST_SECOND)) + .WillOnce(Return(TRUE)); + + double rate = mInterfaceGstPlayer->FlushTrack(mediaType, pos, audioDelta, subDelta); + + EXPECT_EQ(rate, GST_NORMAL_PLAY_RATE); + EXPECT_FALSE(mPlayerContext->filterAudioDemuxBuffers); +} + +extern bool GstPlayer_isVideoOrAudioDecoder(const char* name, InterfacePlayerRDK * _this); +TEST_F(InterfacePlayerTests, GstPlayer_isVideoOrAudioDecoder_RialtoSink) +{ + mPlayerContext->usingRialtoSink = true; + EXPECT_TRUE( GstPlayer_isVideoOrAudioDecoder("rialtomsevideosink", mInterfaceGstPlayer) ); + EXPECT_TRUE( GstPlayer_isVideoOrAudioDecoder("rialtomseaudiosink", mInterfaceGstPlayer) ); + EXPECT_FALSE( GstPlayer_isVideoOrAudioDecoder("rialtomsesubtitlesink", mInterfaceGstPlayer) ); +} + +TEST_F(InterfacePlayerTests, GstPlayer_isVideoOrAudioDecoder_NotDecoder) +{ + const char* name = "notadecoder"; + bool result = GstPlayer_isVideoOrAudioDecoder(name, mInterfaceGstPlayer); + EXPECT_FALSE(result); +} + +TEST_F(InterfacePlayerTests, SetVolumeOrMuteUnMute_UsingRialtoSink) +{ + mPlayerContext->usingRialtoSink = true; + GstElement gst_element_audio_sink = {.object = {.name = (gchar *)"audio_sink"}}; + mPlayerContext->audio_sink = &gst_element_audio_sink; + mPlayerContext->audioVolume = 0.5; + mPlayerContext->audioMuted = false; + + EXPECT_CALL(*g_mockGLib, g_object_set(&gst_element_audio_sink, StrEq("volume"), Matcher(0.5))); + + mInterfaceGstPlayer->SetVolumeOrMuteUnMute(); +} diff --git a/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerTests.cpp b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerTests.cpp new file mode 100644 index 000000000..890188ad1 --- /dev/null +++ b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerTests.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/OCDMSessionAdapter/CMakeLists.txt b/middleware/test/utests/tests/OCDMSessionAdapter/CMakeLists.txt new file mode 100644 index 000000000..1bef05911 --- /dev/null +++ b/middleware/test/utests/tests/OCDMSessionAdapter/CMakeLists.txt @@ -0,0 +1,85 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME OCDMSessionAdapterTests) + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/drm ${PLAYER_ROOT}/drm/helper ${PLAYER_ROOT} ${PLAYER_ROOT}/middleware/drm/ocdm ${PLAYER_ROOT}/drm/ ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/downloader ${PLAYER_ROOT}/isobmff ${PLAYER_ROOT}/subtec/subtecparser ${PLAYER_ROOT}/subtec/playerJsonObject ${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories("fakes/.") + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + include_directories(${PLAYER_ROOT}/Linux/include) +endif(CMAKE_SYSTEM_NAME STREQUAL Linux) + + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${UTESTS_ROOT}/ocdm) +include_directories(${PLAYER_ROOT}/drm) +include_directories(${PLAYER_ROOT}/drm/ocdm) +include_directories(${PLAYER_ROOT}/drm/helper) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/baseConversion) +include_directories(${PLAYER_ROOT}/externals/contentsecuritymanager) + +set(TEST_SOURCES FunctionalTests.cpp + OCDMSessionAdapterTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/drm/ocdm/opencdmsessionadapter.cpp + ${PLAYER_ROOT}/drm/helper/DrmHelper.cpp + ${PLAYER_ROOT}/drm/DrmSession.cpp + ${PLAYER_ROOT}/drm/DrmUtils.cpp + ${PLAYER_ROOT}/externals/PlayerExternalsInterface.cpp + ${PLAYER_ROOT}/externals/PlayerExternalUtils.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/PlayerUtils.cpp + ${PLAYER_ROOT}/ProcessHandler.cpp + ${PLAYER_ROOT}/baseConversion/_base64.cpp) + +add_definitions(-DUSE_THUNDER_OCDM_API_0_2) +add_definitions(-DUSE_OPENCDM_ADAPTER) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() + #include("${PROJECT_SOURCE_DIR}/cmake_exclude_file.list") + SETUP_TARGET_FOR_COVERAGE_LCOV(NAME ${EXEC_NAME}_coverage + EXECUTABLE ${EXEC_NAME} + DEPENDENCIES ${EXEC_NAME}) +endif() + +target_link_libraries(${EXEC_NAME} fakes -lpthread ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/OCDMSessionAdapter/FunctionalTests.cpp b/middleware/test/utests/tests/OCDMSessionAdapter/FunctionalTests.cpp new file mode 100644 index 000000000..e3805f603 --- /dev/null +++ b/middleware/test/utests/tests/OCDMSessionAdapter/FunctionalTests.cpp @@ -0,0 +1,87 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "opencdmsessionadapter.h" +#include "MockDrmHelper.h" +#include "MockOpenCdm.h" +#include +#include "_base64.h" + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::_; + +std::shared_ptr drmHelper; + +// For comparing memory buffers such as C-style arrays +MATCHER_P2(MemBufEq, buffer, elementCount, "") +{ + return std::memcmp(arg, buffer, elementCount * sizeof(buffer[0])) == 0; +} + +class OCDMSessionAdapterTests : public ::testing::Test +{ + +protected: + OCDMSessionAdapter *m_ocdmsessionadapter = nullptr; + struct OpenCDMSystem *ocdmSystem = (OpenCDMSystem *)0x1234; + std::string systemId = "com.microsoft.playready"; + + void SetUp() override + { + drmHelper = std::make_shared(); + g_mockopencdm = new NiceMock(); + + EXPECT_CALL(*drmHelper, ocdmSystemId()).WillOnce(ReturnRef(systemId)); + EXPECT_CALL(*g_mockopencdm, opencdm_create_system(MemBufEq(systemId.c_str(), systemId.length() + 1))).WillOnce(Return(ocdmSystem)); + + m_ocdmsessionadapter = new OCDMSessionAdapter(drmHelper, nullptr); + } + + void TearDown() override + { + delete m_ocdmsessionadapter; + m_ocdmsessionadapter = nullptr; + + delete g_mockopencdm; + g_mockopencdm = nullptr; + + drmHelper = nullptr; + } + +public: +}; + +TEST_F(OCDMSessionAdapterTests, generateDRMSession) +{ + uint8_t initData[] = {0, 1, 2, 3}; + uint8_t initDataLen = sizeof(initData); + uint32_t f_cbInitData = 99; + std::string customData = "Custom Data"; + const char *initDataType = "cenc"; + uint8_t initDataTypeLen = strlen(initDataType); + + ((*g_mockopencdm).gmock_opencdm_construct_session(ocdmSystem, LicenseType::Temporary, MemBufEq(initDataType, initDataTypeLen), MemBufEq(initData, initDataLen), f_cbInitData, MemBufEq(customData.c_str(), customData.length()), customData.length(),_,_,_))(::testing::internal::GetWithoutMatchers(), nullptr) .InternalExpectedAt("/home/rekha/RDK/latest/l1_Final/aamp/middleware/test/utests/tests/OCDMSessionAdapter/FunctionalTests.cpp", 91, "*g_mockopencdm", "opencdm_construct_session(ocdmSystem, LicenseType::Temporary, MemBufEq(initDataType, initDataTypeLen), MemBufEq(initData, initDataLen), f_cbInitData, MemBufEq(customData.c_str(), customData.length()), customData.length(),_,_,_)").WillOnce(Return(ERROR_NONE)); + + m_ocdmsessionadapter->generateDRMSession(initData, f_cbInitData, customData); +} diff --git a/middleware/test/utests/tests/OCDMSessionAdapter/OCDMSessionAdapterTests.cpp b/middleware/test/utests/tests/OCDMSessionAdapter/OCDMSessionAdapterTests.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/OCDMSessionAdapter/OCDMSessionAdapterTests.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/OCDMSessionAdapter/fakes/config.h b/middleware/test/utests/tests/OCDMSessionAdapter/fakes/config.h new file mode 100644 index 000000000..bdb47a66e --- /dev/null +++ b/middleware/test/utests/tests/OCDMSessionAdapter/fakes/config.h @@ -0,0 +1 @@ +// Empty header file to satisfy config.h inclusion from AAMP diff --git a/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/CMakeLists.txt b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/CMakeLists.txt new file mode 100644 index 000000000..413bce463 --- /dev/null +++ b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/CMakeLists.txt @@ -0,0 +1,86 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2024 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME OcdmBasicSessionAdapterTests) + +include_directories(${PLAYER_ROOT}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + include_directories(${PLAYER_ROOT}/Linux/include) +endif(CMAKE_SYSTEM_NAME STREQUAL Linux) + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/drm) +include_directories(${UTESTS_ROOT}/ocdm) +include_directories(${UTESTS_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/externals) +include_directories(${PLAYER_ROOT}/drm/ocdm) +include_directories(${PLAYER_ROOT}/drm/helper) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/baseConversion) +include_directories(${PLAYER_ROOT}/externals/contentsecuritymanager) + +set(TEST_SOURCES FunctionalTests.cpp + OcdmBasicSessionAdapterTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/drm/ocdm/OcdmBasicSessionAdapter.cpp + ${PLAYER_ROOT}/drm/helper/DrmHelper.cpp + ${PLAYER_ROOT}/drm/DrmSession.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/ProcessHandler.cpp + ${PLAYER_ROOT}/PlayerUtils.cpp + ${PLAYER_ROOT}/baseConversion/_base64.cpp) + +#[[class OCDMBasicSessionAdapter inherits from class OCDMSessionAdapter, +which inherits from the abstract class DrmSession. +Since the implementation of DrmSession is fairly simple, the real code is compiled. +However, OCDMSessionAdapter has a more complex implementation, so a fake has been written for this class.]] + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + + + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() + #include("${PROJECT_SOURCE_DIR}/cmake_exclude_file.list") + SETUP_TARGET_FOR_COVERAGE_LCOV(NAME ${EXEC_NAME}_coverage + EXECUTABLE ${EXEC_NAME} + DEPENDENCIES ${EXEC_NAME}) +endif() + +target_link_libraries(${EXEC_NAME} fakes -lpthread ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES} ${LIBCJSON_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/FunctionalTests.cpp b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/FunctionalTests.cpp new file mode 100644 index 000000000..9108eb22a --- /dev/null +++ b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/FunctionalTests.cpp @@ -0,0 +1,243 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "OcdmBasicSessionAdapter.h" +#include "MockOpenCdmSessionAdapter.h" +#include "MockDrmHelper.h" +#include "MockOpenCdm.h" +#include "MockDrmMemorySystem.h" +#include + + +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::_; +using ::testing::SetArgReferee; +using ::testing::DoAll; + + +// For comparing memory buffers such as C-style arrays +MATCHER_P2(MemBufEq, buffer, elementCount, "") +{ + return std::memcmp(arg, buffer, elementCount * sizeof(buffer[0])) == 0; +} + +std::shared_ptr drmHelper; +DrmInfo drminfo; +MockDrmMemorySystem *g_mockMemorySystem; + +class OcdmBasicSessionAdapterTests : public ::testing::Test +{ + +protected: + OCDMBasicSessionAdapter *m_ocdmbasicsessionadapter = nullptr; + + void SetUp() override + { + drmHelper = std::make_shared(); + g_mockopencdm = new NiceMock(); + m_ocdmbasicsessionadapter = new OCDMBasicSessionAdapter(drmHelper,nullptr); + g_mockOpenCdmSessionAdapter = new NiceMock(); + g_mockMemorySystem = new NiceMock(); + } + + void TearDown() override + { + delete m_ocdmbasicsessionadapter; + m_ocdmbasicsessionadapter = nullptr; + + delete g_mockOpenCdmSessionAdapter; + g_mockOpenCdmSessionAdapter = nullptr; + + delete g_mockopencdm; + g_mockopencdm = nullptr; + + drmHelper = nullptr; + + delete g_mockMemorySystem; + g_mockMemorySystem = nullptr; + } + +public: +}; + +TEST_F(OcdmBasicSessionAdapterTests, DecryptHDCPComplianceFailure) +{ + int ret_value; + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(false)); + EXPECT_CALL(*g_mockopencdm, opencdm_session_decrypt(_,_,_,_,_,_,_,_,_,_)).Times(0); + + ret_value = m_ocdmbasicsessionadapter->decrypt(nullptr, 0x0, nullptr, + 0, nullptr); + + EXPECT_EQ(ret_value,HDCP_COMPLIANCE_CHECK_FAILURE); +} + +TEST_F(OcdmBasicSessionAdapterTests, DecryptWithNullMemorySystem) +{ + int ret_value; + const uint8_t *f_pbIV = nullptr; + uint32_t f_cbIV = 0; + const uint8_t payloadData[] = {1,2,3}; + uint32_t payloadDataSize = sizeof(payloadData); + EncryptionScheme encScheme = AesCtr_Cenc; + uint32_t initWithLast15 = 0; + + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(true)); + EXPECT_CALL(*drmHelper, getMemorySystem()).WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*g_mockopencdm, + opencdm_session_decrypt(_, + MemBufEq(payloadData, payloadDataSize), + payloadDataSize, + encScheme, + _, + f_pbIV, + f_cbIV, + MemBufEq(g_mockKeyId.data(), g_mockKeyId.size()), + g_mockKeyId.size(), + initWithLast15)).WillOnce(Return(ERROR_NONE)); + ret_value = m_ocdmbasicsessionadapter->decrypt(f_pbIV, f_cbIV, payloadData, + payloadDataSize, nullptr); + + EXPECT_EQ(ret_value,0); +} + +TEST_F(OcdmBasicSessionAdapterTests, DecryptWithValidMemorySystem) +{ + int ret_value; + const uint8_t f_pbIV[] = {12}; + uint32_t f_cbIV = 2; + const uint8_t payloadData[] = {1,2,3}; + uint32_t payloadDataSize = sizeof(payloadData); + EncryptionScheme encScheme = AesCtr_Cenc; + uint32_t initWithLast15 = 0; + std::vector vdata {250, 3}; + uint32_t sizeToSend = vdata.size(); + uint8_t *dataToSend = vdata.data(); + + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(true)); + EXPECT_CALL(*drmHelper, getMemorySystem()).WillRepeatedly(Return(g_mockMemorySystem)); + EXPECT_CALL(*g_mockMemorySystem, encode(payloadData,payloadDataSize,_)).WillOnce(DoAll(SetArgReferee<2>(vdata), Return(true))); + EXPECT_CALL(*g_mockopencdm, + opencdm_session_decrypt(_, + MemBufEq(dataToSend, sizeToSend), + sizeToSend, + encScheme, + _, + f_pbIV, + f_cbIV, + MemBufEq(g_mockKeyId.data(), g_mockKeyId.size()), + g_mockKeyId.size(), + initWithLast15)).WillOnce(Return(ERROR_NONE)); + EXPECT_CALL(*g_mockMemorySystem, decode(MemBufEq(dataToSend, sizeToSend), sizeToSend,const_cast(payloadData), payloadDataSize)).WillOnce(Return(true)); + ret_value = m_ocdmbasicsessionadapter->decrypt(f_pbIV, f_cbIV, payloadData, + payloadDataSize, nullptr); + EXPECT_EQ(ret_value,0); +} + +TEST_F(OcdmBasicSessionAdapterTests, DecryptWithValidMemorySystemEncodeFail) +{ + int ret_value; + const uint8_t f_pbIV[] = {12}; + uint32_t f_cbIV = 2; + const uint8_t payloadData[] = {1,2,3}; + uint32_t payloadDataSize = sizeof(payloadData); + EncryptionScheme encScheme = AesCtr_Cenc; + uint32_t initWithLast15 = 0; + std::vector vdata {250, 3}; + uint32_t sizeToSend = vdata.size(); + uint8_t *dataToSend = vdata.data(); + + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(true)); + EXPECT_CALL(*drmHelper, getMemorySystem()).WillRepeatedly(Return(g_mockMemorySystem)); + EXPECT_CALL(*g_mockMemorySystem, encode(payloadData,payloadDataSize,_)).WillOnce(DoAll(SetArgReferee<2>(vdata), Return(false))); + EXPECT_CALL(*g_mockopencdm, opencdm_session_decrypt(_,_,_,_,_,_,_,_,_,_)).Times(0); + EXPECT_CALL(*g_mockMemorySystem, decode(_,_,_,_)).Times(0); + ret_value = m_ocdmbasicsessionadapter->decrypt(f_pbIV, f_cbIV, payloadData, + payloadDataSize, nullptr); + EXPECT_EQ(ret_value,-1); +} + +TEST_F(OcdmBasicSessionAdapterTests, DecryptWithValidMemorySystemDecodeFail) +{ + int ret_value; + const uint8_t f_pbIV[] = {12}; + uint32_t f_cbIV = 2; + const uint8_t payloadData[] = {1,2,3}; + uint32_t payloadDataSize = sizeof(payloadData); + EncryptionScheme encScheme = AesCtr_Cenc; + uint32_t initWithLast15 = 0; + std::vector vdata {250, 3}; + uint32_t sizeToSend = vdata.size(); + uint8_t *dataToSend = vdata.data(); + + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(true)); + EXPECT_CALL(*drmHelper, getMemorySystem()).WillRepeatedly(Return(g_mockMemorySystem)); + EXPECT_CALL(*g_mockMemorySystem, encode(payloadData,payloadDataSize,_)).WillOnce(DoAll(SetArgReferee<2>(vdata), Return(true))); + EXPECT_CALL(*g_mockopencdm, + opencdm_session_decrypt(_, + MemBufEq(dataToSend, sizeToSend), + sizeToSend, + encScheme, + _, + f_pbIV, + f_cbIV, + MemBufEq(g_mockKeyId.data(), g_mockKeyId.size()), + g_mockKeyId.size(), + initWithLast15)).WillOnce(Return(ERROR_NONE)); + EXPECT_CALL(*g_mockMemorySystem, decode(MemBufEq(dataToSend, sizeToSend), sizeToSend,const_cast(payloadData), payloadDataSize)).WillOnce(Return(false)); + ret_value = m_ocdmbasicsessionadapter->decrypt(f_pbIV, f_cbIV, payloadData, payloadDataSize, nullptr); + + EXPECT_EQ(ret_value,-1); +} + +TEST_F(OcdmBasicSessionAdapterTests, DecryptFail) +{ + int ret_value; + const uint8_t f_pbIV[] = {12}; + uint32_t f_cbIV = 2; + const uint8_t payloadData[] = {1,2,3}; + uint32_t payloadDataSize = sizeof(payloadData); + EncryptionScheme encScheme = AesCtr_Cenc; + uint32_t initWithLast15 = 0; + std::vector vdata {250, 3}; + uint32_t sizeToSend = vdata.size(); + uint8_t *dataToSend = vdata.data(); + + EXPECT_CALL(*g_mockOpenCdmSessionAdapter, verifyOutputProtection()).WillOnce(Return(true)); + EXPECT_CALL(*drmHelper, getMemorySystem()).WillRepeatedly(Return(g_mockMemorySystem)); + EXPECT_CALL(*g_mockMemorySystem, encode(payloadData,payloadDataSize,_)).WillOnce(DoAll(SetArgReferee<2>(vdata), Return(true))); + EXPECT_CALL(*g_mockopencdm, + opencdm_session_decrypt(_, + MemBufEq(dataToSend, sizeToSend), + sizeToSend, + encScheme, + _, + f_pbIV, + f_cbIV, + MemBufEq(g_mockKeyId.data(), g_mockKeyId.size()), + g_mockKeyId.size(), + initWithLast15)).WillOnce(Return(ERROR_UNKNOWN)); + EXPECT_CALL(*g_mockMemorySystem, terminateEarly()); + ret_value = m_ocdmbasicsessionadapter->decrypt(f_pbIV, f_cbIV, payloadData, payloadDataSize, nullptr); + EXPECT_EQ(ret_value,ERROR_UNKNOWN); +} diff --git a/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/OcdmBasicSessionAdapterTests.cpp b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/OcdmBasicSessionAdapterTests.cpp new file mode 100644 index 000000000..2034179e9 --- /dev/null +++ b/middleware/test/utests/tests/OcdmBasicSessionAdapterTests/OcdmBasicSessionAdapterTests.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2024 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/PluginsTests/CMakeLists.txt b/middleware/test/utests/tests/PluginsTests/CMakeLists.txt new file mode 100644 index 000000000..f53ce7ae3 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/CMakeLists.txt @@ -0,0 +1,100 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2025 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +pkg_check_modules(UUID REQUIRED uuid) +pkg_check_modules(GOBJECT REQUIRED gobject-2.0) + +set(PLAYER_ROOT "../../../..") +set(UTESTS_ROOT "../..") +set(DRM_ROOT ${UTESTS_ROOT}/drm) +set(EXEC_NAME PluginsTests) +set(SEC_CLIENT_ROOT ${PLAYER_ROOT}/../secclient) + +file(GLOB SEC_CLIENT_HEADERS "${SEC_CLIENT_ROOT}/*") + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/gst-plugins/drm/gst) +include_directories(${PLAYER_ROOT}/drm) +include_directories(${PLAYER_ROOT}/drm/helper) +include_directories(${PLAYER_ROOT}/subtitle) +include_directories(${PLAYER_ROOT}/closedcaptions) +include_directories(${PLAYER_ROOT}/downloader) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories(${PLAYER_ROOT}) +include_directories(${PLAYER_ROOT}/externals/contentsecuritymanager) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${PLAYER_ROOT}/vendor) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${DRM_ROOT}/ocdm) +include_directories(${UTESTS_ROOT}/mocks) +include_directories(${UTESTS_ROOT}/drm/mocks) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${GSTREAMERBASE_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${UUID_INCLUDE_DIRS}) + +if (JSC_INCDIR) + include_directories(${JSC_INCDIR}) +endif() +message(GSTREAMER_INCLUDE_DIRS=${GSTREAMER_INCLUDE_DIRS}) + +set(FAKE_SOURCE ${UTEST_ROOT}/fakes/FakeDRMSessionManager.cpp) + +set(TEST_SOURCES PlayerClearKeyDecryptorTests.cpp + PlayerPlayReadyDecryptorTests.cpp + PlayerVeriMatrixDecryptorTests.cpp + PlayerWidevineDecryptorTests.cpp + PluginsRun.cpp) + +add_definitions(-DUSE_OPENCDM_ADAPTER) + +if (CMAKE_PLATFORM_UBUNTU) + add_definitions(-DUBUNTU) +endif() + +set(PLAYER_SOURCES + ${PLAYER_ROOT}/gst-plugins/drm/gst/gstcdmidecryptor.cpp + ${PLAYER_ROOT}/gst-plugins/drm/gst/gstclearkeydecryptor.cpp + ${PLAYER_ROOT}/gst-plugins/drm/gst/gstplayreadydecryptor.cpp + ${PLAYER_ROOT}/gst-plugins/drm/gst/gstverimatrixdecryptor.cpp + ${PLAYER_ROOT}/gst-plugins/drm/gst/gstwidevinedecryptor.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${MOCK_SOURCES} + ${FAKE_SOURCES} + ${PLAYER_SOURCES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes ${UUID_LINK_LIBRARIES} ${OS_LD_FLAGS} -lpthread ${GLIB_LINK_LIBRARIES} ${LIBCJSON_LINK_LIBRARIES} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES} ${GOBJECT_LINK_LIBRARIES} ${GSTREAMERBASE_LINK_LIBRARIES} ${GSTREAMER_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/PluginsTests/PlayerClearKeyDecryptorTests.cpp b/middleware/test/utests/tests/PluginsTests/PlayerClearKeyDecryptorTests.cpp new file mode 100644 index 000000000..06aa28f44 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/PlayerClearKeyDecryptorTests.cpp @@ -0,0 +1,47 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gstclearkeydecryptor.h" +#include +#include +#include + +class PluginsTests : public ::testing::Test +{ +protected: + //GstAampclearkeydecryptor * aampclearkeydecryptor; + + void SetUp() override + { + //aampclearkeydecryptor = new GstAampclearkeydecryptor(); + } + + void TearDown() override + { + //delete aampclearkeydecryptor; + } +public: +}; + +TEST_F(PluginsTests, TestClearKeyDecryptor_Init) +{ + //GstAampclearkeydecryptor *aampclearkeydecryptor = NULL; + //gst_aampclearkeydecryptor_init(aampclearkeydecryptor); +} + diff --git a/middleware/test/utests/tests/PluginsTests/PlayerPlayReadyDecryptorTests.cpp b/middleware/test/utests/tests/PluginsTests/PlayerPlayReadyDecryptorTests.cpp new file mode 100644 index 000000000..596ebb2b5 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/PlayerPlayReadyDecryptorTests.cpp @@ -0,0 +1,44 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "gstplayreadydecryptor.h" +#include +#include + +class PluginsTests : public ::testing::Test +{ +protected: + //GstAampplayreadydecryptor *aampplayreadydecryptor; + + void SetUp() override + { + //aampplayreadydecryptor = new GstAampplayreadydecryptor(); + } + + void TearDown() override + { + //delete aampplayreadydecryptor; + } +public: +}; + +TEST_F(PluginsTests, TestPlayReadyDecryptor_Init) +{ + //GstAampplayreadydecryptor *aampplayreadydecryptor = NULL; + //gst_aampplayreadydecryptor_init(aampplayreadydecryptor); +} diff --git a/middleware/test/utests/tests/PluginsTests/PlayerVeriMatrixDecryptorTests.cpp b/middleware/test/utests/tests/PluginsTests/PlayerVeriMatrixDecryptorTests.cpp new file mode 100644 index 000000000..ea2c281d5 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/PlayerVeriMatrixDecryptorTests.cpp @@ -0,0 +1,44 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "gstverimatrixdecryptor.h" +#include +#include + +class PluginsTests : public ::testing::Test +{ +protected: + //GstAampverimatrixdecryptor *aampverimatrixdecryptor; + + void SetUp() override + { + //aampverimatrixdecryptor = new GstAampverimatrixdecryptor(); + } + + void TearDown() override + { + //delete aampverimatrixdecryptor; + } +public: +}; + +TEST_F(PluginsTests, TestVeriMatrixDecryptor_Init) +{ + //GstAampverimatrixdecryptor *aampverimatrixdecryptor = NULL; + //gst_aampverimatrixdecryptor_init(aampverimatrixdecryptor); +} diff --git a/middleware/test/utests/tests/PluginsTests/PlayerWidevineDecryptorTests.cpp b/middleware/test/utests/tests/PluginsTests/PlayerWidevineDecryptorTests.cpp new file mode 100644 index 000000000..fecc5f9a6 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/PlayerWidevineDecryptorTests.cpp @@ -0,0 +1,45 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gstwidevinedecryptor.h" + +#include +#include + +class PluginsTests : public ::testing::Test +{ +protected: + //GstAampwidevinedecryptor *aampwidevinedecryptor; + void SetUp() override + { + //aampwidevinedecryptor = new GstAampwidevinedecryptor(); + } + + void TearDown() override + { + //delete aampwidevinedecryptor; + } +public: +}; + +TEST_F(PluginsTests, TestWidevineDecryptor_Init) +{ + //GstAampwidevinedecryptor *aampwidevinedecryptor = NULL; + //gst_aampwidevinedecryptor_init(aampwidevinedecryptor); +} diff --git a/middleware/test/utests/tests/PluginsTests/PluginsRun.cpp b/middleware/test/utests/tests/PluginsTests/PluginsRun.cpp new file mode 100644 index 000000000..890188ad1 --- /dev/null +++ b/middleware/test/utests/tests/PluginsTests/PluginsRun.cpp @@ -0,0 +1,26 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/TextStyleAttributes/CMakeLists.txt b/middleware/test/utests/tests/TextStyleAttributes/CMakeLists.txt new file mode 100644 index 000000000..5605b2d99 --- /dev/null +++ b/middleware/test/utests/tests/TextStyleAttributes/CMakeLists.txt @@ -0,0 +1,53 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2022 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME TextStyleAttributes) + +include_directories(${PLAYER_ROOT}) +include_directories(${PLAYER_ROOT}/subtitle) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/playerJsonObject) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${LIBCJSON_INCLUDE_DIRS}) + + +set(TEST_SOURCES GetTextStyleAttributesTests.cpp + TextStyleAttributesTests.cpp) + +set(PLAYER_SOURCES ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp + ${PLAYER_ROOT}/subtec/subtecparser/TextStyleAttributes.cpp) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") +if (CMAKE_XCODE_BUILD_SYSTEM) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES} -lpthread) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/TextStyleAttributes/GetTextStyleAttributesTests.cpp b/middleware/test/utests/tests/TextStyleAttributes/GetTextStyleAttributesTests.cpp new file mode 100644 index 000000000..9bac7ed96 --- /dev/null +++ b/middleware/test/utests/tests/TextStyleAttributes/GetTextStyleAttributesTests.cpp @@ -0,0 +1,2838 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2024 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include "PlayerLogManager.h" +#include "MockPlayerJsonObject.h" +#include "TextStyleAttributes.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::SetArgReferee; +using ::testing::StrictMock; +using ::testing::An; +using ::testing::DoAll; + +class GetTextStyleAttributesTests : public ::testing::Test +{ +protected: + + std::unique_ptr mAttributes{}; + + void SetUp() override + { + mAttributes = std::unique_ptr(new TextStyleAttributes()); + + g_mockPlayerJsonObject = std::make_shared>(); + } + + void TearDown() override + { + mAttributes = nullptr; + + g_mockPlayerJsonObject = nullptr; + } +}; + +ACTION(ThrowJsonException) +{ + throw PlayerJsonParseException(); +} + +/* + Test the getAttributes function supplying it with empty Json string + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, EmptyJsonOptionsString) +{ + std::string options{}; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_EQ(-1, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes function when PlayerJsonObject throws exception + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, JsonExceptionThrown) +{ + std::string options = "{\"fontSize\":\"32.4px\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(ThrowJsonException()); + EXPECT_EQ(-1, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes function when PlayerJsonObject unsuccessfully retrieves value + A wrong key in the Json object (as set in options) is used to test the function. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, JsonValueNotReturned) +{ + std::string options = "{\"fontSize\":\"32.4px\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeRightKeyInvalidValueJsonOptionsString) +{ + std::string penSizeValue = "32.4px"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with font size small, penSizevalue expressed in lower case + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueSmallLowerCase) +{ + std::string penSizeValue = "small"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_SMALL); +} + +/* + Test the getAttributes with font size small, penSizevalue expressed in Upper case + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueSmallUpperCase) +{ + std::string penSizeValue = "SMALL"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_SMALL); +} + +/* + Test the getAttributes with font size Medium, penSizevalue expressed in lower case + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueMediumLowerCase) +{ + std::string penSizeValue = "medium"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_STANDARD); +} + +/* + Test the getAttributes with font size Medium, penSizevalue expressed in Upper case + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue + Two test cases are sufficient to prove that Upper case Json values are handled appropriately +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueMediumUpperCase) +{ + std::string penSizeValue = "MEDIUM"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_STANDARD); +} + +/* + Test the getAttributes with font size Standard + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueStandard) +{ + std::string penSizeValue = "standard"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_STANDARD); +} + +/* + Test the getAttributes with font size large + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueLarge) +{ + std::string penSizeValue = "large"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_LARGE); +} + +/* + Test the getAttributes with font size FONT_SIZE_EXTRALARGE + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueExtralarge) +{ + std::string penSizeValue = "extra_large"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_EXTRALARGE); +} + +/* + Test the getAttributes with font size auto i.e. embedded + This will also test the output expected from the getFontSize function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontSizeExpectedJsonOptionsStringValueAuto) +{ + std::string penSizeValue = "auto"; + std::string options = "{\"penSize\":\"" + penSizeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())) + .WillOnce(DoAll(SetArgReferee<1>(penSizeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_SIZE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_SIZE_ARR_POSITION], mAttributes->FONT_SIZE_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleRightKeyInvalidValueJsonOptionsString) +{ + std::string fontStyleValue = "italics"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with font size monospaced serif, fontstyle value expressed in lower case + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueMonospacedserifLowerCase) +{ + std::string fontStyleValue = "monospaced_serif"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_MONOSPACED_SERIF); +} + +/* + Test the getAttributes with font size monospaced serif, fontstyle value expressed in lower case, separated by space + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueMonospacedserifLowerCaseSpaceSeparated) +{ + std::string fontStyleValue = "monospaced serif"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_MONOSPACED_SERIF); +} + +/* + Test the getAttributes with font style monospaced serif, fontStyle value expressed in Upper case + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per penSizeValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueMonospacedserifUpperCase) +{ + std::string fontStyleValue = "MONOSPACED_SERIF"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_MONOSPACED_SERIF); +} + +/* + Test the getAttributes with font style proportional serif. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueProportionalserif) +{ + std::string fontStyleValue = "proportional_serif"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_PROPORTIONAL_SERIF); +} + +/* + Test the getAttributes with font style monospaced sans serif. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueMonospacedsansserif) +{ + std::string fontStyleValue = "monospaced_sanserif"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_MONOSPACED_SANSSERIF); +} + +/* + Test the getAttributes with font style proportional sans serif. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueProportionalsansserif) +{ + std::string fontStyleValue = "proportional_sanserif"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_PROPORTIONAL_SANSSERIF); +} + +/* + Test the getAttributes with font style casual. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueCasual) +{ + std::string fontStyleValue = "casual"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_CASUAL); +} + +/* + Test the getAttributes with font style cursive. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueCursive) +{ + std::string fontStyleValue = "cursive"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_CURSIVE); +} + +/* + Test the getAttributes with font style small capital. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueSmallcapital) +{ + std::string fontStyleValue = "small capital"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_SMALL_CAPITALS); +} + +/* + Test the getAttributes with font style default. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueDefault) +{ + std::string fontStyleValue = "default"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_DEFAULT); +} + +/* + Test the getAttributes with font style auto. + This will also test the output expected from the getFontStyle function. + Expected values are: - a valid attributesMask and attributeValues as per fontStyleValue +*/ +TEST_F(GetTextStyleAttributesTests, FontStyleExpectedJsonOptionsStringValueAuto) +{ + std::string fontStyleValue = "auto"; + std::string options = "{\"fontStyle\":\"" + fontStyleValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontStyleValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_STYLE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_STYLE_ARR_POSITION], mAttributes->FONT_STYLE_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, FontColorRightKeyInvalidValueJsonOptionsString) +{ + std::string fontColorValue = "pink"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with font color black. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueBlackLowerCase) +{ + std::string fontColorValue = "black"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with font color black. + This will also test the output expected from the getColor function. Color value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueBlackUpperCase) +{ + std::string fontColorValue = "BLACK"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with font color white. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueWhite) +{ + std::string fontColorValue = "white"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_WHITE); +} + +/* + Test the getAttributes with font color red. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value. +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueRed) +{ + std::string fontColorValue = "red"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_RED); +} + +/* + Test the getAttributes with font color green. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueGreen) +{ + std::string fontColorValue = "green"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_GREEN); +} + +/* + Test the getAttributes with font color blue. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value. +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueBlue) +{ + std::string fontColorValue = "blue"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_BLUE); +} + +/* + Test the getAttributes with font color yellow. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value. +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueBlack) +{ + std::string fontColorValue = "yellow"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_YELLOW); +} + +/* + Test the getAttributes with font color magenta. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueMagenta) +{ + std::string fontColorValue = "magenta"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_MAGENTA); +} + +/* + Test the getAttributes with font color cyan. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueCyan) +{ + std::string fontColorValue = "cyan"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_CYAN); +} + +/* + Test the getAttributes with font color auto. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, FontColorExpectedJsonOptionsStringValueAuto) +{ + std::string fontColorValue = "auto"; + std::string options = "{\"textForegroundColor\":\"" + fontColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_COLOR_ARR_POSITION], mAttributes->COLOR_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorRightKeyInvalidValueJsonOptionsString) +{ + std::string backgroundColorValue = "pink"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with background color black. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueBlackLowerCase) +{ + std::string backgroundColorValue = "black"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with background color black. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueBlackUpperCase) +{ + std::string backgroundColorValue = "BLACK"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with background color black. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueWhite) +{ + std::string backgroundColorValue = "white"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_WHITE); +} + +/* + Test the getAttributes with background color red. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueRed) +{ + std::string backgroundColorValue = "red"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_RED); +} + +/* + Test the getAttributes with background color green. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueGreen) +{ + std::string backgroundColorValue = "green"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_GREEN); +} + +/* + Test the getAttributes with background color blue. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueBlue) +{ + std::string backgroundColorValue = "blue"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_BLUE); +} + +/* + Test the getAttributes with background color yellow. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueYellow) +{ + std::string backgroundColorValue = "yellow"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_YELLOW); +} + +/* + Test the getAttributes with background color magenta. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueMagenta) +{ + std::string backgroundColorValue = "magenta"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_MAGENTA); +} + +/* + Test the getAttributes with background color cyan. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueCyan) +{ + std::string backgroundColorValue = "cyan"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_CYAN); +} + +/* + Test the getAttributes with background color auto. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundColor value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundColorExpectedJsonOptionsStringValueAuto) +{ + std::string backgroundColorValue = "auto"; + std::string options = "{\"textBackgroundColor\":\"" + backgroundColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_COLOR_ARR_POSITION], mAttributes->COLOR_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeRightKeyInvalidValueJsonOptionsString) +{ + std::string edgeTypeValue = "curved"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with edge style none. + This will also test the output expected from the getEdgeType function. Edge type value in lower case + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueNoneLowerCase) +{ + std::string edgeTypeValue = "none"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_NONE); +} + +/* + Test the getAttributes with edge style none. + This will also test the output expected from the getEdgeType function. Edge type value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueNoneUpperCase) +{ + std::string edgeTypeValue = "NONE"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_NONE); +} + +/* + Test the getAttributes with edge style raised. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueRaised) +{ + std::string edgeTypeValue = "raised"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_RAISED); +} + +/* + Test the getAttributes with edge style depressed. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueDepressed) +{ + std::string edgeTypeValue = "depressed"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_DEPRESSED); +} + +/* + Test the getAttributes with edge style uniform. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueUniform) +{ + std::string edgeTypeValue = "uniform"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_UNIFORM); +} + +/* + Test the getAttributes with edge style left drop shadow. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueLeftdropshadow) +{ + std::string edgeTypeValue = "Left drop shadow"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_SHADOW_LEFT); +} + +/* + Test the getAttributes with edge style right drop shadow. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueRightdropshadow) +{ + std::string edgeTypeValue = "Right drop shadow"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_SHADOW_RIGHT); +} + +/* + Test the getAttributes with edge style auto. + This will also test the output expected from the getEdgeType function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeStyle value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeTypeExpectedJsonOptionsStringValueAuto) +{ + std::string edgeTypeValue = "auto"; + std::string options = "{\"textEdgeStyle\":\"" + edgeTypeValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeTypeValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_TYPE_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_TYPE_ARR_POSITION], mAttributes->EDGE_TYPE_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorRightKeyInvalidValueJsonOptionsString) +{ + std::string edgeColorValue = "pink"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with edge color black. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueBlackLowerCase) +{ + std::string edgeColorValue = "black"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with edge color black. + This will also test the output expected from the getColor function. Color value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueBlackUpperCase) +{ + std::string edgeColorValue = "BLACK"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with edge color white. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueWhite) +{ + std::string edgeColorValue = "white"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_WHITE); +} + +/* + Test the getAttributes with edge color red. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueRed) +{ + std::string edgeColorValue = "red"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_RED); +} + +/* + Test the getAttributes with edge color green. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueGreen) +{ + std::string edgeColorValue = "green"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_GREEN); +} + +/* + Test the getAttributes with edge color blue. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueBlue) +{ + std::string edgeColorValue = "blue"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_BLUE); +} + +/* + Test the getAttributes with edge color yellow. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueYellow) +{ + std::string edgeColorValue = "yellow"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_YELLOW); +} + +/* + Test the getAttributes with edge color mageta. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueMagenta) +{ + std::string edgeColorValue = "magenta"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_MAGENTA); +} + +/* + Test the getAttributes with edge color cyan. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueCyan) +{ + std::string edgeColorValue = "cyan"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_CYAN); +} + +/* + Test the getAttributes with edge color auto. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per textEdgeColor value +*/ +TEST_F(GetTextStyleAttributesTests, EdgeColorExpectedJsonOptionsStringValueAuto) +{ + std::string edgeColorValue = "auto"; + std::string options = "{\"textEdgeColor\":\"" + edgeColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(edgeColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<EDGE_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->EDGE_COLOR_ARR_POSITION], mAttributes->COLOR_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityRightKeyInvalidValueJsonOptionsString) +{ + std::string backgroundOpacityValue = "none"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with background opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueSolidLowerCase) +{ + std::string backgroundOpacityValue = "solid"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with background opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueSolidUpperCase) +{ + std::string backgroundOpacityValue = "SOLID"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with background opacity flash. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueFlash) +{ + std::string backgroundOpacityValue = "flash"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_FLASHING); +} + +/* + Test the getAttributes with background opacity translucent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueTranslucent) +{ + std::string backgroundOpacityValue = "translucent"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSLUCENT); +} + +/* + Test the getAttributes with background opacity transparent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueTransparent) +{ + std::string backgroundOpacityValue = "transparent"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSPARENT); +} + +/* + Test the getAttributes with background opacity auto. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textBackgroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, BackgroundOpacityExpectedJsonOptionsStringValueAuto) +{ + std::string backgroundOpacityValue = "auto"; + std::string options = "{\"textBackgroundOpacity\":\"" + backgroundOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(backgroundOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<BACKGROUND_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->BACKGROUND_OPACITY_ARR_POSITION], mAttributes->OPACITY_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityRightKeyInvalidValueJsonOptionsString) +{ + std::string fontOpacityValue = "none"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with font opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueSolidLowerCase) +{ + std::string fontOpacityValue = "solid"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with font opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueSolidUpperCase) +{ + std::string fontOpacityValue = "SOLID"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with font opacity flash. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueFlash) +{ + std::string fontOpacityValue = "flash"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_FLASHING); +} + +/* + Test the getAttributes with font opacity translucent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueTranslucent) +{ + std::string fontOpacityValue = "translucent"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSLUCENT); +} + +/* + Test the getAttributes with font opacity transparent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueTransparent) +{ + std::string fontOpacityValue = "transparent"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSPARENT); +} + +/* + Test the getAttributes with font opacity auto. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per textForegroundOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, FontOpacityExpectedJsonOptionsStringValueAuto) +{ + std::string fontOpacityValue = "auto"; + std::string options = "{\"textForegroundOpacity\":\"" + fontOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(fontOpacityValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<FONT_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->FONT_OPACITY_ARR_POSITION], mAttributes->OPACITY_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorRightKeyInvalidValueJsonOptionsString) +{ + std::string windowColorValue = "pink"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with window color black. + This will also test the output expected from the getColor function. Color value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueBlackLowerCase) +{ + std::string windowColorValue = "black"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with window color black. + This will also test the output expected from the getColor function. Color value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueBlackUpperCase) +{ + std::string windowColorValue = "BLACK"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_BLACK); +} + +/* + Test the getAttributes with window color white. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueWhite) +{ + std::string windowColorValue = "white"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_WHITE); +} + +/* + Test the getAttributes with window color red. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueRed) +{ + std::string windowColorValue = "red"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_RED); +} + +/* + Test the getAttributes with window color green. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueGreen) +{ + std::string windowColorValue = "green"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_GREEN); +} + +/* + Test the getAttributes with window color blue. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueBlue) +{ + std::string windowColorValue = "blue"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_BLUE); +} + +/* + Test the getAttributes with window color yellow. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueYellow) +{ + std::string windowColorValue = "yellow"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_YELLOW); +} + +/* + Test the getAttributes with window color magenta. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueMagenta) +{ + std::string windowColorValue = "Magenta"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_MAGENTA); +} + +/* + Test the getAttributes with window color cyan. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueCyan) +{ + std::string windowColorValue = "cyan"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_CYAN); +} + +/* + Test the getAttributes with window color auto. + This will also test the output expected from the getColor function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillColor value +*/ +TEST_F(GetTextStyleAttributesTests, WindowColorExpectedJsonOptionsStringValueAuto) +{ + std::string windowColorValue = "auto"; + std::string options = "{\"windowFillColor\":\"" + windowColorValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowColorValue), Return(true))); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())).WillOnce(Return(false)); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_COLOR_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_COLOR_ARR_POSITION], mAttributes->COLOR_EMBEDDED); +} + +/* + Test the getAttributes function supplying it with Right Key but invalid corresponding value. + In this case getAttributes must set the attributeMask to 0; informing caller nothing to proceed +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityRightKeyInvalidValueJsonOptionsString) +{ + std::string windowOpacityValue = "none"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0x1234; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, 0); +} + +/* + Test the getAttributes with font opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in lower case. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueSolidLowerCase) +{ + std::string windowOpacityValue = "solid"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with font opacity solid. + This will also test the output expected from the getOpacity function. Opacity value in upper case. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueSolidUpperCase) +{ + std::string windowOpacityValue = "SOLID"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_SOLID); +} + +/* + Test the getAttributes with font opacity flash. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueFlash) +{ + std::string windowOpacityValue = "flash"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_FLASHING); +} + +/* + Test the getAttributes with font opacity translucent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueTranslucent) +{ + std::string windowOpacityValue = "translucent"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSLUCENT); +} + +/* + Test the getAttributes with font opacity transparent. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueTransparent) +{ + std::string windowOpacityValue = "transparent"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_TRANSPARENT); +} + +/* + Test the getAttributes with font opacity auto. + This will also test the output expected from the getOpacity function. + Expected values are: - a valid attributesMask and attributeValues as per windowFillOpacity value +*/ +TEST_F(GetTextStyleAttributesTests, WindowOpacityExpectedJsonOptionsStringValueAuto) +{ + std::string windowOpacityValue = "auto"; + std::string options = "{\"windowFillOpacity\":\"" + windowOpacityValue + "\"}"; + std::uint32_t attributesMask = 0; + attributesType attributesValues = {0}; + + EXPECT_CALL(*g_mockPlayerJsonObject, get("penSize", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("fontStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeStyle", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textEdgeColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textBackgroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("textForegroundOpacity", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillColor", An())).WillOnce(Return(false)); + EXPECT_CALL(*g_mockPlayerJsonObject, get("windowFillOpacity", An())) + .WillOnce(DoAll(SetArgReferee<1>(windowOpacityValue), Return(true))); + + EXPECT_EQ(0, mAttributes->getAttributes(options, attributesValues, attributesMask)); + EXPECT_EQ(attributesMask, (1<WIN_OPACITY_ARR_POSITION)); + EXPECT_EQ(attributesValues[mAttributes->WIN_OPACITY_ARR_POSITION], mAttributes->OPACITY_EMBEDDED); +} diff --git a/middleware/test/utests/tests/TextStyleAttributes/TextStyleAttributesTests.cpp b/middleware/test/utests/tests/TextStyleAttributes/TextStyleAttributesTests.cpp new file mode 100644 index 000000000..f51285d11 --- /dev/null +++ b/middleware/test/utests/tests/TextStyleAttributes/TextStyleAttributesTests.cpp @@ -0,0 +1,26 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2022 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include + +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/test/utests/tests/base16Tests/CMakeLists.txt b/middleware/test/utests/tests/base16Tests/CMakeLists.txt new file mode 100644 index 000000000..c161e0d38 --- /dev/null +++ b/middleware/test/utests/tests/base16Tests/CMakeLists.txt @@ -0,0 +1,65 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2022 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(GoogleTest) + +set(PLAYER_ROOT "../../../../") +set(UTESTS_ROOT "../../") +set(EXEC_NAME base16Tests) + +include_directories(${PLAYER_ROOT} ${PLAYER_ROOT}/isobmff ${PLAYER_ROOT}/drm ${PLAYER_ROOT}/downloader ${PLAYER_ROOT}/drm/helper ${PLAYER_ROOT}/drm/ave ${PLAYER_ROOT}/subtitle ${PLAYER_ROOT}/middleware/subtitle) +include_directories(${PLAYER_ROOT}/subtec/libsubtec) +include_directories(${PLAYER_ROOT}/subtec/subtecparser) +include_directories(${PLAYER_ROOT}/playerJsonobject) +include_directories(${PLAYER_ROOT}/baseConversion) + +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) +include_directories(${GLIB_INCLUDE_DIRS}) +include_directories(${GSTREAMER_INCLUDE_DIRS}) +include_directories(${LibXml2_INCLUDE_DIRS}) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(SYSTEM ${UTESTS_ROOT}/mocks) +include_directories(${PLAYER_ROOT}/tsb/api) +include_directories(${PLAYER_ROOT}/../) + + +set(TEST_SOURCES base16Tests.cpp + base16playerTests.cpp) + + +set(PLAYER_SOURCES ${PLAYER_ROOT}/baseConversion/base16.cpp) + +add_executable(${EXEC_NAME} + ${TEST_SOURCES} + ${PLAYER_SOURCES}) + +set_target_properties(${EXEC_NAME} PROPERTIES FOLDER "utests") + +if (CMAKE_XCODE_BUILD_SYSTEM) + # XCode schema target + xcode_define_schema(${EXEC_NAME}) +endif() + +if (COVERAGE_ENABLED) + include(CodeCoverage) + APPEND_COVERAGE_COMPILER_FLAGS() +endif() + +target_link_libraries(${EXEC_NAME} fakes -pthread ${GLIB_LINK_LIBRARIES} ${OS_LD_FLAGS} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/middleware/test/utests/tests/base16Tests/base16Tests.cpp b/middleware/test/utests/tests/base16Tests/base16Tests.cpp new file mode 100644 index 000000000..2b0f772e3 --- /dev/null +++ b/middleware/test/utests/tests/base16Tests/base16Tests.cpp @@ -0,0 +1,153 @@ +/* +* If not stated otherwise in this file or this component's license file the +* following copyright and licenses apply: +* +* Copyright 2023 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "base16.h" +#include +using namespace testing; + +class Base16EncodeDecodeTest : public ::testing::Test { +protected: + char *encoded; + + void SetUp() override { + const unsigned char *inputData = (const unsigned char *)"hello"; + encoded = base16_Encode(inputData, 5); + ASSERT_NE(encoded, nullptr); + } + + void TearDown() override { + if (encoded != nullptr) { + free(encoded); + } + } +}; +TEST_F(Base16EncodeDecodeTest, EncodeValidData1) +{ + const unsigned char inputData[]={'H','e','l','l','o'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "48656c6c6f"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} +TEST_F(Base16EncodeDecodeTest, EncodeValidData2) +{ + const unsigned char inputData[]={'r','a','j','a','j','j','a','a','m'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "72616a616a6a61616d"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} +TEST_F(Base16EncodeDecodeTest, EncodeValidData3) +{ + const unsigned char inputData[]={'H','e','l','l','o','1','2','3','W','o','r','l','d'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "48656c6c6f313233576f726c64"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} +TEST_F(Base16EncodeDecodeTest, EncodeValidData4) +{ + const unsigned char inputData[]={'1','2','3','4','5','6','7','8'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "3132333435363738"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} + +TEST_F(Base16EncodeDecodeTest, EncodeValidData5) +{ + const unsigned char inputData[]={'@','#','$',' ','%','&'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "402324202526"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} +TEST_F(Base16EncodeDecodeTest, EncodeValidData6) +{ + const unsigned char inputData[]={' ',' ',' ',' ',' ','H'}; + size_t inputLength = sizeof(inputData); + const char* expectedOutput = "202020202048"; + char* encodedData = base16_Encode(inputData, inputLength); + EXPECT_STREQ(encodedData, expectedOutput); + +} + +TEST_F(Base16EncodeDecodeTest, DecodeEmptyString) { + size_t decodedLength = 0; + + const char *emptyData=""; + base16_Decode(emptyData, 0, &decodedLength); + EXPECT_EQ(decodedLength, 0); +} + +TEST_F(Base16EncodeDecodeTest, DecodeValidData1) +{ + size_t decodedLength = 0; + + const char inputData[]= {'4','8','6','5','6','c','6','c','6','f'}; + size_t inputLength = sizeof(inputData); + unsigned char outputData[]="Hello"; + unsigned char *decodeData= base16_Decode(inputData, inputLength, &decodedLength); + for(size_t i=0;i +int main(int argc, char** argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/middleware/vendor/SocInterface.cpp b/middleware/vendor/SocInterface.cpp new file mode 100644 index 000000000..8eb2fbf14 --- /dev/null +++ b/middleware/vendor/SocInterface.cpp @@ -0,0 +1,250 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "SocInterface.h" +#include "vendor/amlogic/AmlogicSocInterface.h" +#include "vendor/brcm/BrcmSocInterface.h" +#include "vendor/realtek/RealtekSocInterface.h" +#include "vendor/default/DefaultSocInterface.h" + +/** + * @brief Checks if the input string starts with the given prefix. + * + * @param inputStr The input string to check. + * @param prefix The prefix to check for. + * + * @return True if the input string starts with the prefix, false otherwise. + */ +bool SocInterface::StartsWith( const char *inputStr, const char *prefix ) +{ + bool rc = true; + while( *prefix ) + { + if( *inputStr++ != *prefix++ ) + { + rc = false; + break; + } + } + return rc; +} + +/** + * @brief To enable certain player configs based upon platform check + */ +SocPlatformType InferPlatformFromPluginScan() +{ + SocPlatformType platform = SOC_PLATFORM_DEFAULT; + // Ensure GST is initialized + if (!gst_init_check(nullptr, nullptr, nullptr)) { + MW_LOG_ERR("gst_init_check() failed"); + } + static const std::pair plugins[] = { + {"amlhalasink", SOC_PLATFORM_AMLOGIC}, + {"omxeac3dec", SOC_PLATFORM_REALTEK}, + {"brcmaudiodecoder", SOC_PLATFORM_BROADCOM}, + }; + + GstRegistry* registry = gst_registry_get(); + + for (const auto& plugin : plugins) + { + GstPluginFeature* pluginFeature = gst_registry_lookup_feature(registry, plugin.first); + if (pluginFeature) + { + gst_object_unref(pluginFeature); + MW_LOG_MIL("InterfacePlayerRDK: %s plugin found in registry", plugin.first); + platform = plugin.second; + break; + } + } + + if( platform == SOC_PLATFORM_DEFAULT ) + { + MW_LOG_WARN("InterfacePlayerRDK: None of the plugins found in registry"); + } + return platform; +} + +/** + * @brief Infers SoC platform type from device.properties. + * @return Inferred SoC platform type. + */ +SocPlatformType SocInterface::InferPlatformFromDeviceProperties( void ) +{ + SocPlatformType platform = SOC_PLATFORM_DEFAULT; + FILE* fp = fopen("/etc/device.properties", "rb"); + if (fp) + { + MW_LOG_MIL("opened /etc/device.properties"); + char buf[4096]; + while( fgets(buf, sizeof(buf), fp) ) + { + if (strncmp(buf, "SOC=", 4) == 0) + { + char* socName = buf + 4; // Start after "SOC=" + for (int i = 0; socName[i] != '\0'; i++) + { + if (isspace(socName[i])) + { + socName[i] = '\0'; // Terminate at first whitespace + break; + } + } + if (*socName != '\0') // If SOC name is not empty + { + MW_LOG_MIL("*** SOC %s ***", socName); + if (strcmp(socName, "AMLOGIC") == 0) + { + platform = SOC_PLATFORM_AMLOGIC; + break; + } + else if (strcmp(socName, "RTK") == 0) + { + platform = SOC_PLATFORM_REALTEK; + break; + } + else if (strcmp(socName, "BRCM") == 0) + { + platform = SOC_PLATFORM_BROADCOM; + break; + } + } + else + { + MW_LOG_WARN("*** SOC not found ***"); + } + } + } + fclose(fp); + } + else + { + MW_LOG_ERR("failed to open /etc/device.properties."); + } + return platform; +} + + +/** + * @brief Creates an instance of the SoC-specific interface based on the detected platform. + * + * @return A pointer to the created SocInterface object, or nullptr on failure. + */ +std::shared_ptr SocInterface::CreateSocInterface() +{ + static std::shared_ptr socInterface; + if( !socInterface) + { + SocPlatformType platformType = InferPlatformFromDeviceProperties(); + if(platformType == SOC_PLATFORM_DEFAULT) + { + platformType = InferPlatformFromPluginScan(); + } + switch (platformType) + { + case SOC_PLATFORM_AMLOGIC: + socInterface = std::make_shared(); + break; + case SOC_PLATFORM_BROADCOM: + socInterface = std::make_shared(); + break; + case SOC_PLATFORM_REALTEK: + socInterface = std::make_shared(); + break; + default: + socInterface = std::make_shared(); + break; + } + } + return socInterface; +} + +/** + * @brief Get video PTS. + * + * Retrieves the current video presentation timestamp (PTS). + * + * @param video_sink The video sink element (unused) + * @param video_dec The video decoder element. + * @param isWesteros A flag for Westeros logic. + * + * @return Video PTS in nanoseconds, or -1 on error. + */ +long long SocInterface::GetVideoPts(GstElement *video_sink, GstElement *video_dec, bool isWesteros) +{ + gint64 currentPTS = 0; + if(video_dec) + { + g_object_get(video_dec, "video-pts", ¤tPTS, NULL); + if(!isWesteros) + { + currentPTS *= 2; + } + } + return (long long)currentPTS; +} + +/** + * @brief Set decode error on source. + * + * Sets a decode error flag on the given source object. + * + * @param src The source object. + */ +void SocInterface::SetDecodeError(GstObject* src) +{ + if(src) + { + g_object_set(src, "report_decode_errors", TRUE, NULL); + } +} + +/** + * @brief Sets the state of Westeros Sink usage. + * + * This function updates the internal flag to indicate whether + * Westeros Sink is being used. It does not enable or disable + * Westeros Sink itself, but merely informs the SocInterface + * about its status. + * + * @param status Set to `true` if Westeros Sink is enabled, `false` otherwise. + */ +void SocInterface::SetWesterosSinkState(bool status) +{ + mUsingWesterosSink = status; +} + +/** + * @brief Configure Capability Acceptance for GStreamer Transform + * + * Sets up the accept_caps function pointer for a GStreamer base transform class. + * This allows the transform element to decide whether it can accept a given set of capabilities (caps), + * which is essential for negotiating media formats during pipeline setup. + * + * @param base_transform_class Pointer to the GStreamer base transform class to configure. + * @param accept_caps_func Function used to determine if the transform accepts specific caps. + */ +void SocInterface::ConfigureAcceptCaps(GstBaseTransformClass* base_transform_class , + AcceptCapsFunc accept_caps_func) { + if (accept_caps_func) { + base_transform_class->accept_caps = GST_DEBUG_FUNCPTR(accept_caps_func); + } +} diff --git a/middleware/vendor/SocInterface.h b/middleware/vendor/SocInterface.h new file mode 100644 index 000000000..cdf88edb0 --- /dev/null +++ b/middleware/vendor/SocInterface.h @@ -0,0 +1,488 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SOC_INTERFACE_H +#define SOC_INTERFACE_H +#include +#include +#include +#include +#include +#include +#include +#include "PlayerLogManager.h" + +#define REQUIRED_QUEUED_FRAMES_DEFAULT (5+1) + +typedef gboolean (*AcceptCapsFunc)(GstBaseTransform *, GstPadDirection, GstCaps *); + +/** + * @brief Enumeration for play flags. + * + * This enumeration defines flags used to control playback behavior. + */ +typedef enum +{ + PLAY_FLAG_VIDEO = (1 << 0), /**< value is 0x001 */ + PLAY_FLAG_AUDIO = (1 << 1), /**< value is 0x002 */ + PLAY_FLAG_TEXT = (1 << 2), /**< value is 0x004 */ + PLAY_FLAG_VIS = (1 << 3), /**< value is 0x008 */ + PLAY_FLAG_SOFT_VOLUME = (1 << 4), /**< value is 0x010 */ + PLAY_FLAG_NATIVE_AUDIO = (1 << 5), /**< value is 0x020 */ + PLAY_FLAG_NATIVE_VIDEO = (1 << 6), /**< value is 0x040 */ + PLAY_FLAG_DOWNLOAD = (1 << 7), /**< value is 0x080 */ + PLAY_FLAG_BUFFERING = (1 << 8), /**< value is 0x100 */ + PLAY_FLAG_DEINTERLACE = (1 << 9), /**< value is 0x200 */ + PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10) /**< value is 0x400 */ +}playFlags; + +/** + * @enum SocPlatformType + * @brief Enumeration of supported SoC platforms. + */ +enum SocPlatformType +{ + SOC_PLATFORM_DEFAULT, /**< Ubuntu/OSX */ + SOC_PLATFORM_AMLOGIC, /**< Amlogic */ + SOC_PLATFORM_REALTEK, /**< Realtek */ + SOC_PLATFORM_BROADCOM, /**< Broadcom */ +}; + +/** + * @class SocInterface + * @brief Interface class for SoC-specific functionalities. + */ +class SocInterface +{ +protected: + /** + * @brief Infers the SoC type from device.properties. + * @return The SoC platform type. + */ + static SocPlatformType InferPlatformFromDeviceProperties( void ); + + /** + * @brief Checks if the input string starts with the given prefix. + * + * @param inputStr The input string to check. + * @param prefix The prefix to check for. + * + * @return True if the input string starts with the prefix, false otherwise. + */ + bool StartsWith( const char *inputStr, const char *prefix ); + + /*config to indicate platforms using westeros sink*/ + bool mUsingWesterosSink = false; + +public: + SocInterface() {} + + /** + * @brief Sets the state of Westeros Sink usage. + * + * This function updates the internal flag to indicate whether + * Westeros Sink is being used. It does not enable or disable + * Westeros Sink itself, but merely informs the SocInterface + * about its status. + * + * @param status Set to `true` if Westeros Sink is enabled, `false` otherwise. + */ + void SetWesterosSinkState(bool status); + + /** + * @brief Get SVP Context + */ + virtual void SvpGetContext(void **svpCtx, int flags){}; + + /** + * @brief Free SVP Context + */ + virtual void SvpFreeContext(void *svpCtx){}; + + /*@brief returns true if video stats required from sink otherwise false*/ + virtual bool IsPlaybackQualityFromSink(){return false;} + + /** + * Sets buffer size and duration for the given GstElement. + * + * @param sink The GstElement to configure. + * @param size The desired buffer size. + */ + virtual void SetVideoBufferSize(GstElement *sink, int size){}; + + /** + * Sets asynchronous mode for the given Sink. + * + * @param sink element. + * @param status Enable (TRUE) or disable (FALSE) asynchronous mode. + */ + virtual void SetSinkAsync(GstElement *sink, gboolean status){} + + /** + * @brief Creates an instance of the SoC-specific interface. + * @return A pointer to the created SocInterface object. + */ + static std::shared_ptr CreateSocInterface(); + + /** + * @brief Configure the accept caps + * @return void + */ + virtual void ConfigureAcceptCaps( GstBaseTransformClass* base_transform_class, + AcceptCapsFunc accept_caps_func); + + /** + * @brief Indicates whether transform capabilities are required. + * @return true if transform capabilities are required; otherwise, false + */ + virtual bool IsTransformCapsRequired() const { + return false; } + + /** + * @brief Indicates whether decryption is required. + * @return true if decryption are required; otherwise, false + */ + virtual bool IsDecryptRequired() const { + return false; } + + /** + * @brief Check if AppSrc should be used. + * + * Determines whether the AppSrc element should be used in the current context. + * + * @return True if AppSrc should be used, false otherwise. + */ + virtual bool UseAppSrc(){return false;} + + /** + * @brief Check if Westeros sink should be used. + * + * Determines whether the Westeros sink should be used in the current context. + * + * @return True if platform uses Westeros sink, false otherwise. + */ + virtual bool UseWesterosSink(){return true;} + + /** + * @brief Check if audio fragments should be synchronized. + * + * Determines whether audio fragments should be synchronized. + * + * @return True if audio fragments should be synchronized, false otherwise. + */ + virtual bool IsAudioFragmentSyncSupported(){return false;} + + /** + * @brief Check if live latency correction should be enabled. + * + * Determines whether live latency correction should be enabled. + * + * @return True if live latency correction should be enabled, false otherwise. + */ + virtual bool EnableLiveLatencyCorrection(){return false;} + + /** + * @brief Get the required number of queued frames. + * + * Returns the total number of frames that should be queued. + * + * @return The required number of queued frames. + */ + virtual int RequiredQueuedFrames(){return REQUIRED_QUEUED_FRAMES_DEFAULT;} + + /** + * @brief Check if PTS restamping is supported by the platform. + * + * Determines whether the platform supports PTS restamping. + * + * @return True if PTS restamping is supported, false otherwise. + */ + virtual bool EnablePTSRestamp(){return false;} + + /** + * Checks if this is the first tune with Westeros disabled. + * + * @return True if this is the first tune with Westeros disabled for the current platform, false otherwise. + */ + virtual bool IsFirstTuneWithWesteros(){return false;} + + /** + * @brief Set SoC volume property name. + */ + virtual void SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume)=0; + + /** + * @brief enables the seamless switch property + * @param object The GStreamer element to configure. + * @param value True to enable seamless switching, false to disable. + */ + virtual void SetSeamlessSwitch(GstElement* object, gboolean value){} + + /** + * @brief Sets the sinkbin to audio-only mode. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param sinkbin The GStreamer sinkbin to configure. + * @param property The name of the property to set for audio-only mode. + */ + virtual bool AudioOnlyMode(GstElement *sinkbin){return false;} + + /** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ + virtual bool SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) = 0; + + /** + * @brief Retrieves the source pad of the given GStreamer element. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param element The GStreamer element to retrieve the source pad from. + * @return A pointer to the source pad of the element, or NULL if not found. + */ + virtual GstPad* GetSourcePad(GstElement* element){return NULL;} + + /** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + */ + virtual GstElement* GetVideoSink(GstElement* sinkbin){return nullptr;} + + /** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ + virtual void SetAC4Tracks(GstElement *src, int trackId) = 0; + + /** + * @brief Set platform playback rate. + * @return True on success, false otherwise. + */ + virtual bool SetPlatformPlaybackRate(){return false;} + + /** + * @brief Set rate correction. + * @return True on success, false otherwise. + */ + virtual bool SetRateCorrection() = 0; + + /** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ + virtual bool IsVideoSink(const char* name) = 0; + + /** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ + virtual bool IsAudioSinkOrAudioDecoder(const char* name) = 0; + + /** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ + virtual bool IsVideoDecoder(const char* name) = 0; + + /** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ + virtual bool ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) = 0; + + /** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name + * @return True if it's an audio or video decoder, false otherwise. + */ + virtual bool IsAudioOrVideoDecoder(const char* name) = 0; + + /** + * @brief Disable asynchronous audio. + * @param audio_sink Audio sink element. + * @param rate Playback rate. + * @param isSeeking True if seeking is in progress, false otherwise. + * @return True if async changed from enabled to disabled, false otherwise. + */ + virtual bool DisableAsyncAudio(GstElement *audio_sink, int rate, bool isSeeking){return false;}; + + /** + * Gets the decoder handle from the video decoder element. + * + * @param dec_handle Pointer to store the decoder handle. + * @param video_dec The video decoder element. + */ + virtual void GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec) = 0; + + /** + * @brief Resets the trick play UTC. + * + * @return True if the reset is required, false otherwise. + */ + virtual bool ResetTrickUTC(){return false;} + + /** + * @brief Get video PTS. + * + * Retrieves the current video presentation timestamp (PTS). + * + * @param video_sink The video sink element. + * @param video_dec The video decoder element. + * @param isWesteros A flag for Westeros logic. + * + * @return Video PTS in nanoseconds, or -1 on error. + */ + virtual long long GetVideoPts(GstElement *video_sink, GstElement *video_dec, bool isWesteros); + + /** + * @brief Notify first video frame. + */ + virtual bool NotifyVideoFirstFrame(){return false;} + + /** + * @brief Set decode error on source. + * + * Sets a decode error flag on the given source object. + * + * @param src The source object. + */ + virtual void SetDecodeError(GstObject* src); + + /** + * @brief Set freerun threshold on source. + * + * Sets the freerun threshold on the given source object. + * + * @param src The source object. + */ + virtual void SetFreerunThreshold(GstObject* src){}; + + /** + * @brief Check if element setup is required. + * + * Determines if the element requires setup before it can be used. + * + * @return True if setup is required, false otherwise. + */ + virtual bool RequiredElementSetup(){return false;} + + /** + * @brief Set audio routing properties on source. + * + * Sets audio routing properties on the given source element. + * + * @param source The source element. + */ + virtual void SetAudioRoutingProperties(GstElement *source){} + + /** + * @brief Check if first audio frame callback is set. + * + * Determines if a callback function has been set for the first audio frame. + * + * @return True if a callback is set, false otherwise. + */ + + virtual bool HasFirstAudioFrameCallback(){return true;} + + /** + * @brief Check if video sink errors are handled. + * + * Determines if the platform handles errors from the video sink. + * + * @return True if video sink errors are handled, false otherwise. + */ + virtual bool IsVideoSinkHandleErrors(){return false;} + + /** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ + virtual void SetPlaybackFlags(gint &flags, bool isSub)=0; + + /** + * @brief checks if the firstFrame is received from the simulator + */ + virtual bool IsSimulatorFirstFrame(){return false;} + + /** + * @brief checks if the sink is from the simulator + */ + virtual bool IsSimulatorSink(){return false;} + + /** + * @brief Configure the plugin priority for PulseAudio. + */ + virtual void ConfigurePluginPriority(){}; + + /** + * @brief checks if the teardown is required for simulator + */ + virtual bool ShouldTearDownForTrickplay(){return false;} + + /** + * @brief checks if the video sample is from the simulator + */ + virtual bool IsSimulatorVideoSample(){return false;} + + /** + *@brief Sets the platform specific H264 caps + */ + virtual void SetH264Caps(GstCaps *caps){} + + /** + *@brief Sets the HEVC caps for simulator + */ + virtual void SetHevcCaps(GstCaps *caps){} + + /** + * @brief Resets segment event flags during trickplay transitions. + * + * Manages segment event tracking for trickplay scenarios without disrupting seekplay or advertisements. + */ + virtual bool ResetNewSegmentEvent(){return false;} + + /** + * @brief Checks if the platform is video master. + * + * @param videoSink The video sink element. + * @return 'true' if video master otherwise false. + */ + virtual bool IsVideoMaster(GstElement *videoSink) = 0; +}; +#endif diff --git a/middleware/vendor/amlogic/AmlogicSocInterface.cpp b/middleware/vendor/amlogic/AmlogicSocInterface.cpp new file mode 100644 index 000000000..f6c8c3a5a --- /dev/null +++ b/middleware/vendor/amlogic/AmlogicSocInterface.cpp @@ -0,0 +1,302 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AmlogicSocInterface.h" +#include "gst_svp_meta.h" + +/** + * @brief AmlogicSocInterface constructor. + */ +AmlogicSocInterface::AmlogicSocInterface() +{ +} + +/** + * @brief Get SoC volume property name. + * @return Volume property name. + */ +void AmlogicSocInterface::SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume) +{ + /* Avoid mute property setting for AMLOGIC as use of "mute" property on pipeline is impacting all other players */ + /* Using "stream-volume" property of audio-sink for setting volume and mute for AMLOGIC platform */ + volume = "stream-volume"; + isSinkBinVolume = false; +} + +/** + * @brief enables the seamless switch property + * @param object The GStreamer element to configure. + * @param value True to enable seamless switching, false to disable. + */ +void AmlogicSocInterface::SetSeamlessSwitch(GstElement* sink, gboolean value) +{ + MW_LOG_INFO("AMLOGIC:setting seamless property"); + g_object_set(sink, "seamless-switch", value, NULL); +} + +/** + * @brief Sets the sinkbin to audio-only mode. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param sinkbin The GStreamer sinkbin to configure. + * @param property The name of the property to set for audio-only mode. + */ +bool AmlogicSocInterface::AudioOnlyMode(GstElement *sinkbin) +{ + gint n_audio; + bool firstFrameReceived = false; + g_object_get(sinkbin, "n-audio", &n_audio, NULL); + + if(n_audio > 0) + { + firstFrameReceived = true; + } + return firstFrameReceived; +} + +/** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ +bool AmlogicSocInterface::SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) +{ + bool status = false; + /*for gst version 1.18.0 we need to apply rate into audio/video source pad*/ + for (GstElement* source : sources) + { + if(source) + { + GstPad* sourceEleSrcPad = gst_element_get_static_pad(GST_ELEMENT(source), "src"); + if(!sourceEleSrcPad) + { + MW_LOG_ERR("failed to get static pad retrying"); + continue; + } + /* + gboolean ret = gst_pad_send_event(sourceEleSrcPad, gst_event_new_seek (rate, GST_FORMAT_TIME, + static_cast(GST_SEEK_FLAG_INSTANT_RATE_CHANGE), + GST_SEEK_TYPE_NONE,0, GST_SEEK_TYPE_NONE, 0)); + gst_object_unref(sourceEleSrcPad); + */ + GstEvent* seek_event = gst_event_new_seek(rate, GST_FORMAT_TIME, static_cast(GST_SEEK_FLAG_INSTANT_RATE_CHANGE), GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_NONE, 0); + if (!seek_event) + { + MW_LOG_ERR("Failed to create seek event"); + gst_object_unref(sourceEleSrcPad); + continue; + } + gboolean ret = gst_pad_send_event(sourceEleSrcPad, seek_event); + gst_object_unref(sourceEleSrcPad); + if(ret) + { + status = true; + } + else + { + MW_LOG_ERR("Vendor: failed to send the rate event to src pad"); + } + } + } + MW_LOG_MIL("Current rate: %g", rate); + + return status; +} + +/** + * @brief Retrieves the source pad of the given GStreamer element. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param element The GStreamer element to retrieve the source pad from. + * @return A pointer to the source pad of the element, or NULL if not found. + */ +GstPad* AmlogicSocInterface::GetSourcePad(GstElement* source) +{ + GstPad* sourceEleSrcPad = gst_element_get_static_pad(source, "src"); + return sourceEleSrcPad; // Return the pad, or NULL if not found +} + +/** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ +void AmlogicSocInterface::SetAC4Tracks(GstElement *src, int trackId) +{ + MW_LOG_INFO("Selecting AC4 Track Id : %d", trackId); + if(src) + { + g_object_set(src, "ac4-presentation-group-index", trackId, NULL); + } + else + { + MW_LOG_ERR("No valid src to set ac4-presentation-group-index"); + } +} + +/** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ +bool AmlogicSocInterface::IsVideoSink(const char* name) +{ + return name && StartsWith(name, "westerossink"); +} + +/** + * @brief Get SVP Context + * @param svpCtx svp context + * @param server To identify server/client + * @param flags SVP Flag + */ +void AmlogicSocInterface::SvpGetContext(void **svpCtx, int flags) +{ + gst_svp_ext_get_context(svpCtx, Server, flags); +} + +/** + * @brief Free SVP Context + * @param svpCtx svp context + */ +void AmlogicSocInterface::SvpFreeContext(void *svpCtx) +{ + gst_svp_ext_free_context(svpCtx); +} + +/** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ +bool AmlogicSocInterface::IsAudioSinkOrAudioDecoder(const char* name) +{ + return name && StartsWith(name, "amlhalasink"); +} + +/** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @param isWesteros Westeros flag. + * @return True if it's a video decoder, false otherwise. + */ +bool AmlogicSocInterface::IsVideoDecoder(const char* name) +{ + return name && StartsWith(name, "westerossink"); +} + +/** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ +bool AmlogicSocInterface::ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) +{ + if (!audio_sink || !src) + { + MW_LOG_ERR("ConfigureAudioSink: Invalid input parameters"); + return false; + } + + bool status = false; + const char* srcName = GST_OBJECT_NAME(src); + if (srcName && StartsWith(srcName, "amlhalasink")) + { + gst_object_replace(reinterpret_cast(audio_sink), src); + + if (*audio_sink) // Ensure audio_sink was set correctly + { + g_object_set(*audio_sink, "disable-xrun", TRUE, NULL); + status = true; + } + } + return status; +} +/** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ +bool AmlogicSocInterface::IsAudioOrVideoDecoder(const char* name) +{ + return name && StartsWith(name, "westerossink"); +} + +/** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ +void AmlogicSocInterface::SetPlaybackFlags(gint &flags, bool isSub) +{ + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_NATIVE_AUDIO | PLAY_FLAG_NATIVE_VIDEO; + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_SOFT_VOLUME; + if(isSub) + { + flags = PLAY_FLAG_TEXT; + } +} + +/** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + * @return Video sink element. + */ + +GstElement* AmlogicSocInterface::GetVideoSink(GstElement* sinkbin) +{ + GstElement* vidsink = nullptr; + if(!sinkbin) + { + MW_LOG_ERR("Invalid SinkBin"); + } + else if(mUsingWesterosSink) + { + MW_LOG_INFO("using westerossink"); + vidsink = gst_element_factory_make("westerossink", NULL); + g_object_set(sinkbin, "video-sink", vidsink, NULL); + } + return vidsink; +} + +/** + * @brief Retrieves the CC decoder handle. + * @param[out] dec_handle Pointer to store the retrieved CC decoder handle. + * @param[in] video_dec The GStreamer video decoder element from which the CC decoder handle is extracted. + */ +void AmlogicSocInterface::GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec) +{ + if (video_dec) + { + g_object_get(video_dec, "videodecoder", dec_handle, NULL); + } +} diff --git a/middleware/vendor/amlogic/AmlogicSocInterface.h b/middleware/vendor/amlogic/AmlogicSocInterface.h new file mode 100644 index 000000000..3d5373040 --- /dev/null +++ b/middleware/vendor/amlogic/AmlogicSocInterface.h @@ -0,0 +1,205 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AMLOGIC_SOC_INTERFACE_H +#define AMLOGIC_SOC_INTERFACE_H + +#include "SocInterface.h" +/** + * @brief Amlogic SoC interface class. + * + * This class provides an implementation of the SocInterface for Amlogic SoCs. + */ +class AmlogicSocInterface : public SocInterface +{ + public: + AmlogicSocInterface(); + + /** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + */ + + GstElement* GetVideoSink(GstElement* sinkbin)override; + + /** + * @brief Get SoC volume property name. + * @return Volume property name. + */ + void SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume)override; + + /** + * @brief enables the seamless switch property + * @param object The GStreamer element to configure. + * @param value True to enable seamless switching, false to disable. + */ + void SetSeamlessSwitch(GstElement* object, gboolean value) override; + + /** + * @brief Sets the sinkbin to audio-only mode. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param sinkbin The GStreamer sinkbin to configure. + * @param property The name of the property to set for audio-only mode. + */ + bool AudioOnlyMode(GstElement *sinkbin) override; + + /** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ + bool SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) override; + + /** + * @brief Retrieves the source pad of the given GStreamer element. + * + * This is a pure virtual function that must be implemented by derived classes. + * + * @param element The GStreamer element to retrieve the source pad from. + * @return A pointer to the source pad of the element, or NULL if not found. + */ + GstPad* GetSourcePad(GstElement* element) override; + + /** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ + void SetAC4Tracks(GstElement *src, int trackId) override; + + /** + * @brief Get SVP Context + */ + void SvpGetContext(void **svpCtx, int flags)override; + + /** + * @brief Free SVP Context + */ + void SvpFreeContext(void *svpCtx)override; + + /** + * @brief Configure the accept caps + * @return void + */ + void ConfigureAcceptCaps( GstBaseTransformClass* base_transform_class, + AcceptCapsFunc accept_caps_func)override { + return; } + + /** + * @brief Indicates whether transform capabilities are required. + * @return true if transform capabilities are required; otherwise, false + */ + bool IsTransformCapsRequired() const override { + return true; } + + /** + * @brief Indicates whether decryption is required. + * @return true if decryption are required; otherwise, false + */ + bool IsDecryptRequired() const override { + return true; } + + /** + * @brief Set rate correction. + * @return True on success, false otherwise. + */ + bool SetRateCorrection() override {return false;} + + /** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ + bool IsVideoSink(const char* name)override; + + /** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ + bool IsAudioSinkOrAudioDecoder(const char* name) override; + + /** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ + bool IsVideoDecoder(const char* name)override; + + /** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ + bool ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync)override; + + /** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ + bool IsAudioOrVideoDecoder(const char* name)override; + + /** + * @brief Retrieves the CC decoder handle. + * @param[out] dec_handle Pointer to store the retrieved CC decoder handle. + * @param[in] video_dec The GStreamer video decoder element from which the CC decoder handle is extracted. + */ + + void GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec)override; + + /** + * @brief Disable asynchronous audio. + * @param audio_sink Audio sink element. + * @param rate Playback rate. + * @param isSeeking True if seeking is in progress, false otherwise. + * @return True on success, false otherwise. + */ + void SetPlaybackFlags(gint &flags, bool isSub)override; + + /** + * @brief Resets segment event flags during trickplay transitions. + * + * Manages segment event tracking for trickplay scenarios without disrupting seekplay or advertisements. + */ + virtual bool ResetNewSegmentEvent()override{return true;} + + /** + * @brief Check if the video is the master stream. + * + * This function always returns false, indicating that the video is not the master stream. + * + * @param videoSink The video sink element. + * @param isRialto Flag indicating whether Rialto sink is being used. + * @return false indicating the video is not the master stream. + */ + bool IsVideoMaster(GstElement *videoSink)override{return false;} +}; + +#endif diff --git a/middleware/vendor/brcm/BrcmSocInterface.cpp b/middleware/vendor/brcm/BrcmSocInterface.cpp new file mode 100644 index 000000000..ebd0a9f55 --- /dev/null +++ b/middleware/vendor/brcm/BrcmSocInterface.cpp @@ -0,0 +1,209 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "BrcmSocInterface.h" + +BrcmSocInterface::BrcmSocInterface() +{ +} + +/** + * @brief Get SoC volume property name. + * @return Volume property name. + */ +void BrcmSocInterface::SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume) +{ + volume = "volume"; + mute = "mute"; + isSinkBinVolume = false; /*volume/mute property should be applied on audio_sink*/ +} + +/** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ +bool BrcmSocInterface::SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) +{ + //For rialto sinks default soc routine will be called + bool status = true; + MW_LOG_MIL("send custom-instant-rate-change : %f ...", rate); + GstStructure *structure = gst_structure_new("custom-instant-rate-change", "rate", G_TYPE_DOUBLE, rate, NULL); + if (!structure) + { + MW_LOG_ERR("failed to create custom-instant-rate-change structure"); + return false; + } + GstEvent * rate_event = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure); + if (!rate_event) + { + MW_LOG_ERR("failed to create rate_event"); + /* cleanup */ + gst_structure_free (structure); + return false; + } + MW_LOG_MIL("rate_event %p video_decoder %p audio_decoder %p", (void*)rate_event, (void*)video_dec, (void *)audio_dec); + if(video_dec) + { + if (!gst_element_send_event(video_dec, gst_event_ref(rate_event))) + { + MW_LOG_ERR("failed to push rate_event %p to video sink %p", (void*)rate_event, (void*)video_dec); + status = false; + } + } + + if(audio_dec) + { + if (!gst_element_send_event(audio_dec, gst_event_ref(rate_event))) + { + MW_LOG_ERR("failed to push rate_event %p to audio decoder %p", (void*)rate_event, (void*)audio_dec); + status = false; + } + } + // Unref since we have explicitly increased ref count + gst_event_unref(rate_event); + MW_LOG_MIL("Current rate: %g", rate); + + return status; +} + +/** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + * @return Video sink element. + */ +GstElement* BrcmSocInterface::GetVideoSink(GstElement* sinkbin) +{ + GstElement* vidsink = nullptr; + + if(mUsingWesterosSink) + { + MW_LOG_INFO("using westerossink"); + vidsink = gst_element_factory_make("westerossink", NULL); + } + else + { + vidsink = gst_element_factory_make("brcmvideosink", NULL); + } + + g_object_set(vidsink, "secure-video", TRUE, NULL); + g_object_set(sinkbin, "video-sink", vidsink, NULL); + return vidsink; +} + +/** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ +bool BrcmSocInterface::IsVideoSink(const char* name) +{ + return name && ( + StartsWith(name, "brcmvideosink") || + StartsWith(name, "westerossink")); +} + +/** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ +bool BrcmSocInterface::IsAudioSinkOrAudioDecoder(const char* name) +{ + return name && StartsWith(name, "brcmaudiodecoder"); +} + +/** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ +bool BrcmSocInterface::IsVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name, "westerossink") || + StartsWith(name, "brcmvideodecoder") ); +} + +bool BrcmSocInterface::ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) +{ + bool status = false; + if ((StartsWith(GST_OBJECT_NAME(src), "brcmaudiosink") == true)) + { + gst_object_replace((GstObject **)audio_sink, src); + status = true; + } + else if(strstr(GST_OBJECT_NAME(src), "brcmaudiodecoder")) + { + // this reduces amount of data in the fifo, which is flushed/lost when transition from expert to normal modes + g_object_set(src, "limit_buffering_ms", 1500, NULL); /* default 500ms was a bit low.. try 1500ms */ + g_object_set(src, "limit_buffering", 1, NULL); + MW_LOG_MIL("Found audiodecoder, limiting audio decoder buffering"); + + /* if mAudioDecoderStreamSync==false, tell decoder not to look for 2nd/next frame sync, decode if it finds a single frame sync */ + g_object_set(src, "stream_sync_mode", (decStreamSync)? 1 : 0, NULL); + MW_LOG_MIL("For audiodecoder set 'stream_sync_mode': %d", decStreamSync); + + } + return status; +} + +/** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ +bool BrcmSocInterface::IsAudioOrVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name, "brcmvideodecoder") || + StartsWith(name, "brcmaudiodecoder") || + StartsWith(name, "westerossink") ); +} + +void BrcmSocInterface::GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec) +{ + if (video_dec) + { + g_object_get(video_dec, "videodecoder", dec_handle, NULL); + } +} + +/** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ +void BrcmSocInterface::SetPlaybackFlags(gint &flags, bool isSub) +{ + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_NATIVE_AUDIO | PLAY_FLAG_NATIVE_VIDEO; + if(isSub) + { + flags = PLAY_FLAG_TEXT; + } +} + diff --git a/middleware/vendor/brcm/BrcmSocInterface.h b/middleware/vendor/brcm/BrcmSocInterface.h new file mode 100644 index 000000000..5af6c48a8 --- /dev/null +++ b/middleware/vendor/brcm/BrcmSocInterface.h @@ -0,0 +1,140 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BRCM_SOC_INTERFACE_H +#define BRCM_SOC_INTERFACE_H + +#include "SocInterface.h" + +/** + * @brief BRCM SoC interface class. + * + * This class provides an implementation of the SocInterface for BRCM SoC. + */ +class BrcmSocInterface : public SocInterface +{ + +public: + BrcmSocInterface(); + + /** + * @brief Check if PTS restamping is supported by the platform. + * + * Determines whether the platform supports PTS restamping. + * + * @return True if PTS restamping is supported, false otherwise. + */ + bool EnablePTSRestamp()override{return true;} + + /** + * @brief Get SoC volume property name. + * @return Volume property name. + */ + void SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume)override; + + /** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ + bool SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) override; + + /** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + */ + GstElement* GetVideoSink(GstElement* sinkbin) override; + + /** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ + void SetAC4Tracks(GstElement *src, int trackId) override{MW_LOG_WARN("AC4 support has not done for this platform - track Id: %d", trackId);} + + /** + * @brief Set platform playback rate. + * @return True on success, false otherwise. + */ + bool SetPlatformPlaybackRate() override{return true;} + + /** + * @brief Set rate correction. + * @return True on success, false otherwise. + */ + bool SetRateCorrection() override {return true;} + + void GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec)override; + /** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ + bool IsVideoSink(const char* name)override; + + /** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ + bool IsAudioSinkOrAudioDecoder(const char* name)override; + + /** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ + bool IsVideoDecoder(const char* name)override; + + /** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ + bool ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync)override; + + /** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ + bool IsAudioOrVideoDecoder(const char* name) override; + + /** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ + void SetPlaybackFlags(gint &flags, bool isSub)override; + + bool IsVideoMaster(GstElement *videoSink)override{return true;} +}; + + +#endif diff --git a/middleware/vendor/default/DefaultSocInterface.cpp b/middleware/vendor/default/DefaultSocInterface.cpp new file mode 100644 index 000000000..0acc9ecfb --- /dev/null +++ b/middleware/vendor/default/DefaultSocInterface.cpp @@ -0,0 +1,264 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DefaultSocInterface.h" + +/** + @brief this interface implementation used with Rialto + */ +DefaultSocInterface::DefaultSocInterface() +{ +} + +/** + * @brief Check if AppSrc should be used for progressive playback. + * + * Determines whether the AppSrc element should be used in the current context. + * + * @return True if AppSrc should be used, false otherwise. + */ +bool DefaultSocInterface::UseAppSrc() +{ +#if defined (__APPLE__) + return true; +#endif + return false; +} + +/** + * @brief Get SoC volume property name. + * @return Volume property name. + */ +void DefaultSocInterface::SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume) +{ + isSinkBinVolume = false; + volume = "volume"; + mute = "mute"; +#if defined(__APPLE__) + isSinkBinVolume = true; +#endif +} + +/** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ +void DefaultSocInterface::SetAC4Tracks(GstElement *src, int trackId) +{ + MW_LOG_INFO("Selecting AC4 Track Id : %d", trackId); + g_object_set(src, "ac4-presentation-group-index", trackId, NULL); +} + +bool DefaultSocInterface::IsVideoSink(const char* name) +{ + return name && ( + StartsWith(name,"rialtomsevideosink") || + StartsWith(name, "westerossink") ); +} + +/** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ +bool DefaultSocInterface::IsVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name,"rialtomsevideosink") || + StartsWith(name, "westerossink") ); +} + +/** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @return True if it's an audio or video decoder, false otherwise. + */ +bool DefaultSocInterface::IsAudioOrVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name,"rialtomsevideosink") || + StartsWith(name,"rialtomseaudiosink") || + StartsWith(name, "westerossink") ); +} + +/** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param noNativeAV Flag indicating whether to disable native AV decoding. + * @param isSub Flag indicating whether the content is a subtitle. + */ +void DefaultSocInterface::SetPlaybackFlags(gint &flags, bool isSub) +{ +#if defined(__APPLE__) + // on OSX, just use working defaults + // note that if PLAY_FLAG_DEINTERLACE is not included, video freezes on first frame +#else + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_SOFT_VOLUME; +#endif + if(isSub) + { + flags = PLAY_FLAG_TEXT; + } +} + +bool DefaultSocInterface::IsSimulatorFirstFrame() +{ + return true; +} + +bool DefaultSocInterface::IsSimulatorSink() +{ +#if !defined(UBUNTU) + return false; +#endif + return true; +} + +void DefaultSocInterface::ConfigurePluginPriority() +{ +#ifdef UBUNTU + GstPluginFeature* pluginFeature = gst_registry_lookup_feature(gst_registry_get(), "pulsesink"); + if (pluginFeature != NULL) + { + MW_LOG_INFO("InterfacePlayerRDK: pulsesink plugin priority set to GST_RANK_SECONDARY"); + gst_plugin_feature_set_rank(pluginFeature, GST_RANK_SECONDARY); + gst_object_unref(pluginFeature); + } +#endif +} + +bool DefaultSocInterface::ShouldTearDownForTrickplay() +{ +#if defined(__APPLE__) || defined(UBUNTU) + return true; +#endif + return false; +} + +bool DefaultSocInterface::IsSimulatorVideoSample() +{ +#if defined(__APPLE__) + return true; +#endif + return true; +} + +void DefaultSocInterface::SetH264Caps(GstCaps *caps) +{ +#ifdef UBUNTU + // below required on Ubuntu - harmless on OSX, but breaks RPI + gst_caps_set_simple (caps, + "alignment", G_TYPE_STRING, "au", + "stream-format", G_TYPE_STRING, "avc", + NULL); +#endif +} + +void DefaultSocInterface::SetHevcCaps(GstCaps *caps) +{ +#ifdef UBUNTU + // below required on Ubuntu - harmless on OSX, but breaks RPI + gst_caps_set_simple(caps, + "alignment", G_TYPE_STRING, "au", + "stream-format", G_TYPE_STRING, "hev1", + NULL); +#endif +} + +/** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ +bool DefaultSocInterface::ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) +{ + bool status = false; + if (StartsWith(GST_OBJECT_NAME(src), "amlhalasink") == true) + { + gst_object_replace((GstObject **)audio_sink, src); + g_object_set(audio_sink, "disable-xrun", TRUE, NULL); + status = true; + } + return status; +} + +/** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ +bool DefaultSocInterface::SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) +{ + #if defined(__APPLE__) || defined(UBUNTU) + return false; + #else + if(!pipeline) + { + MW_LOG_ERR("Failed to set playback rate"); + return false; + } + MW_LOG_MIL("=send custom-instant-rate-change : %f ...", rate); + GstStructure *structure = gst_structure_new("custom-instant-rate-change", "rate", G_TYPE_DOUBLE, rate, NULL); + if(!structure) + { + MW_LOG_ERR("Failed to create custom-instant-rate-change structure"); + return false; + } + /* The above statement creates a new GstStructure with the name + 'custom-instant-rate-change' that has a member variable + 'rate' of G_TYPE_DOUBLE and a value of rate i.e. second last parameter */ + GstEvent * rate_event = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure); + if (!rate_event) + { + MW_LOG_ERR("Failed to create rate_event"); + gst_structure_free (structure); + return false; + } + int ret = gst_element_send_event(pipeline, rate_event ); + if(!ret) + { + MW_LOG_ERR("Rate change failed : %g [gst_element_send_event]", rate); + return false; + } + MW_LOG_MIL("Current rate: %g", rate); + return true; + #endif +} + +bool DefaultSocInterface::IsVideoMaster(GstElement *videoSink) +{ + gboolean isMaster{TRUE}; + GParamSpec *pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(videoSink),"is-master"); + if( pspec!=NULL ) + { // rialto-specific + g_object_get(videoSink, "is-master", &isMaster, nullptr); + MW_LOG_INFO("is-master %d", isMaster); + } + return (isMaster == TRUE); +} diff --git a/middleware/vendor/default/DefaultSocInterface.h b/middleware/vendor/default/DefaultSocInterface.h new file mode 100644 index 000000000..29a1aa0ff --- /dev/null +++ b/middleware/vendor/default/DefaultSocInterface.h @@ -0,0 +1,172 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DEFAULT_SOC_INTERFACE_H +#define DEFAULT_SOC_INTERFACE_H + +#include "SocInterface.h" + +/** + * @brief Default SoC interface class. + * + * This class provides an implementation of the APPLE/UBUNTU/RPI gstreamer code. + */ +class DefaultSocInterface : public SocInterface +{ + +public: + DefaultSocInterface(); + + /** + * @brief Check if AppSrc should be used. + * + * Determines whether the AppSrc element should be used in the current context. + * + * @return True if AppSrc should be used, false otherwise. + */ + bool UseAppSrc()override; + + /** + * @brief Check if Westeros sink should be used. + * + * Determines whether the Westeros sink should be used in the current context. + * + * @return True if Westeros sink should be used, false otherwise. + */ + bool UseWesterosSink()override{return false;} + + /** + * @brief Get volume property name. + * @return Volume property name. + */ + void SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume)override; + + /** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ + bool SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) override; + + /** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ + void SetAC4Tracks(GstElement *src, int trackId) override; + + /** + * @brief Set rate correction. + * @return True on success, false otherwise. + */ + bool SetRateCorrection() override {return false;} + + void GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec)override{}; + + /** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ + bool IsVideoSink(const char* name) override; + + /** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ + bool IsAudioSinkOrAudioDecoder(const char* name) override {return false;} + + /** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ + bool IsVideoDecoder(const char* name) override; + + /** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ + bool ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync)override; + + /** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ + bool IsAudioOrVideoDecoder(const char* name) override; + + /** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ + void SetPlaybackFlags(gint &flags, bool isSub)override; + + /** + * @brief checks if the firstFrame is received from the simulator + */ + bool IsSimulatorFirstFrame()override; + + /** + * @brief checks if the sink is from the simulator + */ + bool IsSimulatorSink()override; + + /** + * @brief Configure the plugin priority for PulseAudio. + */ + void ConfigurePluginPriority()override; + + /** + * @brief checks if the teardown is required for simulator + */ + bool ShouldTearDownForTrickplay()override; + + /** + * @brief checks if the video sample is from the simulator + */ + bool IsSimulatorVideoSample()override; + + /** + * @brief checks if the video sample is from the simulator + */ + void SetH264Caps(GstCaps *caps)override; + + /** + *@brief Sets the HEVC caps for simulator + */ + void SetHevcCaps(GstCaps *caps)override; + + bool IsVideoMaster(GstElement *videoSink)override; +}; + +#endif diff --git a/middleware/vendor/realtek/RealtekSocInterface.cpp b/middleware/vendor/realtek/RealtekSocInterface.cpp new file mode 100644 index 000000000..d4c0af757 --- /dev/null +++ b/middleware/vendor/realtek/RealtekSocInterface.cpp @@ -0,0 +1,355 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "RealtekSocInterface.h" + +#define DEFAULT_AVSYNC_FREERUN_THRESHOLD_SECS 12 /**< Currently MAX FRAG DURATION + 2 per Realtek */ + +RealtekSocInterface::RealtekSocInterface() +{ +} + +/** + * Sets buffer size and duration for the given GstElement. + * + * @param sink The GstElement to configure. + * @param size The desired buffer size. + */ +void RealtekSocInterface::SetVideoBufferSize(GstElement *sink, int size) +{ + g_object_set(sink, "buffer-size", (guint64)size, NULL); + g_object_set(sink, "buffer-duration", 3000000000, NULL); //3000000000(ns), 3s +} + +/** + * Sets asynchronous mode for the given Sink. + * + * @param sink element. + * @param status Enable (TRUE) or disable (FALSE) asynchronous mode. + */ +void RealtekSocInterface::SetSinkAsync(GstElement *sink, gboolean status) +{ + gst_base_sink_set_async_enabled(GST_BASE_SINK(sink), status); +} + +/** + * @brief Get SoC volume property name. + * @return Volume property name. + */ +void RealtekSocInterface::SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume) +{ + volume = "volume"; + mute = "mute"; + isSinkBinVolume = true; /*volume/mute property should be applied on sinkbin*/ +} + +/** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ +bool RealtekSocInterface::SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) +{ + //SOC specific code will be executed for rialto sink at default soc + if(!pipeline) + { + MW_LOG_ERR("Failed to set playback rate"); + return false; + } + MW_LOG_MIL("=send custom-instant-rate-change : %f ...", rate); + GstStructure *structure = gst_structure_new("custom-instant-rate-change", "rate", G_TYPE_DOUBLE, rate, NULL); + if(!structure) + { + MW_LOG_ERR("Failed to create custom-instant-rate-change structure"); + return false; + } + /* The above statement creates a new GstStructure with the name + 'custom-instant-rate-change' that has a member variable + 'rate' of G_TYPE_DOUBLE and a value of rate i.e. second last parameter */ + GstEvent * rate_event = gst_event_new_custom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure); + if (!rate_event) + { + MW_LOG_ERR("Failed to create rate_event"); + gst_structure_free (structure); + return false; + } + int ret = gst_element_send_event(pipeline, rate_event ); + if(!ret) + { + MW_LOG_ERR("Rate change failed : %g [gst_element_send_event]", rate); + return false; + } + MW_LOG_MIL("Current rate: %g", rate); + return true; +} + +/** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ +void RealtekSocInterface::SetAC4Tracks(GstElement *src, int trackId) +{ + MW_LOG_INFO("Selecting AC4 Track Id : %d", trackId); + if(src) + { + g_object_set(src, "ac4-presentation-group-index", trackId, NULL); + } + else + { + MW_LOG_ERR("No valid src to set ac4-presentation-group-index"); + } +} + +/** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ +bool RealtekSocInterface::IsVideoSink(const char* name) +{ + return name && ( + StartsWith(name, "westerossink") || + StartsWith(name, "rtkv1sink") ); +} + +/** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ +bool RealtekSocInterface::IsAudioSinkOrAudioDecoder(const char* name) +{ + return name && ( + StartsWith(name, "rtkaudiosink") || + StartsWith(name, "alsasink") || + StartsWith(name, "fakesink") ); +} + +/** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ +bool RealtekSocInterface::IsVideoDecoder(const char* name) +{ + return name && ( + StartsWith(name, "omxwmvdec") || + StartsWith(name, "omxh26") || + StartsWith(name, "omxav1dec") || + StartsWith(name, "omxvp") || + StartsWith(name, "omxmpeg") ); +} + +/** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ +bool RealtekSocInterface::ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync) +{ + if (!audio_sink || !src) + { + MW_LOG_ERR("ConfigureAudioSink: Invalid input parameters"); + return false; + } + + bool status = false; + const char* srcName = GST_OBJECT_NAME(src); + + if (srcName && (StartsWith(srcName, "rtkaudiosink") + || StartsWith(srcName, "alsasink") + || StartsWith(srcName, "fakesink"))) + { + gst_object_replace(reinterpret_cast(audio_sink), src); + status = true; + } + + return status; +} + +/** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ +bool RealtekSocInterface::IsAudioOrVideoDecoder(const char* name) +{ + return name && StartsWith(name, "omx"); +} + +/** + * @brief Disable asynchronous audio. + * @param audio_sink Audio sink element. + * @param rate Playback rate. + * @param isSeeking True if seeking is in progress, false otherwise. + * @return True if async changed from enabled to disabled, false otherwise. + */ +bool RealtekSocInterface::DisableAsyncAudio(GstElement *audio_sink, int rate, bool isSeeking) +{ + bool bAsyncModify = false; + if (audio_sink) + { + if (rate > 1 || rate < 0 || isSeeking) + { + MW_LOG_MIL("Disable async for audio stream at trickplay"); + if (gst_base_sink_is_async_enabled(GST_BASE_SINK(audio_sink)) == TRUE) + { + gst_base_sink_set_async_enabled(GST_BASE_SINK(audio_sink), FALSE); + bAsyncModify = true; + } + } + } + return bAsyncModify; +} + +/** + * Gets the decoder handle from the video decoder element. + * + * @param dec_handle Pointer to store the decoder handle. + * @param video_dec The video decoder element. + */ +void RealtekSocInterface::GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec) +{ + *dec_handle = video_dec; // Realtek directly returns the decoder element +} + +/** + * @brief Get video PTS. + * + * Retrieves the current video presentation timestamp (PTS). + * + * @param video_sink The video sink element. + * @param video_dec The video decoder element. + * @param isWesteros A flag for Westeros logic. + * + * @return Video PTS in nanoseconds, or -1 on error. + */ +long long RealtekSocInterface::GetVideoPts(GstElement *video_sink, GstElement *video_dec, bool isWesteros) +{ + gint64 currentPTS = 0; + GstElement *element = nullptr; + if(video_sink) + { + element = video_sink; + } + + if (element) + { + g_object_get(element, "video-pts", ¤tPTS, NULL); /* Gets the 'video-pts' from the element into the currentPTS */ + } + return (long long)currentPTS; +} + +/** + * @brief Set freerun threshold on source. + * + * Sets the freerun threshold on the given source object. + * + * @param src The source object. + */ +void RealtekSocInterface::SetFreerunThreshold(GstObject* src) +{ + if(src) + { + g_object_set(src, "freerun-threshold", DEFAULT_AVSYNC_FREERUN_THRESHOLD_SECS, NULL); + } +} + +/** + * @brief Set audio routing properties on source. + * + * Sets audio routing properties on the given source element. + * + * @param source The source element. + */ +void RealtekSocInterface::SetAudioRoutingProperties(GstElement *source) +{ + if ((strstr(GST_ELEMENT_NAME(source), "omxaacdec") != NULL) || + (strstr(GST_ELEMENT_NAME(source), "omxac3dec") != NULL) || + (strstr(GST_ELEMENT_NAME(source), "omxeac3dec") != NULL) || + (strstr(GST_ELEMENT_NAME(source), "omxmp3dec") != NULL) || + (strstr(GST_ELEMENT_NAME(source), "omxvorbisdec") != NULL) || + (strstr(GST_ELEMENT_NAME(source), "omxac4dec") != NULL)) + { + g_object_set(source, "audio-tunnel-mode", FALSE, NULL ); + MW_LOG_INFO("callback_element_added audio-tunnel-mode FALSE"); + g_object_set(source, "aux-audio", TRUE, NULL ); + MW_LOG_INFO("callback_element_added aux-audio TRUE"); + } +} + +/** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ +void RealtekSocInterface::SetPlaybackFlags(gint &flags, bool isSub) +{ + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_NATIVE_AUDIO | PLAY_FLAG_NATIVE_VIDEO; + + flags = PLAY_FLAG_VIDEO | PLAY_FLAG_AUDIO | PLAY_FLAG_NATIVE_AUDIO | PLAY_FLAG_NATIVE_VIDEO | PLAY_FLAG_SOFT_VOLUME; + if(isSub) + { + flags = PLAY_FLAG_TEXT; + } +} + +/** + *@brief Sets the platform specific H264 caps + */ +void RealtekSocInterface::SetH264Caps(GstCaps *caps) +{ + gst_caps_set_simple (caps, "enable-fastplayback", G_TYPE_STRING, "true", NULL); +} + +/** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + */ +GstElement* RealtekSocInterface::GetVideoSink(GstElement* sinkbin) +{ + GstElement* vidsink = nullptr; + if(!sinkbin) + { + MW_LOG_ERR("Invalid SinkBin"); + } + else if(mUsingWesterosSink) + { + MW_LOG_INFO("using westerossink"); + vidsink = gst_element_factory_make("westerossink", NULL); + g_object_set(sinkbin, "video-sink", vidsink, NULL); + } + return vidsink; +} + +void RealtekSocInterface::SetHevcCaps(GstCaps *caps) +{ + gst_caps_set_simple (caps, "enable-fastplayback", G_TYPE_STRING, "true", NULL); +} diff --git a/middleware/vendor/realtek/RealtekSocInterface.h b/middleware/vendor/realtek/RealtekSocInterface.h new file mode 100644 index 000000000..50f2a651c --- /dev/null +++ b/middleware/vendor/realtek/RealtekSocInterface.h @@ -0,0 +1,291 @@ +/* + * If not stated otherwise in this file or this component's license file the + * following copyright and licenses apply: + * + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef REALTEK_SOC_INTERFACE_H +#define REALTEK_SOC_INTERFACE_H + +#include "SocInterface.h" + +#define REQUIRED_QUEUED_FRAMES_REALTEK (3+1) +/** + * @brief Realtek SoC interface class. + * + * This class provides an implementation of the SocInterface for Realtek SoC. + */ +class RealtekSocInterface : public SocInterface +{ + + public: + RealtekSocInterface(); + + + /** + * @brief Check if audio fragments should be synchronized. + * + * Determines whether audio fragments should be synchronized. + * + * @return True if audio fragments should be synchronized, false otherwise. + */ + bool IsAudioFragmentSyncSupported()override{return true;} + + /*@brief returns true if video stats required from sink otherwise false*/ + bool IsPlaybackQualityFromSink() override {return true;} + + /** + * @brief Get video sink from sinkbin. + * @param sinkbin The GStreamer sinkbin. + */ + + GstElement* GetVideoSink(GstElement* sinkbin)override; + + + /** + * @brief Check if live latency correction should be enabled. + * + * Determines whether live latency correction should be enabled. + * + * @return True if live latency correction should be enabled, false otherwise. + */ + bool EnableLiveLatencyCorrection()override{return true;} + + /** + * @brief Get the required number of queued frames. + * + * Returns the total number of frames that should be queued. + * + * @return The required number of queued frames. + */ + int RequiredQueuedFrames()override{return REQUIRED_QUEUED_FRAMES_REALTEK;} + + /** + * Sets buffer size and duration for the given GstElement. + * + * @param sink The GstElement to configure. + * @param size The desired buffer size. + */ + void SetVideoBufferSize(GstElement *sink, int size)override; + + /** + * Sets asynchronous mode for the given Sink. + * + * @param sink element. + * @param status Enable (TRUE) or disable (FALSE) asynchronous mode. + */ + void SetSinkAsync(GstElement *sink, gboolean status)override; + + /** + * Checks if this is the first tune with Westeros disabled. + * + * @return True if this is the first tune with Westeros disabled for the current platform, false otherwise. + */ + bool IsFirstTuneWithWesteros()override{return true;} + + /** + * @brief Get SoC volume property name. + * @return Volume property name. + */ + void SetAudioProperty(const char * &volume, const char * &mute, bool& isSinkBinVolume)override; + + /** + * @brief Sets the playback rate for the given GStreamer elements. + * + * @param sources A vector of GStreamer source elements. + * @param pipeline The main GStreamer pipeline. + * @param rate The desired playback rate. + * @param video_dec The video decoder element. + * @param audio_dec The audio decoder element. + * @return True if the playback rate was set successfully, false otherwise. + */ + bool SetPlaybackRate(const std::vector& sources, GstElement *pipeline, double rate, GstElement *video_dec, GstElement *audio_dec) override; + + /** + * @brief Set AC4 tracks. + * @param src Source element. + * @param trackId Track ID. + */ + void SetAC4Tracks(GstElement *src, int trackId) override; + + /** + * @brief Set platform playback rate. + * @return True on success, false otherwise. + */ + bool SetPlatformPlaybackRate() override{return true;} + + /** + * @brief Set rate correction. + * @return True on success, false otherwise. + */ + bool SetRateCorrection() override {return false;} + + /** + * @brief Check if the given name is a video sink. + * @param name Element name. + * @return True if it's a video sink, false otherwise. + */ + bool IsVideoSink(const char* name)override; + + /** + * @brief Check if the given name is an audio sink or audio decoder. + * @param name Element name. + * @return True if it's an audio sink or audio decoder, false otherwise. + */ + bool IsAudioSinkOrAudioDecoder(const char* name)override; + + /** + * @brief Check if the given name is a video decoder. + * @param name Element name. + * @return True if it's a video decoder, false otherwise. + */ + bool IsVideoDecoder(const char* name)override; + + /** + * @brief Configure the audio sink. + * @param audio_sink Pointer to the audio sink element. + * @param src Source object. + * @param decStreamSync Decoder stream synchronization flag. + * @return True on success, false otherwise. + */ + bool ConfigureAudioSink(GstElement **audio_sink, GstObject *src, bool decStreamSync)override; + + /** + * @brief Check if the given name is an audio or video decoder. + * @param name Element name. + * @param IsWesteros Westeros flag. + * @return True if it's an audio or video decoder, false otherwise. + */ + bool IsAudioOrVideoDecoder(const char* name)override; + + /** + * @brief Disable asynchronous audio. + * @param audio_sink Audio sink element. + * @param rate Playback rate. + * @param isSeeking True if seeking is in progress, false otherwise. + * @return True if async changed from enabled to disabled, false otherwise. + */ + bool DisableAsyncAudio(GstElement *audio_sink, int rate, bool isSeeking)override; + + /** + * Gets the decoder handle from the video decoder element. + * + * @param dec_handle Pointer to store the decoder handle. + * @param video_dec The video decoder element. + */ + void GetCCDecoderHandle(gpointer *dec_handle, GstElement *video_dec)override; + + /** + * @brief Resets the trick play UTC. + * + * @return True if the reset is required, false otherwise. + */ + bool ResetTrickUTC()override{return true;} + + /** + * @brief Get video PTS. + * + * Retrieves the current video presentation timestamp (PTS). + * + * @param video_sink The video sink element. + * @param video_dec The video decoder element. + * @param isWesteros A flag for Westeros logic. + * + * @return Video PTS in nanoseconds, or -1 on error. + */ + long long GetVideoPts(GstElement *video_sink, GstElement *video_dec, bool isWesteros)override; + + /** + * @brief Notify first video frame. + */ + bool NotifyVideoFirstFrame()override{return true;} + + /** + * @brief Set decode error on source. + * + * Sets a decode error flag on the given source object. + * + * @param src The source object. + */ + void SetDecodeError(GstObject* src)override{} + + /** + * @brief Set freerun threshold on source. + * + * Sets the freerun threshold on the given source object. + * + * @param src The source object. + */ + void SetFreerunThreshold(GstObject* src)override; + + /** + * @brief Check if element setup is required. + * + * Determines if the element requires setup before it can be used. + * + * @return True if setup is required, false otherwise. + */ + bool RequiredElementSetup()override{return true;} + + /** + * @brief Set audio routing properties on source. + * + * Sets audio routing properties on the given source element. + * + * @param source The source element. + */ + void SetAudioRoutingProperties(GstElement *source)override; + + /** + * @brief Check if first audio frame callback is set. + * + * Determines if a callback function has been set for the first audio frame. + * + * @return True if a callback is set, false otherwise. + */ + bool HasFirstAudioFrameCallback()override{return false;} + + /** + * @brief Check if video sink errors are handled. + * + * Determines if the platform handles errors from the video sink. + * + * @return True if video sink errors are handled, false otherwise. + */ + bool IsVideoSinkHandleErrors()override{return true;} + + /** + * @brief Set playback flags. + * + * Sets the playback flags based on the given parameters. + * @param flags Reference to the flags integer. + * @param isSub Flag indicating whether the content is a subtitle. + */ + void SetPlaybackFlags(gint &flags, bool isSub)override; + + /** + *@brief Sets the platform specific H264 caps + */ + void SetH264Caps(GstCaps *caps)override; + + /** + *@brief Sets the HEVC caps + */ + void SetHevcCaps(GstCaps *caps)override; + + bool IsVideoMaster(GstElement *videoSink)override{return true;} +}; +#endif