diff --git a/CMakeLists.txt b/CMakeLists.txt index 86e8ed66..3215347e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,6 +402,13 @@ if (CMAKE_SUBTITLE_SUPPORT) set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} closedcaptions/subtec/PlayerSubtecCCManager.cpp closedcaptions/rialto/PlayerRialtoCCManager.cpp) endif() + +if (CMAKE_PLAYER_TELEMETRY_SUPPORT) + message("CMAKE_PLAYER_TELEMETRY_SUPPORT set") + set(LIBPLAYERGSTINTERFACE_DEFINES "${LIBPLAYERGSTINTERFACE_DEFINES} -DPLAYER_TELEMETRY_SUPPORT") + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/playerTelemetry) + add_subdirectory(playerTelemetry) +endif() add_library(playergstinterface SHARED ${SOURCES} ${LIBPLAYERGSTINTERFACE_HEADERS} ${LIBPLAYERGSTINTERFACE_SOURCES} ${LIBPLAYERGSTINTERFACE_DRM_SOURCES} ${LIBPLAYERGSTINTERFACE_HELP_SOURCES}) target_include_directories(playergstinterface PUBLIC @@ -459,6 +466,9 @@ target_link_libraries(playergstinterface ${GSTVIDEO_LIBRARIES} ${LIBPLAYERGSTINT target_link_libraries(playergstinterface subtec) target_link_libraries(playergstinterface baseconversion) target_link_libraries(playergstinterface playerfbinterface) +if (CMAKE_PLAYER_TELEMETRY_SUPPORT) + target_link_libraries(playergstinterface playertelemetry) +endif() set(LIBPLAYERGSTINTERFACE_SOURCES ${LIBPLAYERGSTINTERFACE_SOURCES} {LIBPLAYERGSTINTERFACE_MOCK_SOURCES}) # Optionally build pi-cli for l2 diff --git a/pkgconfig/libplayertelemetry.pc.in b/pkgconfig/libplayertelemetry.pc.in new file mode 100644 index 00000000..3edf4ae0 --- /dev/null +++ b/pkgconfig/libplayertelemetry.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: playertelemetry +Description: Telemetry 2.0 support for player middleware +Version: @PROJECT_VERSION@ +Requires: libcjson +Requires.private: playerlogmanager +Libs: -L${libdir} -lplayertelemetry +Cflags: -I${includedir} diff --git a/playerTelemetry/CMakeLists.txt b/playerTelemetry/CMakeLists.txt new file mode 100644 index 00000000..92d6468d --- /dev/null +++ b/playerTelemetry/CMakeLists.txt @@ -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. + +cmake_minimum_required(VERSION 3.5) + +project(playertelemetry VERSION 0.1.0) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set default install prefix +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Default install prefix" FORCE) +endif() + +include(FindPkgConfig) +pkg_check_modules(LIBCJSON REQUIRED libcjson) +link_directories(${LIBCJSON_LIBRARY_DIRS}) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../playerLogManager) +include_directories(${LIBCJSON_INCLUDE_DIRS}) + +set(PlayerTelemetry_SRC PlayerTelemetry2.cpp) + +add_library(playertelemetry SHARED ${PlayerTelemetry_SRC}) + +target_compile_definitions(playertelemetry PUBLIC PLAYER_TELEMETRY_SUPPORT) + +target_include_directories(playertelemetry PUBLIC + ${LIBCJSON_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/../playerLogManager +) + +target_link_libraries(playertelemetry PUBLIC + ${LIBCJSON_LINK_LIBRARIES} + playerlogmanager +) + +set_target_properties(playertelemetry PROPERTIES + PUBLIC_HEADER "PlayerTelemetry2.hpp" +) + +# Configure pkg-config file +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/../pkgconfig/libplayertelemetry.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/libplayertelemetry.pc" + @ONLY +) + +install(TARGETS playertelemetry + DESTINATION lib + PUBLIC_HEADER DESTINATION include +) + +# Install pkg-config file +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libplayertelemetry.pc" + DESTINATION lib/pkgconfig) diff --git a/playerTelemetry/PlayerTelemetry2.cpp b/playerTelemetry/PlayerTelemetry2.cpp new file mode 100644 index 00000000..1eea5a42 --- /dev/null +++ b/playerTelemetry/PlayerTelemetry2.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 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 PlayerTelemetry2.cpp + * @brief PlayerTelemetry2 class implementation + */ + +#include "PlayerTelemetry2.hpp" + +#ifdef PLAYER_TELEMETRY_SUPPORT + +#ifndef PLAYER_SIMULATOR_BUILD +#include +#endif + + +PlayerTelemetryInitializer::PlayerTelemetryInitializer() +: m_Initialized(false) +{ +} + +void PlayerTelemetryInitializer::Init() +{ + if (false == m_Initialized) + { + m_Initialized = true; +#ifndef PLAYER_SIMULATOR_BUILD + t2_init((char *)"player"); + MW_LOG_MIL("t2_init done"); +#endif + } +} + +bool PlayerTelemetryInitializer::isInitialized() const +{ + return m_Initialized; +} + +PlayerTelemetryInitializer::~PlayerTelemetryInitializer() +{ +#ifndef PLAYER_SIMULATOR_BUILD + t2_uninit(); + MW_LOG_MIL("t2_uninit done"); +#endif +} + + +PlayerTelemetryInitializer PlayerTelemetry2::mInitializer; + +PlayerTelemetry2::PlayerTelemetry2() : PlayerTelemetry2("") +{ +} + +PlayerTelemetry2::PlayerTelemetry2(const std::string &appName) : appName(appName) +{ + mInitializer.Init(); +} + +bool PlayerTelemetry2::send(const std::string &markerName, + const std::map& intData, + const std::map& stringData, + const std::map& floatData) +{ + bool bRet = false; + if (mInitializer.isInitialized()) + { + cJSON *root = cJSON_CreateObject(); + + cJSON_AddStringToObject(root, "app", appName.c_str()); + + for (const auto& pair : intData) + { + cJSON_AddNumberToObject(root, pair.first.c_str(), pair.second); + } + + for (const auto& pair : stringData) + { + cJSON_AddStringToObject(root, pair.first.c_str(), pair.second.c_str()); + } + + for (const auto& pair : floatData) + { + cJSON_AddNumberToObject(root, pair.first.c_str(), pair.second); + } + + char *jsonString = cJSON_PrintUnformatted(root); + + MW_LOG_INFO("[M] Marker Name: %s value:%s", markerName.c_str(), jsonString); + +#ifndef PLAYER_SIMULATOR_BUILD + T2ERROR t2Error = t2_event_s((char *)markerName.c_str(), jsonString); + + if (T2ERROR_SUCCESS == t2Error) + { + bRet = true; + } + else + { + MW_LOG_ERR("t2_event_s map failed:%d", t2Error); + } +#else + bRet = true; +#endif + cJSON_free(jsonString); + cJSON_Delete(root); + } + + return bRet; +} + +bool PlayerTelemetry2::send(const std::string &markerName, const char *data) +{ + bool bRet = false; + if (mInitializer.isInitialized() && nullptr != data) + { +#ifndef PLAYER_SIMULATOR_BUILD + T2ERROR t2Error = t2_event_s((char *)markerName.c_str(), (char *)data); + + if (T2ERROR_SUCCESS == t2Error) + { + bRet = true; + } + else + { + MW_LOG_ERR("t2_event_s string failed:%d", t2Error); + } +#else + bRet = true; +#endif + } + + return bRet; +} + +#endif // PLAYER_TELEMETRY_SUPPORT diff --git a/playerTelemetry/PlayerTelemetry2.hpp b/playerTelemetry/PlayerTelemetry2.hpp new file mode 100644 index 00000000..e2e8f2d1 --- /dev/null +++ b/playerTelemetry/PlayerTelemetry2.hpp @@ -0,0 +1,119 @@ +/* + * 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 PlayerTelemetry2.hpp + * @brief Supporting class to provide telemetry support to Player middleware + */ + +#ifndef __PLAYER_TELEMETRY_2_H__ +#define __PLAYER_TELEMETRY_2_H__ + +#ifdef PLAYER_TELEMETRY_SUPPORT + +#include +#include +#include +#include +#include "PlayerLogManager.h" + +// Note that RDK telemetry 2.0 support is per process basis, +// this class is created to take care of un initialization of telemetry but having object as global variable +// when process goes down, destructor of this class will be called and it will uninitialize the telemetry. + +/** + * @class PlayerTelemetryInitializer + * @brief Manages the lifecycle of the T2 telemetry library initialization + */ +class PlayerTelemetryInitializer { +private: + bool m_Initialized = false; +public: + /** + * @brief Constructor + */ + PlayerTelemetryInitializer(); + + /** + * @brief Initialize the telemetry library (idempotent) + */ + void Init(); + + /** + * @brief Check if telemetry has been initialized + * @return true if initialized, false otherwise + */ + bool isInitialized() const; + + /** + * @brief Destructor - uninitializes the telemetry library + */ + ~PlayerTelemetryInitializer(); +}; + + +/** + * @class PlayerTelemetry2 + * @brief Provides object-based telemetry event sending via RDK T2 (Telemetry 2.0) + * + * Instead of calling telemetry send functions directly, create a PlayerTelemetry2 + * object and call send() to dispatch telemetry events. + */ +class PlayerTelemetry2 { +private: + static PlayerTelemetryInitializer mInitializer; + std::string appName; + +public: + /** + * @brief Constructor with no application name + */ + PlayerTelemetry2(); + + /** + * @brief Constructor + * @param[in] appName - Name of the application + */ + PlayerTelemetry2(const std::string &appName); + + /** + * @brief Send telemetry data to the telemetry bus by converting input maps to a JSON string + * @param[in] markerName - Name of the marker/event + * @param[in] intData - Map of integer key-value pairs + * @param[in] stringData - Map of string key-value pairs + * @param[in] floatData - Map of float key-value pairs + * @return true if the event was sent successfully, false otherwise + */ + bool send(const std::string &markerName, + const std::map& intData, + const std::map& stringData, + const std::map& floatData); + + /** + * @brief Send telemetry data to the telemetry bus + * @param[in] markerName - Name of the marker/event + * @param[in] data - Raw data string to send + * @return true if the event was sent successfully, false otherwise + */ + bool send(const std::string &markerName, const char *data); +}; + +#endif // PLAYER_TELEMETRY_SUPPORT + +#endif // __PLAYER_TELEMETRY_2_H__ diff --git a/test/utests/tests/CMakeLists.txt b/test/utests/tests/CMakeLists.txt index fcfdd245..31aea046 100644 --- a/test/utests/tests/CMakeLists.txt +++ b/test/utests/tests/CMakeLists.txt @@ -32,3 +32,4 @@ add_subdirectory(InterfacePlayerTests) add_subdirectory(DrmSessionManagerTests) add_subdirectory(PlayerUtilsTests) add_subdirectory(PlayerExternalsRdkTests) +add_subdirectory(PlayerTelemetryTests) diff --git a/test/utests/tests/PlayerTelemetryTests/CMakeLists.txt b/test/utests/tests/PlayerTelemetryTests/CMakeLists.txt new file mode 100644 index 00000000..11e72063 --- /dev/null +++ b/test/utests/tests/PlayerTelemetryTests/CMakeLists.txt @@ -0,0 +1,60 @@ +# 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 PlayerTelemetryTests) + +include_directories(${PLAYER_ROOT}/playerTelemetry) +include_directories(${PLAYER_ROOT}/playerLogManager) +include_directories(${LIBCJSON_INCLUDE_DIRS}) +include_directories(${GTEST_INCLUDE_DIRS}) +include_directories(${GMOCK_INCLUDE_DIRS}) + +set(TEST_SOURCES + TelemetryTests.cpp +) + +set(PLAYER_SOURCES + ${PLAYER_ROOT}/playerTelemetry/PlayerTelemetry2.cpp + ${PLAYER_ROOT}/playerLogManager/PlayerLogManager.cpp +) + +add_definitions(-DPLAYER_SIMULATOR_BUILD) +add_definitions(-DPLAYER_TELEMETRY_SUPPORT) + +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} ${LIBCJSON_LINK_LIBRARIES} ${GMOCK_LINK_LIBRARIES} ${GTEST_LINK_LIBRARIES}) + +player_utest_run_add(${EXEC_NAME}) diff --git a/test/utests/tests/PlayerTelemetryTests/TelemetryTests.cpp b/test/utests/tests/PlayerTelemetryTests/TelemetryTests.cpp new file mode 100644 index 00000000..4df51e8f --- /dev/null +++ b/test/utests/tests/PlayerTelemetryTests/TelemetryTests.cpp @@ -0,0 +1,115 @@ +/* + * 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 "PlayerTelemetry2.hpp" + +#ifdef PLAYER_TELEMETRY_SUPPORT + +class PlayerTelemetryTest : public ::testing::Test { +protected: + + void SetUp() override { + } + + void TearDown() override { + } + +public: + PlayerTelemetry2 telemetry; +}; + + +TEST_F(PlayerTelemetryTest, Send_StringData) +{ + std::string markername = "VideoStartTime"; + std::string data = "{\"type\":\"IP_PLAYER_TUNETIME\",\"ver\":5,\"bld\":\"1.0\"}"; + EXPECT_EQ(true, telemetry.send(markername, data.c_str())); +} + +TEST_F(PlayerTelemetryTest, Send_NullData) +{ + std::string markername = "VideoStartTime"; + EXPECT_EQ(false, telemetry.send(markername, nullptr)); +} + +TEST_F(PlayerTelemetryTest, Send_MapData_VideoStartFailure) +{ + std::string markername = "VideoStartFailure"; + std::map intData; + intData["err"] = 10; + intData["cat"] = 10; + intData["cls"] = 0; + std::map stringData; + stringData["desc"] = "manifest request failed"; + std::map floatData; + floatData["pos"] = 0.0f; + EXPECT_EQ(true, telemetry.send(markername, intData, stringData, floatData)); +} + +TEST_F(PlayerTelemetryTest, Send_MapData_VideoBufferingStart) +{ + std::string markername = "VideoBufferingStart"; + std::map intData; + intData["buffer"] = 1; + std::map stringData; + stringData["type"] = "underrun"; + std::map floatData; + floatData["pos"] = 10.5f; + EXPECT_EQ(true, telemetry.send(markername, intData, stringData, floatData)); +} + +TEST_F(PlayerTelemetryTest, Send_MapData_VideoBufferingEnd) +{ + std::string markername = "VideoBufferingEnd"; + std::map intData; + intData["buffer"] = 0; + std::map stringData; + stringData["type"] = "underrun"; + std::map floatData; + floatData["dur"] = 2.1f; + EXPECT_EQ(true, telemetry.send(markername, intData, stringData, floatData)); +} + +TEST_F(PlayerTelemetryTest, Send_MapData_VideoBitrateChange) +{ + std::string markername = "VideoBitrateChange"; + std::map intData; + intData["bit"] = 1650064; + intData["wdh"] = 640; + intData["hth"] = 266; + std::map stringData; + stringData["desc"] = "Reset to default bitrate due to tune"; + std::map floatData; + floatData["frt"] = 25.0f; + floatData["pos"] = 102.0f; + EXPECT_EQ(true, telemetry.send(markername, intData, stringData, floatData)); +} + +TEST_F(PlayerTelemetryTest, Send_MapData_EmptyMaps) +{ + std::string markername = "VideoPlaybackSuccess"; + std::map intData; + std::map stringData; + std::map floatData; + EXPECT_EQ(true, telemetry.send(markername, intData, stringData, floatData)); +} + +#endif // PLAYER_TELEMETRY_SUPPORT