diff --git a/.github/workflows/native_rialto_build.yml b/.github/workflows/native_rialto_build.yml index 7ee8cf52a..580779f6c 100644 --- a/.github/workflows/native_rialto_build.yml +++ b/.github/workflows/native_rialto_build.yml @@ -36,25 +36,11 @@ jobs: - name: Install Dependencies run: | - sudo apt-get update - sudo apt-get install build-essential - sudo apt-get install cmake - sudo apt-get install libunwind-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev libgstreamer1.0-dev - - - name: Install protobuf - run: | - sudo apt-get install protobuf-compiler + sudo sh -x install_dependencies_for_native_build.sh - name: Build Rialto run: | - cmake . -B build -DNATIVE_BUILD=ON -DRIALTO_BUILD_TYPE="Debug" &> output_file.txt - if [ $? -eq 0 ] - then - make -C build &>> output_file.txt - else - exit 1 - fi - + sh -x build_native.sh &> output_file.txt - name: Report Build Status Success if: success() diff --git a/CMakeLists.txt b/CMakeLists.txt index eb66410b7..04c239071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,7 @@ endif() # Retrieve release tag execute_process( - COMMAND bash -c "git tag --points-at ${SRCREV} | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'" + COMMAND bash -c "git tag --points-at ${SRCREV} | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-r[0-9]+)?$'" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE TAGS OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/build_native.sh b/build_native.sh new file mode 100644 index 000000000..bcd2870c9 --- /dev/null +++ b/build_native.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 Sky UK +# +# 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. +# + +# Script for building the Rialto Native. + +cmake . -B build -DNATIVE_BUILD=ON -DRIALTO_BUILD_TYPE="Debug" +if [ $? -eq 0 ] +then + make -C build -j$(nproc) +else + exit 1 +fi \ No newline at end of file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 42d7a115f..58fd49fa0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -31,6 +31,7 @@ add_library( source/EventThread.cpp source/LinuxUtils.cpp source/Timer.cpp + source/Profiler.cpp ) set_property ( diff --git a/common/include/Profiler.h b/common/include/Profiler.h new file mode 100644 index 000000000..c45a1653c --- /dev/null +++ b/common/include/Profiler.h @@ -0,0 +1,69 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_COMMON_PROFILER_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_H_ + +#include "IProfiler.h" + +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerFactory : public IProfilerFactory +{ +public: + std::unique_ptr createProfiler(std::string moduleName) const override; +}; + +class Profiler final : public IProfiler +{ +public: + explicit Profiler(std::string module); + + bool isEnabled() const noexcept override; + + std::optional record(const std::string &stage) override; + std::optional record(const std::string &stage, const std::string &info) override; + + void log(const RecordId id) override; + + bool dumpToFile() const override; + std::vector getRecords() const override; + +private: + std::optional findById(RecordId id) const; + + std::string m_module; + bool m_enabled; + std::optional m_dumpFileName; + + mutable std::mutex m_mutex; + RecordId m_id{1}; + std::vector m_records; +}; + +}; // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_H_ diff --git a/common/interface/IProfiler.h b/common/interface/IProfiler.h new file mode 100644 index 000000000..bf03acff5 --- /dev/null +++ b/common/interface/IProfiler.h @@ -0,0 +1,136 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ +#define FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class IProfiler; + +/** + * @brief IProfiler factory class, returns a concrete implementation of IProfiler + */ +class IProfilerFactory +{ +public: + IProfilerFactory() = default; + virtual ~IProfilerFactory() = default; + + /** + * @brief Creates a IProfilerFactory instance. + * + * @retval the factory instance or null on error. + */ + static std::shared_ptr createFactory(); + + /** + * @brief Creates an IProfiler object. + * + * @param[in] moduleName : The name of the module + * + * @retval the new profiler instance or null on error. + */ + virtual std::unique_ptr createProfiler(std::string moduleName) const = 0; +}; + +class IProfiler +{ +public: + using RecordId = std::uint64_t; + using Clock = std::chrono::system_clock; + + struct Record + { + std::string moduleName; + uint64_t id{0}; + std::string stage; + std::string info; + Clock::time_point time; + }; + + virtual ~IProfiler() = default; + + IProfiler(const IProfiler &) = delete; + IProfiler &operator=(const IProfiler &) = delete; + IProfiler(IProfiler &&) = delete; + IProfiler &operator=(IProfiler &&) = delete; + + /** + * @brief Checks if profiler is enabled. + * + * @retval true if profiler is enabled, false otherwise. + */ + virtual bool isEnabled() const noexcept = 0; + + /** + * @brief Creates a record for given stage. + * + * @param[in] stage : Stage name used for record creation + * + * @retval Record identifier for created record or std::nullopt. + */ + virtual std::optional record(const std::string &stage) = 0; + + /** + * @brief Creates a record for given stage and info. + * + * @param[in] stage : Stage name used for record creation + * @param[in] info : Additional information used for record creation + * + * @retval Record identifier for created record or std::nullopt. + */ + virtual std::optional record(const std::string &stage, const std::string &info) = 0; + + /** + * @brief Logs a record for given identifier. + * + * @param[in] id : Record identifier + */ + virtual void log(RecordId id) = 0; + + /** + * @brief Dumps all records into pre-configured file. + * + * @retval true if file is created and records are dumped, false otherwise. + */ + virtual bool dumpToFile() const = 0; + + /** + * @brief Retrieves existing records. + * + * @retval Snapshot copy of existing records. + */ + virtual std::vector getRecords() const = 0; + +protected: + IProfiler() = default; +}; + +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_I_PROFILER_H_ diff --git a/common/source/EventThread.cpp b/common/source/EventThread.cpp index 1ad60188b..be4bc6d0e 100644 --- a/common/source/EventThread.cpp +++ b/common/source/EventThread.cpp @@ -54,13 +54,11 @@ EventThread::EventThread(std::string threadName) : m_kThreadName(std::move(threa EventThread::~EventThread() { - std::unique_lock locker(m_lock); - - m_shutdown = true; - - m_cond.notify_all(); - - locker.unlock(); + { + std::lock_guard locker(m_lock); + m_shutdown = true; + m_cond.notify_all(); + } if (m_thread.joinable()) m_thread.join(); diff --git a/common/source/LinuxUtils.cpp b/common/source/LinuxUtils.cpp index b8906830a..b019b8317 100644 --- a/common/source/LinuxUtils.cpp +++ b/common/source/LinuxUtils.cpp @@ -19,34 +19,46 @@ #include "LinuxUtils.h" #include "RialtoCommonLogging.h" +#include #include #include #include #include +#include namespace { -constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner -constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group +constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner +constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group +constexpr size_t kDefaultBufferSize = 4096; // Fallback buffer size uid_t getFileOwnerId(const std::string &fileOwner) { uid_t ownerId = kNoOwnerChange; - const size_t kBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (!fileOwner.empty() && kBufferSize > 0) + if (!fileOwner.empty()) { + const int64_t bufferSizeLong = sysconf(_SC_GETPW_R_SIZE_MAX); + const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast(bufferSizeLong) : kDefaultBufferSize; + errno = 0; passwd passwordStruct{}; passwd *passwordResult = nullptr; - char buffer[kBufferSize]; - int result = getpwnam_r(fileOwner.c_str(), &passwordStruct, buffer, kBufferSize, &passwordResult); - if (result == 0 && passwordResult) + std::vector buffer(kBufferSize); + int result = getpwnam_r(fileOwner.c_str(), &passwordStruct, buffer.data(), buffer.size(), &passwordResult); + if (result == 0) { - ownerId = passwordResult->pw_uid; + if (passwordResult) + { + ownerId = passwordResult->pw_uid; + } + else + { + RIALTO_COMMON_LOG_WARN("Owner name '%s' not found", fileOwner.c_str()); + } } else { - RIALTO_COMMON_LOG_SYS_WARN(errno, "Failed to determine ownerId for '%s'", fileOwner.c_str()); + RIALTO_COMMON_LOG_SYS_WARN(result, "Failed to lookup ownerId for '%s'", fileOwner.c_str()); } } return ownerId; @@ -55,21 +67,30 @@ uid_t getFileOwnerId(const std::string &fileOwner) gid_t getFileGroupId(const std::string &fileGroup) { gid_t groupId = kNoGroupChange; - const size_t kBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (!fileGroup.empty() && kBufferSize > 0) + if (!fileGroup.empty()) { + const int64_t bufferSizeLong = sysconf(_SC_GETGR_R_SIZE_MAX); + const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast(bufferSizeLong) : kDefaultBufferSize; + errno = 0; group groupStruct{}; group *groupResult = nullptr; - char buffer[kBufferSize]; - int result = getgrnam_r(fileGroup.c_str(), &groupStruct, buffer, kBufferSize, &groupResult); - if (result == 0 && groupResult) + std::vector buffer(kBufferSize); + int result = getgrnam_r(fileGroup.c_str(), &groupStruct, buffer.data(), buffer.size(), &groupResult); + if (result == 0) { - groupId = groupResult->gr_gid; + if (groupResult) + { + groupId = groupResult->gr_gid; + } + else + { + RIALTO_COMMON_LOG_WARN("Group name '%s' not found", fileGroup.c_str()); + } } else { - RIALTO_COMMON_LOG_SYS_WARN(errno, "Failed to determine groupId for '%s'", fileGroup.c_str()); + RIALTO_COMMON_LOG_SYS_WARN(result, "Failed to lookup groupId for '%s'", fileGroup.c_str()); } } return groupId; diff --git a/common/source/Profiler.cpp b/common/source/Profiler.cpp new file mode 100644 index 000000000..172e204fc --- /dev/null +++ b/common/source/Profiler.cpp @@ -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 Sky UK + * + * 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 "Profiler.h" +#include "RialtoCommonLogging.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +inline constexpr const char *kProfilerEnv{"PROFILER_ENABLED"}; +inline constexpr const char *kProfilerDumpFileEnv{"PROFILER_DUMP_FILE_NAME"}; +inline constexpr std::size_t kMaxRecords{100}; + +bool getProfilerEnabled() +{ + const char *value = std::getenv(kProfilerEnv); + if (!value || (value[0] == '\0')) + return false; + + std::string stringValue(value); + std::transform(stringValue.begin(), stringValue.end(), stringValue.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + + if (stringValue == "1" || stringValue == "true" || stringValue == "yes" || stringValue == "on") + return true; + if (stringValue == "0" || stringValue == "false" || stringValue == "no" || stringValue == "off") + return false; + + return false; +} + +std::optional getDumpFileName() +{ + const char *value = std::getenv(kProfilerDumpFileEnv); + if (!value || value[0] == '\0') + return std::nullopt; + + return std::string{value}; +} +} // namespace + +namespace firebolt::rialto::common +{ +std::shared_ptr IProfilerFactory::createFactory() +{ + std::shared_ptr factory; + + try + { + factory = std::make_shared(); + } + catch (const std::exception &e) + { + RIALTO_COMMON_LOG_ERROR("Failed to create the profiler factory, reason: %s", e.what()); + } + + return factory; +} + +std::unique_ptr ProfilerFactory::createProfiler(std::string moduleName) const +{ + return std::make_unique(moduleName); +} + +Profiler::Profiler(std::string module) + : m_module(std::move(module)), m_enabled(getProfilerEnabled()), m_dumpFileName(getDumpFileName()) +{ +} + +bool Profiler::isEnabled() const noexcept +{ + return m_enabled; +} + +std::optional Profiler::record(const std::string &stage) +{ + if (!m_enabled) + return std::nullopt; + + auto now = Clock::now(); + std::lock_guard lock(m_mutex); + + if (m_records.size() >= kMaxRecords) + { + return std::nullopt; + } + + const Profiler::RecordId id = m_id++; + + m_records.push_back(Record{m_module, id, stage, std::string{}, now}); + + return id; +} + +std::optional Profiler::record(const std::string &stage, const std::string &info) +{ + if (!m_enabled) + return std::nullopt; + + auto now = Clock::now(); + std::lock_guard lock(m_mutex); + + if (m_records.size() >= kMaxRecords) + { + return std::nullopt; + } + + const Profiler::RecordId id = m_id++; + + m_records.push_back(Record{m_module, id, stage, info, now}); + + return id; +} + +void Profiler::log(const RecordId id) +{ + if (!m_enabled) + return; + + const auto record = findById(id); + if (record) + { + const auto ms = std::chrono::duration_cast(record->time.time_since_epoch()).count(); + + const auto idStr = std::to_string(static_cast(record->id)); + const auto tsStr = std::to_string(static_cast(ms)); + + RIALTO_COMMON_LOG_MIL("PROFILER | RECORD | MODULE[%s] ID[%s] STAGE[%s] INFO[%s] TIMESTAMP[%s]", + record->moduleName.c_str(), idStr.c_str(), record->stage.c_str(), record->info.c_str(), + tsStr.c_str()); + } +} + +bool Profiler::dumpToFile() const +{ + if (!m_dumpFileName) + return false; + + if (!m_enabled) + return false; + + std::vector copy; + { + std::lock_guard lock(m_mutex); + copy = m_records; + } + + std::ofstream out(*m_dumpFileName, std::ios::out | std::ios::app); + if (!out.is_open()) + return false; + + for (const auto &record : copy) + { + const auto us = std::chrono::duration_cast(record.time.time_since_epoch()).count(); + + out << "MODULE[" << record.moduleName << "] " << "ID[" << record.id << "] " << "STAGE[" << record.stage << "] " + << "INFO[" << record.info << "] " << "TIMESTAMP[" << us << "]" << '\n'; + } + + return static_cast(out); +} + +std::vector Profiler::getRecords() const +{ + if (!m_enabled) + return {}; + + std::lock_guard lock(m_mutex); + return m_records; +} + +std::optional Profiler::findById(Profiler::RecordId id) const +{ + std::lock_guard lock(m_mutex); + + const auto it = std::find_if(m_records.begin(), m_records.end(), [&](const auto &record) { return record.id == id; }); + + if (it != m_records.end()) + return *it; + + return std::nullopt; +} + +}; // namespace firebolt::rialto::common diff --git a/common/source/Timer.cpp b/common/source/Timer.cpp index ae109a692..8469a4737 100644 --- a/common/source/Timer.cpp +++ b/common/source/Timer.cpp @@ -59,15 +59,22 @@ Timer::Timer(const std::chrono::milliseconds &timeout, const std::function lock{m_mutex}; - if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) + bool shouldExecuteCallback = false; { - if (m_active && m_callback) + std::unique_lock lock{m_mutex}; + if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) { - lock.unlock(); - m_callback(); + if (m_active && m_callback) + { + shouldExecuteCallback = true; + } } } + + if (shouldExecuteCallback) + { + m_callback(); + } } while (timerType == TimerType::PERIODIC && m_active); m_active = false; }); @@ -81,10 +88,19 @@ Timer::~Timer() void Timer::cancel() { m_active = false; + m_cv.notify_one(); + + if (std::this_thread::get_id() == m_thread.get_id()) + { + if (m_thread.joinable()) + { + m_thread.detach(); + } + return; + } - if (std::this_thread::get_id() != m_thread.get_id() && m_thread.joinable()) + if (m_thread.joinable()) { - m_cv.notify_one(); m_thread.join(); } } diff --git a/install_dependencies_for_native_build.sh b/install_dependencies_for_native_build.sh new file mode 100644 index 000000000..679fb67ff --- /dev/null +++ b/install_dependencies_for_native_build.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 Sky UK +# +# 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. +# + +# Entry script for building the dependencies for Rialto Native Build. + +apt-get update +apt-get install -y build-essential +apt-get install -y cmake +apt-get install -y libunwind-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev libgstreamer1.0-dev libyaml-cpp-dev +apt-get install -y protobuf-compiler \ No newline at end of file diff --git a/ipc/client/source/IpcChannelImpl.cpp b/ipc/client/source/IpcChannelImpl.cpp index 85c6fea52..98f6aceee 100644 --- a/ipc/client/source/IpcChannelImpl.cpp +++ b/ipc/client/source/IpcChannelImpl.cpp @@ -562,37 +562,36 @@ void ChannelImpl::processTimeoutEvent() return; } - // check if any method call has no expired - std::unique_lock locker(m_lock); - // stores the timed-out method calls std::vector timedOuts; - // remove the method calls that have expired - const auto kNow = std::chrono::steady_clock::now(); - auto it = m_methodCalls.begin(); - while (it != m_methodCalls.end()) { - if (kNow >= it->second.timeoutDeadline) + // check if any method call has now expired + std::unique_lock locker(m_lock); + + // remove the method calls that have expired + const auto kNow = std::chrono::steady_clock::now(); + auto it = m_methodCalls.begin(); + while (it != m_methodCalls.end()) { - timedOuts.emplace_back(it->second); - it = m_methodCalls.erase(it); + if (kNow >= it->second.timeoutDeadline) + { + timedOuts.emplace_back(it->second); + it = m_methodCalls.erase(it); + } + else + { + ++it; + } } - else + + // if we still have method calls available, then re-calculate the timer for the next timeout + if (!m_methodCalls.empty()) { - ++it; + updateTimeoutTimer(); } } - // if we still have method calls available, then re-calculate the timer for the next timeout - if (!m_methodCalls.empty()) - { - updateTimeoutTimer(); - } - - // drop the lock and now terminate the timed out method calls - locker.unlock(); - for (auto &call : timedOuts) { completeWithError(&call, "Timed out"); @@ -718,28 +717,25 @@ void ChannelImpl::processReplyFromServer(const transport::MethodCallReply &reply { RIALTO_IPC_LOG_DEBUG("processing reply from server"); - std::unique_lock locker(m_lock); - - // find the original request + MethodCall methodCall; const uint64_t kSerialId = reply.reply_id(); - auto it = m_methodCalls.find(kSerialId); - if (it == m_methodCalls.end()) + { - RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); - return; - } + std::lock_guard locker(m_lock); - // take the method call and remove from the map of outstanding calls - MethodCall methodCall = it->second; - m_methodCalls.erase(it); + auto it = m_methodCalls.find(kSerialId); + if (it == m_methodCalls.end()) + { + RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); + return; + } - // update the timeout timer now a method call has been processed - updateTimeoutTimer(); + methodCall = it->second; + m_methodCalls.erase(it); - // can now drop the lock - locker.unlock(); + updateTimeoutTimer(); + } - // this is an actual reply so try and read it if (!methodCall.response->ParseFromString(reply.reply_message())) { RIALTO_IPC_LOG_ERROR("failed to parse method reply from server"); @@ -769,26 +765,27 @@ void ChannelImpl::processErrorFromServer(const transport::MethodCallError &error { RIALTO_IPC_LOG_DEBUG("processing error from server"); - std::unique_lock locker(m_lock); - - // find the original request + MethodCall methodCall; const uint64_t kSerialId = error.reply_id(); - auto it = m_methodCalls.find(kSerialId); - if (it == m_methodCalls.end()) + { - RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", error.reply_id()); - return; - } + std::unique_lock locker(m_lock); - // take the method call and remove from the map of outstanding calls - MethodCall methodCall = it->second; - m_methodCalls.erase(it); + // find the original request + auto it = m_methodCalls.find(kSerialId); + if (it == m_methodCalls.end()) + { + RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", error.reply_id()); + return; + } - // update the timeout timer now a method call has been processed - updateTimeoutTimer(); + // take the method call and remove from the map of outstanding calls + methodCall = it->second; + m_methodCalls.erase(it); - // can now drop the lock - locker.unlock(); + // update the timeout timer now a method call has been processed + updateTimeoutTimer(); + } RIALTO_IPC_LOG_DEBUG("error{ serial %" PRIu64 " } - %s", kSerialId, error.error_reason().c_str()); @@ -1158,40 +1155,55 @@ void ChannelImpl::CallMethod(const google::protobuf::MethodDescriptor *method, / method->options().GetExtension(::firebolt::rialto::ipc::no_reply); // finally, send the message - std::unique_lock locker(m_lock); - - if (m_sock < 0) + google::protobuf::Closure *doneClosure = nullptr; + std::string errorMessage; // Capture error state { - locker.unlock(); - completeWithError(&methodCall, "Not connected"); - } - else if (sendmsg(m_sock, header, MSG_NOSIGNAL) != static_cast(kRequiredDataLen)) - { - locker.unlock(); - completeWithError(&methodCall, "Failed to send message"); - } - else - { - RIALTO_IPC_LOG_DEBUG("call{ serial %" PRIu64 " } - %s.%s { %s }", kSerialId, call->service_name().c_str(), - call->method_name().c_str(), request->ShortDebugString().c_str()); + std::unique_lock locker(m_lock); - if (kNoReplyExpected) + if (m_sock < 0) + { + errorMessage = "Not connected"; + } + else if (sendmsg(m_sock, header, MSG_NOSIGNAL) != static_cast(kRequiredDataLen)) { - // no reply from server is expected, however if the caller supplied - // a closure (it shouldn't) we should still call it now to indicate - // the method call has been made - if (done) - done->Run(); + errorMessage = "Failed to send message"; } else { - // add the message to the queue so we pick-up the reply - m_methodCalls.emplace(kSerialId, methodCall); + RIALTO_IPC_LOG_DEBUG("call{ serial %" PRIu64 " } - %s.%s { %s }", kSerialId, call->service_name().c_str(), + call->method_name().c_str(), request->ShortDebugString().c_str()); - // update the single timeout timer - updateTimeoutTimer(); + if (kNoReplyExpected) + { + // no reply from server is expected, however if the caller supplied + // a closure (it shouldn't) we should still call it now to indicate + // the method call has been made + doneClosure = done; + } + else + { + // add the message to the queue so we pick-up the reply + m_methodCalls.emplace(kSerialId, methodCall); + + // update the single timeout timer + updateTimeoutTimer(); + } } } + // Lock is released here automatically + + // Handle error case outside lock to avoid deadlock if user closure re-enters + if (!errorMessage.empty()) + { + completeWithError(&methodCall, std::move(errorMessage)); + return; + } + + // Invoke closure outside the lock to avoid deadlock if the user closure re-enters + if (kNoReplyExpected && doneClosure) + { + doneClosure->Run(); + } } int ChannelImpl::subscribeImpl(const std::string &kEventName, const google::protobuf::Descriptor *descriptor, diff --git a/ipc/common/source/NamedSocket.cpp b/ipc/common/source/NamedSocket.cpp index f3565aa5b..a6e855605 100644 --- a/ipc/common/source/NamedSocket.cpp +++ b/ipc/common/source/NamedSocket.cpp @@ -19,6 +19,7 @@ #include "NamedSocket.h" #include "IpcLogging.h" +#include #include #include #include @@ -28,11 +29,13 @@ #include #include #include +#include namespace { -constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner -constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group +constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner +constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group +constexpr size_t kDefaultBufferSize = 4096; // Fallback buffer size } // namespace namespace firebolt::rialto::ipc @@ -268,21 +271,30 @@ bool NamedSocket::getSocketLock() uid_t NamedSocket::getSocketOwnerId(const std::string &socketOwner) const { uid_t ownerId = kNoOwnerChange; - const size_t kBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (!socketOwner.empty() && kBufferSize > 0) + if (!socketOwner.empty()) { + const int64_t bufferSizeLong = sysconf(_SC_GETPW_R_SIZE_MAX); + const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast(bufferSizeLong) : kDefaultBufferSize; + errno = 0; passwd passwordStruct{}; passwd *passwordResult = nullptr; - char buffer[kBufferSize]; - int result = getpwnam_r(socketOwner.c_str(), &passwordStruct, buffer, kBufferSize, &passwordResult); - if (result == 0 && passwordResult) + std::vector buffer(kBufferSize); + int result = getpwnam_r(socketOwner.c_str(), &passwordStruct, buffer.data(), buffer.size(), &passwordResult); + if (result == 0) { - ownerId = passwordResult->pw_uid; + if (passwordResult) + { + ownerId = passwordResult->pw_uid; + } + else + { + RIALTO_IPC_LOG_WARN("Owner name '%s' not found", socketOwner.c_str()); + } } else { - RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine ownerId for '%s'", socketOwner.c_str()); + RIALTO_IPC_LOG_SYS_WARN(result, "Failed to lookup ownerId for '%s'", socketOwner.c_str()); } } return ownerId; @@ -291,21 +303,30 @@ uid_t NamedSocket::getSocketOwnerId(const std::string &socketOwner) const gid_t NamedSocket::getSocketGroupId(const std::string &socketGroup) const { gid_t groupId = kNoGroupChange; - const size_t kBufferSize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (!socketGroup.empty() && kBufferSize > 0) + if (!socketGroup.empty()) { + const int64_t bufferSizeLong = sysconf(_SC_GETGR_R_SIZE_MAX); + const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast(bufferSizeLong) : kDefaultBufferSize; + errno = 0; group groupStruct{}; group *groupResult = nullptr; - char buffer[kBufferSize]; - int result = getgrnam_r(socketGroup.c_str(), &groupStruct, buffer, kBufferSize, &groupResult); - if (result == 0 && groupResult) + std::vector buffer(kBufferSize); + int result = getgrnam_r(socketGroup.c_str(), &groupStruct, buffer.data(), buffer.size(), &groupResult); + if (result == 0) { - groupId = groupResult->gr_gid; + if (groupResult) + { + groupId = groupResult->gr_gid; + } + else + { + RIALTO_IPC_LOG_WARN("Group name '%s' not found", socketGroup.c_str()); + } } else { - RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine groupId for '%s'", socketGroup.c_str()); + RIALTO_IPC_LOG_SYS_WARN(result, "Failed to lookup groupId for '%s'", socketGroup.c_str()); } } return groupId; diff --git a/ipc/server/include/IIpcServer.h b/ipc/server/include/IIpcServer.h index 10a2a8936..b7c9bc26d 100644 --- a/ipc/server/include/IIpcServer.h +++ b/ipc/server/include/IIpcServer.h @@ -162,10 +162,10 @@ class IServer return addSocket(socketPath, std::move(clientConnectedCb), nullptr); } virtual bool addSocket(const std::string &socketPath, - std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) = 0; - virtual bool addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) = 0; + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) = 0; + virtual bool addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) = 0; /** * @brief Create a client. diff --git a/ipc/server/source/IpcServerImpl.cpp b/ipc/server/source/IpcServerImpl.cpp index f6cd2da73..55cc266b0 100644 --- a/ipc/server/source/IpcServerImpl.cpp +++ b/ipc/server/source/IpcServerImpl.cpp @@ -209,8 +209,8 @@ void ServerImpl::closeListeningSocket(Socket *socket) } bool ServerImpl::addSocket(const std::string &socketPath, - std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) { // store the path Socket socket; @@ -289,8 +289,8 @@ bool ServerImpl::addSocket(const std::string &socketPath, return true; } -bool ServerImpl::addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) +bool ServerImpl::addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) { // store the path Socket socket; @@ -616,37 +616,42 @@ void ServerImpl::processNewConnection(uint64_t socketId) { RIALTO_IPC_LOG_DEBUG("processing new connection"); - std::unique_lock socketLocker(m_socketsLock); + int clientSock; + std::string sockPath; + std::function &)> connectedCb; + std::function &)> disconnectedCb; - // find matching socket object - auto it = m_sockets.find(socketId); - if (it == m_sockets.end()) { - RIALTO_IPC_LOG_ERROR("failed to find listening socket with id %" PRIu64, socketId); - return; - } + std::unique_lock socketLocker(m_socketsLock); - const Socket &kSocket = it->second; + // find matching socket object + auto it = m_sockets.find(socketId); + if (it == m_sockets.end()) + { + RIALTO_IPC_LOG_ERROR("failed to find listening socket with id %" PRIu64, socketId); + return; + } - // accept the connection from the client - struct sockaddr clientAddr = {0}; - socklen_t clientAddrLen = sizeof(clientAddr); + const Socket &kSocket = it->second; - int clientSock = accept4(kSocket.sockFd, &clientAddr, &clientAddrLen, SOCK_NONBLOCK | SOCK_CLOEXEC); - if (clientSock < 0) - { - RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to accept client connection"); - return; - } + // accept the connection from the client + struct sockaddr clientAddr = {0}; + socklen_t clientAddrLen = sizeof(clientAddr); - const std::string kSockPath = kSocket.sockPath; - std::function &)> connectedCb = kSocket.connectedCb; - std::function &)> disconnectedCb = kSocket.disconnectedCb; + clientSock = accept4(kSocket.sockFd, &clientAddr, &clientAddrLen, SOCK_NONBLOCK | SOCK_CLOEXEC); + if (clientSock < 0) + { + RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to accept client connection"); + return; + } - socketLocker.unlock(); + sockPath = kSocket.sockPath; + connectedCb = kSocket.connectedCb; + disconnectedCb = kSocket.disconnectedCb; + } // attempt to add the socket to the client list - auto client = addClientSocket(clientSock, kSockPath, std::move(disconnectedCb)); + auto client = addClientSocket(clientSock, sockPath, std::move(disconnectedCb)); if (!client) { close(clientSock); @@ -736,31 +741,34 @@ static std::vector readMessageFds(const struct msghdr *msg, size */ void ServerImpl::processClientSocket(uint64_t clientId, unsigned events) { - // take the lock while accessing the client list - std::unique_lock locker(m_clientsLock); + int sockFd; + std::shared_ptr clientObj; - auto it = m_clients.find(clientId); - if (it == m_clients.end()) + // take the lock while accessing the client list { - // should never happen - RIALTO_IPC_LOG_ERROR("received an event from a socket with no matching client"); - return; - } + std::unique_lock locker(m_clientsLock); - // check if the client is marked for closure, if so then just ignore the data - if (m_condemnedClients.count(clientId) != 0) - { - return; - } + auto it = m_clients.find(clientId); + if (it == m_clients.end()) + { + // should never happen + RIALTO_IPC_LOG_ERROR("received an event from a socket with no matching client"); + return; + } - // get the socket that corresponds to the client connection - const int kSockFd = it->second.sock; + // check if the client is marked for closure, if so then just ignore the data + if (m_condemnedClients.count(clientId) != 0) + { + return; + } - // get the client object - std::shared_ptr clientObj = it->second.client; + // get the socket that corresponds to the client connection + sockFd = it->second.sock; - // can safely release the lock now we have the clientId and client object - locker.unlock(); + // get the client object + clientObj = it->second.client; + } + // lock is released here automatically // if there was an error disconnect the socket if (events & EPOLLERR) @@ -786,7 +794,7 @@ void ServerImpl::processClientSocket(uint64_t clientId, unsigned events) msg.msg_controllen = sizeof(m_recvCtrlBuf); // read one message - ssize_t rd = TEMP_FAILURE_RETRY(recvmsg(kSockFd, &msg, MSG_CMSG_CLOEXEC)); + ssize_t rd = TEMP_FAILURE_RETRY(recvmsg(sockFd, &msg, MSG_CMSG_CLOEXEC)); if (rd < 0) { if (errno != EWOULDBLOCK) @@ -1303,9 +1311,10 @@ bool ServerImpl::isClientConnected(uint64_t clientId) const */ void ServerImpl::disconnectClient(uint64_t clientId) { - std::unique_lock locker(m_clientsLock); - m_condemnedClients.insert(clientId); - locker.unlock(); + { + std::lock_guard locker(m_clientsLock); + m_condemnedClients.insert(clientId); + } wakeEventLoop(); } @@ -1393,21 +1402,22 @@ bool ServerImpl::sendEvent(uint64_t clientId, const std::shared_ptr locker(m_clientsLock); - - auto it = m_clients.find(clientId); - if (it == m_clients.end() || it->second.sock < 0) - { - RIALTO_IPC_LOG_WARN("socket closed before event could be sent"); - return false; - } - else if (TEMP_FAILURE_RETRY(sendmsg(it->second.sock, header, MSG_NOSIGNAL)) != static_cast(requiredDataLen)) { - RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to send the complete event message"); - return false; - } + std::unique_lock locker(m_clientsLock); - locker.unlock(); + auto it = m_clients.find(clientId); + if (it == m_clients.end() || it->second.sock < 0) + { + RIALTO_IPC_LOG_WARN("socket closed before event could be sent"); + return false; + } + else if (TEMP_FAILURE_RETRY(sendmsg(it->second.sock, header, MSG_NOSIGNAL)) != + static_cast(requiredDataLen)) + { + RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to send the complete event message"); + return false; + } + } RIALTO_IPC_LOG_DEBUG("event{ %s } - { %s }", eventMessage->GetTypeName().c_str(), eventMessage->ShortDebugString().c_str()); diff --git a/ipc/server/source/IpcServerImpl.h b/ipc/server/source/IpcServerImpl.h index 591f2a80b..758dc89e1 100644 --- a/ipc/server/source/IpcServerImpl.h +++ b/ipc/server/source/IpcServerImpl.h @@ -64,10 +64,11 @@ class ServerImpl final : public ::firebolt::rialto::ipc::IServer, public std::en ~ServerImpl() final; public: - bool addSocket(const std::string &socketPath, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) override; - bool addSocket(int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb) override; + bool addSocket(const std::string &socketPath, + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) override; + bool addSocket(int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb) override; std::shared_ptr addClient(int socketFd, std::function &)> clientDisconnectedCb) override; diff --git a/logging/source/EnvVariableParser.cpp b/logging/source/EnvVariableParser.cpp index 5ef89d89c..0253891fb 100644 --- a/logging/source/EnvVariableParser.cpp +++ b/logging/source/EnvVariableParser.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace @@ -70,7 +71,7 @@ std::vector split(std::string s, const std::string &delimiter) result.push_back(s.substr(0, pos)); s.erase(0, pos + delimiter.length()); } - result.push_back(s); + result.push_back(std::move(s)); return result; } diff --git a/media/client/ipc/include/MediaKeysCapabilitiesIpc.h b/media/client/ipc/include/MediaKeysCapabilitiesIpc.h index fd02f1b33..7bc829c9b 100644 --- a/media/client/ipc/include/MediaKeysCapabilitiesIpc.h +++ b/media/client/ipc/include/MediaKeysCapabilitiesIpc.h @@ -80,6 +80,8 @@ class MediaKeysCapabilitiesIpc : public IMediaKeysCapabilities, public IpcModule bool isServerCertificateSupported(const std::string &keySystem) override; + bool getSupportedRobustnessLevels(const std::string &keySystem, std::vector &robustnessLevels) override; + private: /** * @brief The ipc protobuf media keys capabilities stub. diff --git a/media/client/ipc/include/MediaKeysIpc.h b/media/client/ipc/include/MediaKeysIpc.h index 6302d383a..f962fc85d 100644 --- a/media/client/ipc/include/MediaKeysIpc.h +++ b/media/client/ipc/include/MediaKeysIpc.h @@ -69,11 +69,12 @@ class MediaKeysIpc : public IMediaKeys, public IpcModule bool containsKey(int32_t keySessionId, const std::vector &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index 5e50dd4f4..5b8877688 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -75,11 +75,11 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool allSourcesAttached() override; - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool setVideoWindow(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -93,6 +93,10 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -144,6 +148,8 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + private: /** * @brief The media player client ipc. @@ -213,6 +219,13 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule */ void onBufferUnderflow(const std::shared_ptr &event); + /** + * @brief Handler for a first frame received notification from the server. + * + * @param[in] event : The first frame received event structure. + */ + void onFirstFrameReceived(const std::shared_ptr &event); + /** * @brief Handler for a playback error notification from the server. * diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index 1d055d2b7..54e6e5c1b 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -106,10 +106,11 @@ class IMediaPipelineIpc * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. * * @retval true on success. */ - virtual bool load(MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; /** * @brief Request to set the coordinates of the video window. @@ -126,9 +127,11 @@ class IMediaPipelineIpc /** * @brief Request play on the playback session. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Request pause on the playback session. @@ -186,6 +189,30 @@ class IMediaPipelineIpc */ virtual bool setImmediateOutput(int32_t sourceId, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous, it will set the "Report Decode Errors" property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * This method is synchronous, it gets the queued frames property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[out] queuedFrames : Get queued frames on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * @@ -451,6 +478,17 @@ class IMediaPipelineIpc * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto::client diff --git a/media/client/ipc/interface/IMediaPipelineIpcClient.h b/media/client/ipc/interface/IMediaPipelineIpcClient.h index e393152da..b4d41f556 100644 --- a/media/client/ipc/interface/IMediaPipelineIpcClient.h +++ b/media/client/ipc/interface/IMediaPipelineIpcClient.h @@ -96,6 +96,15 @@ class IMediaPipelineIpcClient */ virtual void notifyBufferUnderflow(int32_t sourceId) = 0; + /** + * @brief Notifies the client that the first frame has been received. + * + * Notification shall be sent whenever a video/audio first frame is received + * + * @param[in] sourceId : The id of the source that received the first frame + */ + virtual void notifyFirstFrameReceived(int32_t sourceId) = 0; + /** * @brief Notifies the client that a non-fatal error has occurred in the player. * diff --git a/media/client/ipc/source/IpcClient.cpp b/media/client/ipc/source/IpcClient.cpp index 53c5b8d6a..57f2f5c60 100644 --- a/media/client/ipc/source/IpcClient.cpp +++ b/media/client/ipc/source/IpcClient.cpp @@ -19,6 +19,7 @@ #include "IpcClient.h" #include "RialtoClientLogging.h" +#include namespace firebolt::rialto::client { @@ -191,7 +192,7 @@ std::shared_ptr IpcClient::createBlockingClosure() // check which thread we're being called from, this determines if we pump // event loop from within the wait() method or not if (m_ipcThread.get_id() == std::this_thread::get_id()) - return m_blockingClosureFactory->createBlockingClosurePoll(ipcChannel); + return m_blockingClosureFactory->createBlockingClosurePoll(std::move(ipcChannel)); else return m_blockingClosureFactory->createBlockingClosureSemaphore(); } diff --git a/media/client/ipc/source/IpcModule.cpp b/media/client/ipc/source/IpcModule.cpp index a48597f22..940c2481e 100644 --- a/media/client/ipc/source/IpcModule.cpp +++ b/media/client/ipc/source/IpcModule.cpp @@ -126,7 +126,9 @@ bool IpcModule::reattachChannelIfRequired() std::shared_ptr IpcModule::getConnectedChannel() { - std::shared_ptr ipcChannel = m_ipc.getChannel().lock(); + // Split for Coverity clarity due to independent lifetime + std::weak_ptr weakChannel = m_ipc.getChannel(); + std::shared_ptr ipcChannel = weakChannel.lock(); if (ipcChannel && ipcChannel->isConnected()) { return ipcChannel; diff --git a/media/client/ipc/source/MediaKeysCapabilitiesIpc.cpp b/media/client/ipc/source/MediaKeysCapabilitiesIpc.cpp index 4227f3848..6137bb27a 100644 --- a/media/client/ipc/source/MediaKeysCapabilitiesIpc.cpp +++ b/media/client/ipc/source/MediaKeysCapabilitiesIpc.cpp @@ -213,4 +213,37 @@ bool MediaKeysCapabilitiesIpc::isServerCertificateSupported(const std::string &k return response.is_supported(); } +bool MediaKeysCapabilitiesIpc::getSupportedRobustnessLevels(const std::string &keySystem, + std::vector &robustnessLevels) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetSupportedRobustnessLevelsRequest request; + request.set_key_system(keySystem); + + firebolt::rialto::GetSupportedRobustnessLevelsResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaKeysCapabilitiesStub->getSupportedRobustnessLevels(ipcController.get(), &request, &response, + blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get supported robustness levels due to '%s'", + ipcController->ErrorText().c_str()); + return false; + } + + robustnessLevels.assign(response.robustness_levels().begin(), response.robustness_levels().end()); + return true; +} + }; // namespace firebolt::rialto::client diff --git a/media/client/ipc/source/MediaKeysIpc.cpp b/media/client/ipc/source/MediaKeysIpc.cpp index 24909664e..0b7a964c3 100644 --- a/media/client/ipc/source/MediaKeysIpc.cpp +++ b/media/client/ipc/source/MediaKeysIpc.cpp @@ -55,6 +55,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::ProtoMediaKeyErrorStatus &err { return firebolt::rialto::MediaKeyErrorStatus::FAIL; } + case firebolt::rialto::ProtoMediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } } return firebolt::rialto::MediaKeyErrorStatus::FAIL; } @@ -123,6 +127,10 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &errorStatus) { return "FAIL"; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return "OUTPUT_RESTRICTED"; + } } return "UNKNOWN"; } @@ -334,7 +342,7 @@ bool MediaKeysIpc::containsKey(int32_t keySessionId, const std::vector } MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { if (!reattachChannelIfRequired()) { @@ -362,7 +370,6 @@ MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, s firebolt::rialto::CreateKeySessionRequest request; request.set_media_keys_handle(m_mediaKeysHandle); request.set_session_type(protoSessionType); - request.set_is_ldl(isLDL); firebolt::rialto::CreateKeySessionResponse response; // Default error status to FAIL @@ -387,7 +394,8 @@ MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, s } MediaKeyErrorStatus MediaKeysIpc::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { if (!reattachChannelIfRequired()) { @@ -415,10 +423,29 @@ MediaKeyErrorStatus MediaKeysIpc::generateRequest(int32_t keySessionId, InitData break; } + GenerateRequestRequest_LimitedDurationLicense protoLimitedDurationLicense{ + GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED}; + switch (ldlState) + { + case LimitedDurationLicense::NOT_SPECIFIED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED; + break; + case LimitedDurationLicense::ENABLED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_ENABLED; + break; + case LimitedDurationLicense::DISABLED: + protoLimitedDurationLicense = GenerateRequestRequest_LimitedDurationLicense_DISABLED; + break; + default: + RIALTO_CLIENT_LOG_WARN("Received unknown limited duration license state"); + break; + } + firebolt::rialto::GenerateRequestRequest request; request.set_media_keys_handle(m_mediaKeysHandle); request.set_key_session_id(keySessionId); request.set_init_data_type(protoInitDataType); + request.set_ldl_state(protoLimitedDurationLicense); for (auto it = initData.begin(); it != initData.end(); it++) { diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index 23c0375da..86d52f165 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -158,6 +158,13 @@ bool MediaPipelineIpc::subscribeToEvents(const std::shared_ptr &i return false; m_eventTags.push_back(eventTag); + eventTag = ipcChannel->subscribe( + [this](const std::shared_ptr &event) + { m_eventThread->add(&MediaPipelineIpc::onFirstFrameReceived, this, event); }); + if (eventTag < 0) + return false; + m_eventTags.push_back(eventTag); + eventTag = ipcChannel->subscribe( [this](const std::shared_ptr &event) { m_eventThread->add(&MediaPipelineIpc::onPlaybackError, this, event); }); @@ -182,7 +189,7 @@ bool MediaPipelineIpc::subscribeToEvents(const std::shared_ptr &i return true; } -bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { if (!reattachChannelIfRequired()) { @@ -196,6 +203,7 @@ bool MediaPipelineIpc::load(MediaType type, const std::string &mimeType, const s request.set_type(convertLoadRequestMediaType(type)); request.set_mime_type(mimeType); request.set_url(url); + request.set_is_live(isLive); firebolt::rialto::LoadResponse response; auto ipcController = m_ipc.createRpcController(); @@ -348,7 +356,7 @@ bool MediaPipelineIpc::setVideoWindow(uint32_t x, uint32_t y, uint32_t width, ui return true; } -bool MediaPipelineIpc::play() +bool MediaPipelineIpc::play(bool &async) { if (!reattachChannelIfRequired()) { @@ -375,6 +383,8 @@ bool MediaPipelineIpc::play() return false; } + async = response.async(); + return true; } @@ -533,6 +543,37 @@ bool MediaPipelineIpc::getPosition(int64_t &position) return true; } +bool MediaPipelineIpc::getDuration(int64_t &duration) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetDurationRequest request; + + request.set_session_id(m_sessionId); + + firebolt::rialto::GetDurationResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->getDuration(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get duration due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + duration = response.duration(); + return true; +} + bool MediaPipelineIpc::setImmediateOutput(int32_t sourceId, bool immediateOutput) { if (!reattachChannelIfRequired()) @@ -565,6 +606,73 @@ bool MediaPipelineIpc::setImmediateOutput(int32_t sourceId, bool immediateOutput return true; } +bool MediaPipelineIpc::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::SetReportDecodeErrorsRequest request; + + request.set_session_id(m_sessionId); + request.set_source_id(sourceId); + request.set_report_decode_errors(reportDecodeErrors); + + firebolt::rialto::SetReportDecodeErrorsResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->setReportDecodeErrors(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to set report decode error due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + return true; +} + +bool MediaPipelineIpc::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetQueuedFramesRequest request; + + request.set_session_id(m_sessionId); + request.set_source_id(sourceId); + + firebolt::rialto::GetQueuedFramesResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->getQueuedFrames(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get queued frames due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + else + { + queuedFrames = response.queued_frames(); + } + + return true; +} + bool MediaPipelineIpc::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { if (!reattachChannelIfRequired()) @@ -1499,6 +1607,15 @@ void MediaPipelineIpc::onBufferUnderflow(const std::shared_ptr &event) +{ + // Ignore event if not for this session + if (event->session_id() == m_sessionId) + { + m_mediaPipelineIpcClient->notifyFirstFrameReceived(event->source_id()); + } +} + void MediaPipelineIpc::onPlaybackError(const std::shared_ptr &event) { // Ignore event if not for this session @@ -1510,6 +1627,9 @@ void MediaPipelineIpc::onPlaybackError(const std::shared_ptr &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; diff --git a/media/client/main/include/MediaKeysCapabilities.h b/media/client/main/include/MediaKeysCapabilities.h index cece33a3c..c4dd39a85 100644 --- a/media/client/main/include/MediaKeysCapabilities.h +++ b/media/client/main/include/MediaKeysCapabilities.h @@ -81,6 +81,8 @@ class MediaKeysCapabilities : public IMediaKeysCapabilities bool isServerCertificateSupported(const std::string &keySystem) override; + bool getSupportedRobustnessLevels(const std::string &keySystem, std::vector &robustnessLevels) override; + private: /** * @brief The media keys capabilities ipc object. diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index 74bf8d13b..12cbb250a 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -110,7 +110,7 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel */ virtual ~MediaPipeline(); - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(const std::unique_ptr &source) override; @@ -118,7 +118,7 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -132,6 +132,10 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -156,6 +160,8 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel void notifyBufferUnderflow(int32_t sourceId) override; + void notifyFirstFrameReceived(int32_t sourceId) override; + void notifyPlaybackError(int32_t sourceId, PlaybackError error) override; void notifySourceFlushed(int32_t sourceId) override; @@ -188,6 +194,8 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool getStreamSyncMode(int32_t &streamSyncMode) override; + bool getDuration(int64_t &duration) override; + bool flush(int32_t sourceId, bool resetTime, bool &async) override; bool setSourcePosition(int32_t sourceId, int64_t position, bool resetTime, double appliedRate, diff --git a/media/client/main/include/MediaPipelineProxy.h b/media/client/main/include/MediaPipelineProxy.h index cf0283cd2..0fbfa3376 100644 --- a/media/client/main/include/MediaPipelineProxy.h +++ b/media/client/main/include/MediaPipelineProxy.h @@ -38,9 +38,9 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient std::weak_ptr getClient() override { return m_mediaPipeline->getClient(); } - bool load(MediaType type, const std::string &mimeType, const std::string &url) override + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override { - return m_mediaPipeline->load(type, mimeType, url); + return m_mediaPipeline->load(type, mimeType, url, isLive); } bool attachSource(const std::unique_ptr &source) override @@ -52,7 +52,7 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient bool allSourcesAttached() override { return m_mediaPipeline->allSourcesAttached(); } - bool play() override { return m_mediaPipeline->play(); } + bool play(bool &async) override { return m_mediaPipeline->play(async); } bool pause() override { return m_mediaPipeline->pause(); } @@ -68,6 +68,14 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient { return m_mediaPipeline->setImmediateOutput(sourceId, immediateOutput); } + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) + { + return m_mediaPipeline->setReportDecodeErrors(sourceId, reportDecodeErrors); + } + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) + { + return m_mediaPipeline->getQueuedFrames(sourceId, queuedFrames); + } bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) { return m_mediaPipeline->getImmediateOutput(sourceId, immediateOutput); @@ -174,6 +182,8 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient return m_mediaPipeline->switchSource(source); } + bool getDuration(int64_t &duration) override { return m_mediaPipeline->getDuration(duration); } + void notifyApplicationState(ApplicationState state) override { m_mediaPipeline->notifyApplicationState(state); } private: diff --git a/media/client/main/source/ClientController.cpp b/media/client/main/source/ClientController.cpp index eac82154c..5f9fe6b6f 100644 --- a/media/client/main/source/ClientController.cpp +++ b/media/client/main/source/ClientController.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace { @@ -265,7 +266,7 @@ void ClientController::changeStateAndNotifyClients(ApplicationState state) std::shared_ptr clientLocked{client.lock()}; if (clientLocked) { - currentClients.push_back(clientLocked); + currentClients.push_back(std::move(clientLocked)); } else { diff --git a/media/client/main/source/Control.cpp b/media/client/main/source/Control.cpp index 215558fe8..2526694eb 100644 --- a/media/client/main/source/Control.cpp +++ b/media/client/main/source/Control.cpp @@ -20,6 +20,7 @@ #include "Control.h" #include "IControlIpc.h" #include "RialtoClientLogging.h" +#include namespace firebolt::rialto { @@ -76,7 +77,7 @@ bool Control::registerClient(std::weak_ptr client, ApplicationSt std::shared_ptr lockedClient = client.lock(); if (lockedClient && m_clientController.registerClient(lockedClient, appState)) { - m_clientsToUnregister.push_back(lockedClient); + m_clientsToUnregister.push_back(std::move(lockedClient)); return true; } RIALTO_CLIENT_LOG_WARN("Unable to register client"); diff --git a/media/client/main/source/MediaKeys.cpp b/media/client/main/source/MediaKeys.cpp index 1a60d44e1..4d5afa337 100644 --- a/media/client/main/source/MediaKeys.cpp +++ b/media/client/main/source/MediaKeys.cpp @@ -115,11 +115,11 @@ bool MediaKeys::containsKey(int32_t keySessionId, const std::vector &ke } MediaKeyErrorStatus MediaKeys::createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - auto result{m_mediaKeysIpc->createKeySession(sessionType, client, isLDL, keySessionId)}; + auto result{m_mediaKeysIpc->createKeySession(sessionType, client, keySessionId)}; if (isNetflixPlayready(m_keySystem) && MediaKeyErrorStatus::OK == result) { KeyIdMap::instance().addSession(keySessionId); @@ -128,11 +128,12 @@ MediaKeyErrorStatus MediaKeys::createKeySession(KeySessionType sessionType, std: } MediaKeyErrorStatus MediaKeys::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaKeysIpc->generateRequest(keySessionId, initDataType, initData); + return m_mediaKeysIpc->generateRequest(keySessionId, initDataType, initData, ldlState); } MediaKeyErrorStatus MediaKeys::loadSession(int32_t keySessionId) diff --git a/media/client/main/source/MediaKeysCapabilities.cpp b/media/client/main/source/MediaKeysCapabilities.cpp index 7ed7bc997..bda018d70 100644 --- a/media/client/main/source/MediaKeysCapabilities.cpp +++ b/media/client/main/source/MediaKeysCapabilities.cpp @@ -120,4 +120,12 @@ bool MediaKeysCapabilities::isServerCertificateSupported(const std::string &keyS return m_mediaKeysCapabilitiesIpc->isServerCertificateSupported(keySystem); } +bool MediaKeysCapabilities::getSupportedRobustnessLevels(const std::string &keySystem, + std::vector &robustnessLevels) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + return m_mediaKeysCapabilitiesIpc->getSupportedRobustnessLevels(keySystem, robustnessLevels); +} + }; // namespace firebolt::rialto::client diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index 280aff919..d555eecd8 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -184,7 +184,7 @@ MediaPipeline::MediaPipeline(std::weak_ptr client, const V const std::shared_ptr &mediaFrameWriterFactory, IClientController &clientController) : m_mediaPipelineClient(client), m_clientController{clientController}, m_currentAppState{ApplicationState::UNKNOWN}, - m_mediaFrameWriterFactory(mediaFrameWriterFactory), m_currentState(State::IDLE) + m_mediaFrameWriterFactory(mediaFrameWriterFactory), m_currentState(State::IDLE), m_attachingSource(false) { RIALTO_CLIENT_LOG_DEBUG("entry:"); @@ -203,11 +203,11 @@ MediaPipeline::~MediaPipeline() m_mediaPipelineIpc.reset(); } -bool MediaPipeline::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipeline::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaPipelineIpc->load(type, mimeType, url); + return m_mediaPipelineIpc->load(type, mimeType, url, isLive); } bool MediaPipeline::attachSource(const std::unique_ptr &source) @@ -252,11 +252,11 @@ bool MediaPipeline::allSourcesAttached() return m_mediaPipelineIpc->allSourcesAttached(); } -bool MediaPipeline::play() +bool MediaPipeline::play(bool &async) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaPipelineIpc->play(); + return m_mediaPipelineIpc->play(async); } bool MediaPipeline::pause() @@ -313,6 +313,16 @@ bool MediaPipeline::setImmediateOutput(int32_t sourceId, bool immediateOutput) return m_mediaPipelineIpc->setImmediateOutput(sourceId, immediateOutput); } +bool MediaPipeline::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + return m_mediaPipelineIpc->setReportDecodeErrors(sourceId, reportDecodeErrors); +} + +bool MediaPipeline::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + return m_mediaPipelineIpc->getQueuedFrames(sourceId, queuedFrames); +} + bool MediaPipeline::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { return m_mediaPipelineIpc->getImmediateOutput(sourceId, immediateOutput); @@ -545,28 +555,29 @@ bool MediaPipeline::flush(int32_t sourceId, bool resetTime, bool &async) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - std::unique_lock flushLock{m_flushMutex}; - if (m_mediaPipelineIpc->flush(sourceId, resetTime, async)) { + std::unique_lock flushLock{m_flushMutex}; + if (!m_mediaPipelineIpc->flush(sourceId, resetTime, async)) + { + return false; + } m_attachedSources.setFlushing(sourceId, true); - flushLock.unlock(); + } - // Clear all need datas for flushed source - std::lock_guard lock{m_needDataRequestMapMutex}; - for (auto it = m_needDataRequestMap.begin(); it != m_needDataRequestMap.end();) + // Clear all need datas for flushed source + std::lock_guard lock{m_needDataRequestMapMutex}; + for (auto it = m_needDataRequestMap.begin(); it != m_needDataRequestMap.end();) + { + if (it->second->sourceId == sourceId) { - if (it->second->sourceId == sourceId) - { - it = m_needDataRequestMap.erase(it); - } - else - { - ++it; - } + it = m_needDataRequestMap.erase(it); + } + else + { + ++it; } - return true; } - return false; + return true; } bool MediaPipeline::setSourcePosition(int32_t sourceId, int64_t position, bool resetTime, double appliedRate, @@ -626,6 +637,13 @@ bool MediaPipeline::switchSource(const std::unique_ptr &source) return m_mediaPipelineIpc->switchSource(source); } +bool MediaPipeline::getDuration(int64_t &duration) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + return m_mediaPipelineIpc->getDuration(duration); +} + void MediaPipeline::discardNeedDataRequest(uint32_t needDataRequestId) { // Find the needDataRequest for this needDataRequestId @@ -808,7 +826,7 @@ void MediaPipeline::notifyNeedMediaData(int32_t sourceId, size_t frameCount, uin RIALTO_CLIENT_LOG_INFO("NeedMediaData received in state != RUNNING, ignoring request id %u", requestId); break; } - m_needDataRequestMap[requestId] = needDataRequest; + m_needDataRequestMap[requestId] = std::move(needDataRequest); } std::shared_ptr client = m_mediaPipelineClient.lock(); @@ -870,6 +888,17 @@ void MediaPipeline::notifyBufferUnderflow(int32_t sourceId) } } +void MediaPipeline::notifyFirstFrameReceived(int32_t sourceId) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + std::shared_ptr client = m_mediaPipelineClient.lock(); + if (client) + { + client->notifyFirstFrameReceived(sourceId); + } +} + void MediaPipeline::notifyPlaybackError(int32_t sourceId, PlaybackError error) { RIALTO_CLIENT_LOG_DEBUG("entry:"); diff --git a/media/public/include/IMediaKeys.h b/media/public/include/IMediaKeys.h index 6408d0056..c2e4ced3d 100644 --- a/media/public/include/IMediaKeys.h +++ b/media/public/include/IMediaKeys.h @@ -110,13 +110,12 @@ class IMediaKeys * * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks - * @param[in] isLDL : Is this an LDL * @param[out] keySessionId: The key session id * * @retval an error status. */ virtual MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) = 0; + int32_t &keySessionId) = 0; /** * @brief Generates a licence request. @@ -130,11 +129,14 @@ class IMediaKeys * @param[in] keySessionId : The key session id for the session. * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter, + * so the default value is NOT_SPECIFIED. * * @retval an error status. */ - virtual MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) = 0; + virtual MediaKeyErrorStatus + generateRequest(int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState = LimitedDurationLicense::NOT_SPECIFIED) = 0; /** * @brief Loads an existing key session diff --git a/media/public/include/IMediaKeysCapabilities.h b/media/public/include/IMediaKeysCapabilities.h index 3e4ab10c7..73465ccf6 100644 --- a/media/public/include/IMediaKeysCapabilities.h +++ b/media/public/include/IMediaKeysCapabilities.h @@ -115,6 +115,17 @@ class IMediaKeysCapabilities * @retval true if server certificate is supported */ virtual bool isServerCertificateSupported(const std::string &keySystem) = 0; + + /** + * @brief Gets the robustness levels supported by the specified key system. + * + * @param[in] keySystem : The key system. + * @param[out] robustnessLevels : The supported robustness levels. + * + * @retval true if operation was successful + */ + virtual bool getSupportedRobustnessLevels(const std::string &keySystem, + std::vector &robustnessLevels) = 0; }; }; // namespace firebolt::rialto diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index 1064ccc1e..f084429a8 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -543,7 +543,7 @@ class IMediaPipeline * * @retval the media key session id. */ - const int32_t getMediaKeySessionId() const { return m_mediaKeySessionId; } + int32_t getMediaKeySessionId() const { return m_mediaKeySessionId; } /** * @brief Returns the key id. Empty if unencrypted. @@ -571,14 +571,14 @@ class IMediaPipeline * * @retval the initWithLast15 value. */ - const uint32_t getInitWithLast15() const { return m_initWithLast15; } + uint32_t getInitWithLast15() const { return m_initWithLast15; } /** * @brief Returns the segment alignment * * @retval the segment alignment */ - const SegmentAlignment getSegmentAlignment() const { return m_alignment; } + SegmentAlignment getSegmentAlignment() const { return m_alignment; } /** * @brief Gets the codec data @@ -602,7 +602,7 @@ class IMediaPipeline * * @retval if the encryption pattern has been set */ - const bool getEncryptionPattern(uint32_t &crypt, uint32_t &skip) const + bool getEncryptionPattern(uint32_t &crypt, uint32_t &skip) const { crypt = m_crypt; skip = m_skip; @@ -1072,8 +1072,9 @@ class IMediaPipeline * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. */ - virtual bool load(MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; /** * @brief Attaches a source stream to the backend. @@ -1119,16 +1120,15 @@ class IMediaPipeline /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the * media player client of playback state * IMediaPipelineClient::PlaybackState::PLAYING. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Pauses playback of the media. @@ -1228,6 +1228,30 @@ class IMediaPipeline */ virtual bool setImmediateOutput(int32_t sourceId, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous, it will set the "Report Decode Errors" property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * This method is synchronous, it gets the queued frames property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[out] queuedFrames : Get queued frames on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * @@ -1535,6 +1559,17 @@ class IMediaPipeline * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto diff --git a/media/public/include/IMediaPipelineClient.h b/media/public/include/IMediaPipelineClient.h index 0cbdc2af7..3334b578c 100644 --- a/media/public/include/IMediaPipelineClient.h +++ b/media/public/include/IMediaPipelineClient.h @@ -197,6 +197,15 @@ class IMediaPipelineClient */ virtual void notifyBufferUnderflow(int32_t sourceId) = 0; + /** + * @brief Notifies the client that the first frame has been received. + * + * Notification shall be sent whenever a video/audio first frame is received + * + * @param[in] sourceId : The id of the source that received the first frame + */ + virtual void notifyFirstFrameReceived(int32_t sourceId) = 0; + /** * @brief Notifies the client that a non-fatal error has occurred in the player. * diff --git a/media/public/include/MediaCommon.h b/media/public/include/MediaCommon.h index 92b544b8c..3388660d7 100644 --- a/media/public/include/MediaCommon.h +++ b/media/public/include/MediaCommon.h @@ -114,11 +114,12 @@ enum class MediaType */ enum class MediaSourceStatus { - OK, /**< Source data provided without error. */ - EOS, /**< Source reached the end of stream. */ - ERROR, /**< There was an error providing source data. */ - CODEC_CHANGED, /**< The codec has changed and the decoder must be reconfigured */ - NO_AVAILABLE_SAMPLES /**< Could not retrieve media samples. */ + OK, /**< Source data provided without error. */ + EOS, /**< Source reached the end of stream. */ + ERROR, /**< There was an error providing source data. */ + CODEC_CHANGED, /**< The codec has changed and the decoder must be reconfigured */ + NO_AVAILABLE_SAMPLES, /**< Could not retrieve media samples. */ + NO_SPACE_FOR_SAMPLES /**< Could not copy data to shared buffer space. */ }; /** @@ -292,7 +293,8 @@ enum class MediaKeyErrorStatus NOT_SUPPORTED, /**< The request parameters are not supported. */ INVALID_STATE, /**< The object is in an invalid state for the operation. */ INTERFACE_NOT_IMPLEMENTED, /**< The interface is not implemented. */ - BUFFER_TOO_SMALL /**< The size of the buffer is too small. */ + BUFFER_TOO_SMALL, /**< The size of the buffer is too small. */ + OUTPUT_RESTRICTED /**< HDCP output protection failure. */ }; /** @@ -452,7 +454,8 @@ struct CodecData enum class PlaybackError { UNKNOWN, - DECRYPTION, /* Player failed to decrypt a buffer and the frame has been dropped */ + DECRYPTION, /* Player failed to decrypt a buffer and the frame has been dropped */ + OUTPUT_PROTECTION /* HDCP output protection failure */ }; /** @@ -473,6 +476,16 @@ struct PlaybackInfo int64_t currentPosition{-1}; /**< The current playback position */ double volume{1.0}; /**< The current volume */ }; + +/** + * @brief Limited duration license state. + */ +enum class LimitedDurationLicense +{ + NOT_SPECIFIED, /**< The license duration is not specified */ + ENABLED, /**< The license has a limited duration */ + DISABLED /**< The license does not have a limited duration */ +}; } // namespace firebolt::rialto #endif // FIREBOLT_RIALTO_MEDIA_COMMON_H_ diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 98a77ca4c..448f36731 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -39,6 +39,7 @@ add_library( source/tasks/generic/DeepElementAdded.cpp source/tasks/generic/EnoughData.cpp source/tasks/generic/Eos.cpp + source/tasks/generic/FirstFrameReceived.cpp source/tasks/generic/FinishSetupSource.cpp source/tasks/generic/Flush.cpp source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -58,6 +59,7 @@ add_library( source/tasks/generic/SetMute.cpp source/tasks/generic/SetPlaybackRate.cpp source/tasks/generic/SetPosition.cpp + source/tasks/generic/SetReportDecodeErrors.cpp source/tasks/generic/SetSourcePosition.cpp source/tasks/generic/SetSubtitleOffset.cpp source/tasks/generic/SetStreamSyncMode.cpp @@ -97,6 +99,7 @@ add_library( source/GstGenericPlayer.cpp source/GstInitialiser.cpp source/GstLogForwarding.cpp + source/GstProfiler.cpp source/GstProtectionMetadata.cpp source/GstProtectionMetadataHelper.cpp source/GstSrc.cpp diff --git a/media/server/gstplayer/include/CapsBuilder.h b/media/server/gstplayer/include/CapsBuilder.h index 283f7ac04..0c9452ab9 100644 --- a/media/server/gstplayer/include/CapsBuilder.h +++ b/media/server/gstplayer/include/CapsBuilder.h @@ -31,8 +31,8 @@ namespace firebolt::rialto::server class MediaSourceCapsBuilder { public: - MediaSourceCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const firebolt::rialto::IMediaPipeline::MediaSourceAV &source); virtual ~MediaSourceCapsBuilder() = default; virtual GstCaps *buildCaps(); @@ -51,8 +51,8 @@ class MediaSourceCapsBuilder class MediaSourceAudioCapsBuilder : public MediaSourceCapsBuilder { public: - MediaSourceAudioCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceAudioCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceAudio &source); ~MediaSourceAudioCapsBuilder() override = default; GstCaps *buildCaps() override; @@ -72,8 +72,8 @@ class MediaSourceAudioCapsBuilder : public MediaSourceCapsBuilder class MediaSourceVideoCapsBuilder : public MediaSourceCapsBuilder { public: - MediaSourceVideoCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceVideoCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideo &source); ~MediaSourceVideoCapsBuilder() override = default; GstCaps *buildCaps() override; @@ -85,8 +85,8 @@ class MediaSourceVideoCapsBuilder : public MediaSourceCapsBuilder class MediaSourceVideoDolbyVisionCapsBuilder : public MediaSourceVideoCapsBuilder { public: - MediaSourceVideoDolbyVisionCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + MediaSourceVideoDolbyVisionCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideoDolbyVision &source); ~MediaSourceVideoDolbyVisionCapsBuilder() override = default; GstCaps *buildCaps() override; diff --git a/media/server/gstplayer/include/FlushOnPrerollController.h b/media/server/gstplayer/include/FlushOnPrerollController.h index 3b17dfaed..8ae19bfc3 100644 --- a/media/server/gstplayer/include/FlushOnPrerollController.h +++ b/media/server/gstplayer/include/FlushOnPrerollController.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_FLUSH_ONPREROLL_CONTROLLER_H_ #include "IFlushOnPrerollController.h" +#include #include #include #include @@ -37,13 +38,16 @@ class FlushOnPrerollController : public IFlushOnPrerollController FlushOnPrerollController() = default; ~FlushOnPrerollController() override = default; - bool shouldPostponeFlush(const MediaSourceType &type) const override; - void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) override; + void waitIfRequired(const MediaSourceType &type) override; + void setFlushing(const MediaSourceType &type) override; + void setPrerolling() override; void stateReached(const GstState &newPipelineState) override; + void setTargetState(const GstState &state) override; void reset() override; private: - mutable std::mutex m_mutex{}; + std::mutex m_mutex{}; + std::condition_variable m_conditionVariable{}; std::set m_flushingSources{}; std::optional m_targetState{std::nullopt}; bool m_isPrerolled{false}; diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index a2f214535..17cf6f741 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_GENERIC_PLAYER_CONTEXT_H_ #include "FlushOnPrerollController.h" +#include "IGstProfiler.h" #include "IGstSrc.h" #include "IRdkGstreamerUtilsWrapper.h" #include "ITimer.h" @@ -155,6 +156,11 @@ struct GenericPlayerContext */ std::optional pendingImmediateOutputForVideo{}; + /** + * @brief Pending report decode errors for MediaSourceType::VIDEO + */ + std::optional pendingReportDecodeErrorsForVideo{}; + /** * @brief Pending low latency */ @@ -267,10 +273,52 @@ struct GenericPlayerContext */ std::atomic_bool audioFadeEnabled{false}; + /** + * @brief The last known fade volume value used for PlaybackInfo messages. + * The "fade-volume" property must not be queried too frequently as it can cause decoder issues. + */ + std::atomic audioFadeVolume{1.0}; + /** * @brief Workaround for the gstreamer flush issue */ - FlushOnPrerollController flushOnPrerollController; + std::shared_ptr flushOnPrerollController{std::make_shared()}; + + /** + * @brief Flag used to check if the stream is live + * This is a workaround for Broadcom decoder issue with audio cuts during playback rate change. + */ + bool isLive{false}; + + /** + * @brief Profiler for player pipeline + */ + std::unique_ptr gstProfiler; + + /** + * @brief True when first audio frame has already been scheduled for the current audio source lifecycle. + */ + bool firstAudioFrameReceived{false}; + + /** + * @brief Fallback probe id for first audio frame detection on sink pad. + */ + gulong audioFirstFrameProbeId{0}; + + /** + * @brief Fallback sink pad that owns audio first frame probe. + */ + GstPad *audioFirstFrameProbePad{nullptr}; + + /** + * @brief The audio position set in the GstSegment. + */ + int64_t audioGstSegmentPosition{-1}; + + /** + * @brief Current position of the stream in nanoseconds. + */ + std::atomic streamPosition{-1}; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstDecryptorPrivate.h b/media/server/gstplayer/include/GstDecryptorPrivate.h index 77fb37662..6e8513c5a 100644 --- a/media/server/gstplayer/include/GstDecryptorPrivate.h +++ b/media/server/gstplayer/include/GstDecryptorPrivate.h @@ -93,6 +93,11 @@ class GstRialtoDecryptorPrivate */ std::unique_ptr m_metadataWrapper; + /** + * @brief The flag indicating if the output restricted error occurred in the previous decryption attempt. + */ + bool m_hdcpOutputRestricted{false}; + /** * @brief Creates the protection meta structure. * diff --git a/media/server/gstplayer/include/GstDispatcherThread.h b/media/server/gstplayer/include/GstDispatcherThread.h index 540f161ca..0db6fe2d9 100644 --- a/media/server/gstplayer/include/GstDispatcherThread.h +++ b/media/server/gstplayer/include/GstDispatcherThread.h @@ -34,6 +34,7 @@ class GstDispatcherThreadFactory : public IGstDispatcherThreadFactory ~GstDispatcherThreadFactory() override = default; std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const override; }; @@ -41,6 +42,7 @@ class GstDispatcherThread : public IGstDispatcherThread { public: GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper); ~GstDispatcherThread() override; @@ -58,6 +60,11 @@ class GstDispatcherThread : public IGstDispatcherThread */ IGstDispatcherThreadClient &m_client; + /** + * @brief The flush on preroll controller. + */ + std::shared_ptr m_flushOnPrerollController; + /** * @brief The gstreamer wrapper object. */ diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 22fd800e0..0281fadc3 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -28,6 +28,7 @@ #include "IGstGenericPlayer.h" #include "IGstGenericPlayerPrivate.h" #include "IGstInitialiser.h" +#include "IGstProfiler.h" #include "IGstProtectionMetadataHelperFactory.h" #include "IGstSrc.h" #include "IGstWrapper.h" @@ -36,6 +37,7 @@ #include "tasks/IGenericPlayerTaskFactory.h" #include "tasks/IPlayerTask.h" #include +#include #include #include #include @@ -59,7 +61,7 @@ class GstGenericPlayerFactory : public IGstGenericPlayerFactory std::unique_ptr createGstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) override; }; @@ -82,18 +84,20 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva * @param[in] gstInitialiser : The gst initialiser * @param[in] flushWatcher : The flush watcher * @param[in] gstSrcFactory : The gstreamer rialto src factory. + * @param[in] gstProfilerFactory : The gstreamer rialto profiler factory. * @param[in] timerFactory : The Timer factory * @param[in] taskFactory : The task factory * @param[in] workerThreadFactory : The worker thread factory * @param[in] gstDispatcherThreadFactory : The gst dispatcher thread factory */ GstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::shared_ptr &rdkGstreamerUtilsWrapper, const IGstInitialiser &gstInitialiser, std::unique_ptr &&flushWatcher, const std::shared_ptr &gstSrcFactory, + const std::shared_ptr &gstProfilerFactory, std::shared_ptr timerFactory, std::unique_ptr taskFactory, std::unique_ptr workerThreadFactory, @@ -108,7 +112,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void attachSource(const std::unique_ptr &mediaSource) override; void removeSource(const MediaSourceType &mediaSourceType) override; void allSourcesAttached() override; - void play() override; + void play(bool &async) override; void pause() override; void stop() override; void attachSamples(const IMediaPipeline::MediaSegmentVector &mediaSegments) override; @@ -118,8 +122,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void setEos(const firebolt::rialto::MediaSourceType &type) override; void setPlaybackRate(double rate) override; bool getPosition(std::int64_t &position) override; + bool getDuration(std::int64_t &duration) override; bool setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutput) override; + bool setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) override; bool getImmediateOutput(const MediaSourceType &mediaSourceType, bool &immediateOutput) override; + bool getQueuedFrames(uint32_t &queuedFrames) override; bool getStats(const MediaSourceType &mediaSourceType, uint64_t &renderedFrames, uint64_t &droppedFrames) override; void setVolume(double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType) override; bool getVolume(double &volume) override; @@ -150,9 +157,15 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void scheduleEnoughData(GstAppSrc *src) override; void scheduleAudioUnderflow() override; void scheduleVideoUnderflow() override; + void scheduleFirstVideoFrameReceived() override; + void scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) override; + void setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) override; + void clearAudioFirstFrameFallbackProbe() override; + void clearAudioFirstFrameFallbackProbeState() override; void scheduleAllSourcesAttached() override; bool setVideoSinkRectangle() override; bool setImmediateOutput() override; + bool setReportDecodeErrors() override; bool setShowVideoWindow() override; bool setLowLatency() override; bool setSync() override; @@ -162,16 +175,19 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva bool setBufferingLimit() override; bool setUseBuffering() override; void notifyNeedMediaData(const MediaSourceType mediaSource) override; + void notifyNeedMediaDataWithDelay(const MediaSourceType mediaSource) override; GstBuffer *createBuffer(const IMediaPipeline::MediaSegment &mediaSegment) const override; void attachData(const firebolt::rialto::MediaSourceType mediaType) override; void updateAudioCaps(int32_t rate, int32_t channels, const std::shared_ptr &codecData) override; void updateVideoCaps(int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData) override; void addAudioClippingToBuffer(GstBuffer *buffer, uint64_t clippingStart, uint64_t clippingEnd) const override; - bool changePipelineState(GstState newState) override; + GstStateChangeReturn changePipelineState(GstState newState) override; int64_t getPosition(GstElement *element) override; void startPositionReportingAndCheckAudioUnderflowTimer() override; void stopPositionReportingAndCheckAudioUnderflowTimer() override; + void startNotifyPlaybackInfoTimer() override; + void stopNotifyPlaybackInfoTimer() override; void startSubtitleClockResyncTimer() override; void stopSubtitleClockResyncTimer() override; void stopWorkerThread() override; @@ -185,15 +201,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void addAutoAudioSinkChild(GObject *object) override; void removeAutoVideoSinkChild(GObject *object) override; void removeAutoAudioSinkChild(GObject *object) override; - void setPlaybinFlags(bool enableAudio = true) override; - void pushSampleIfRequired(GstElement *source, const std::string &typeStr) override; bool reattachSource(const std::unique_ptr &source) override; bool hasSourceType(const MediaSourceType &mediaSourceType) const override; GstElement *getSink(const MediaSourceType &mediaSourceType) const override; void setSourceFlushed(const MediaSourceType &mediaSourceType) override; bool isAsync(const MediaSourceType &mediaSourceType) const; - void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) override; - void executePostponedFlushes() override; void notifyPlaybackInfo() override; private: @@ -324,6 +336,61 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva std::optional createAudioAttributes(const std::unique_ptr &source) const; + /** + * @brief Configures audio caps based on audio attributes. + * Called by worker thread only! + * + * @param[in] pAttrib : The audio attributes. + * @param[out] audioaac : Set to true if AAC, false otherwise. + * @param[in] svpenabled : Whether SVP is enabled. + * @param[in,out] appsrcCaps : The caps to configure. + */ + void configAudioCap(firebolt::rialto::wrappers::AudioAttributesPrivate *pAttrib, bool *audioaac, bool svpenabled, + GstCaps **appsrcCaps); + + /** + * @brief Halts audio playback by setting playsink to READY and decodebin to PAUSED. + * Called by worker thread only! + */ + void haltAudioPlayback(); + + /** + * @brief Resumes audio playback by syncing playsink and decodebin with parent. + * Called by worker thread only! + */ + void resumeAudioPlayback(); + + /** + * @brief First-time codec switch from AC3 to AAC when no decoder exists yet. + * Called by worker thread only! + * + * @param[in] newAudioCaps : The new audio caps to apply. + */ + void firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps); + + /** + * @brief Switches the audio codec by unlinking old parser/decoder and linking new ones. + * Called by worker thread only! + * + * @param[in] isAudioAAC : Whether the new codec is AAC. + * @param[in] newAudioCaps : The new audio caps to apply. + * + * @retval true if codec was switched, false if same codec. + */ + bool switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps); + + /** + * @brief Top-level audio track codec channel switch, ported from rdk_gstreamer_utils_soc. + * Called by worker thread only! + */ + bool performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, bool *audioaac, + bool svpenabled, GstElement *aSrc, bool *ret); + /** * @brief Sets text track position before pushing data * @@ -340,6 +407,21 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ void pushAdditionalSegmentIfRequired(GstElement *source); + /** + * @brief Sets the audio and video flags on the pipeline based on the input. + * + * @param[in] enableAudio : Whether to enable audio flags. + */ + void setPlaybinFlags(bool enableAudio = true); + + /** + * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. + * + * @param[in] source : The Gst Source element, that should receive new sample + * @param[in] mediaSourceType : The media source type + */ + void pushSampleIfRequired(GstElement *source, const MediaSourceType &mediaSourceType); + private: /** * @brief The player context. @@ -366,6 +448,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ std::shared_ptr m_rdkGstreamerUtilsWrapper; + /** + * @brief Factory creating gst profilers + */ + std::shared_ptr m_gstProfilerFactory; + /** * @brief Thread for handling player tasks. */ @@ -422,11 +509,6 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva * @brief The object used to check flushing state for all sources */ std::unique_ptr m_flushWatcher; - - /** - * @brief The postponed flush tasks - */ - std::vector> m_postponedFlushes{}; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstPlayerTypes.h b/media/server/gstplayer/include/GstPlayerTypes.h new file mode 100644 index 000000000..210c3f955 --- /dev/null +++ b/media/server/gstplayer/include/GstPlayerTypes.h @@ -0,0 +1,32 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_GST_PLAYER_TYPES_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PLAYER_TYPES_H_ + +namespace firebolt::rialto::server +{ +enum class AudioFirstFrameAction +{ + CLEAR_PROBE, + CLEAR_PROBE_STATE +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PLAYER_TYPES_H_ diff --git a/media/server/gstplayer/include/GstProfiler.h b/media/server/gstplayer/include/GstProfiler.h new file mode 100644 index 000000000..f767918aa --- /dev/null +++ b/media/server/gstplayer/include/GstProfiler.h @@ -0,0 +1,137 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ + +#include "IGlibWrapper.h" +#include "IGstProfiler.h" +#include "IGstProfilerPrivate.h" +#include "IGstWrapper.h" +#include "IProfiler.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerFactory : public IGstProfilerFactory +{ +public: + std::unique_ptr createGstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const override; + + static std::weak_ptr m_factory; +}; + +class GstProfiler : public IGstProfiler, public IGstProfilerPrivate +{ +public: + using RecordId = IGstProfiler::RecordId; + + GstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper); + ~GstProfiler() override; + + std::optional createRecord(const std::string &stage) override; + std::optional createRecord(const std::string &stage, const std::string &info) override; + + void scheduleGstElementRecord(GstElement *element) override; + std::vector getRecords() const override; + + void logRecord(const RecordId id) override; + void dumpToFile() const override; + void logPipelineSummary() const override; + +private: + using Clock = std::chrono::system_clock; + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + using IProfiler = firebolt::rialto::common::IProfiler; + + struct PipelineStageTimestamps + { + std::optional pipelineCreated; + std::optional allSourcesAttached; + + std::optional firstSegmentReceivedVideo; + std::optional firstSegmentReceivedAudio; + + std::optional sourceFbExitVideo; + std::optional sourceFbExitAudio; + + std::optional decryptorFbExitVideo; + std::optional decryptorFbExitAudio; + + std::optional decoderFbExitVideo; + std::optional decoderFbExitAudio; + + std::optional pipelinePaused; + std::optional pipelinePlaying; + }; + + struct PipelineMetrics + { + std::optional preparation; + std::optional videoDownload; + std::optional audioDownload; + std::optional videoSource; + std::optional audioSource; + std::optional videoDecryption; + std::optional audioDecryption; + std::optional videoDecode; + std::optional audioDecode; + std::optional preRoll; + std::optional play; + std::optional total; + std::optional totalWithoutApp; + }; + + std::optional getFirstBufferExitStage(GstElement *element); + const gchar *getElementClassMetadata(GstElement *element); + std::string deriveElementInfoFromName(const std::string &name) const; + GstPadProbeReturn handleProbeCb(GstPad *pad, GstPadProbeInfo *info) override; + void removeProbeCtx(GstPad *pad); + + std::optional calculateMetrics() const; + + struct ProbeCtx + { + std::shared_ptr profiler; + std::string stage; + std::string info; + GstPad *pad; + gulong id; + }; + + GstElement *m_pipeline = nullptr; + std::shared_ptr m_gstWrapper; + std::shared_ptr m_glibWrapper; + std::shared_ptr m_profiler; + std::vector m_probeCtxs; + bool m_enabled = false; +}; +} // namespace firebolt::rialto::server +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_H_ diff --git a/media/server/gstplayer/include/IFlushOnPrerollController.h b/media/server/gstplayer/include/IFlushOnPrerollController.h index 64379b7af..76469a523 100644 --- a/media/server/gstplayer/include/IFlushOnPrerollController.h +++ b/media/server/gstplayer/include/IFlushOnPrerollController.h @@ -34,9 +34,11 @@ class IFlushOnPrerollController public: virtual ~IFlushOnPrerollController() = default; - virtual bool shouldPostponeFlush(const MediaSourceType &type) const = 0; - virtual void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) = 0; + virtual void waitIfRequired(const MediaSourceType &type) = 0; + virtual void setFlushing(const MediaSourceType &type) = 0; + virtual void setPrerolling() = 0; virtual void stateReached(const GstState &newPipelineState) = 0; + virtual void setTargetState(const GstState &state) = 0; virtual void reset() = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/IGstDispatcherThread.h b/media/server/gstplayer/include/IGstDispatcherThread.h index 15d6083e1..a72e2a0ad 100644 --- a/media/server/gstplayer/include/IGstDispatcherThread.h +++ b/media/server/gstplayer/include/IGstDispatcherThread.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ #define FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ +#include "IFlushOnPrerollController.h" #include "IGstDispatcherThreadClient.h" #include "IGstWrapper.h" #include @@ -36,6 +37,7 @@ class IGstDispatcherThreadFactory virtual std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const = 0; }; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 1848c8960..2fec650f1 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_GST_GENERIC_PLAYER_PRIVATE_H_ #define FIREBOLT_RIALTO_SERVER_I_GST_GENERIC_PLAYER_PRIVATE_H_ +#include "GstPlayerTypes.h" #include "IMediaPipeline.h" #include @@ -61,6 +62,34 @@ class IGstGenericPlayerPrivate */ virtual void scheduleVideoUnderflow() = 0; + /** + * @brief Schedules first video frame received task. Called by the worker thread. + */ + virtual void scheduleFirstVideoFrameReceived() = 0; + + /** + * @brief Schedules first audio frame received task. Called by the Gstreamer thread. + */ + virtual void scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) = 0; + + /** + * @brief Stores audio first-frame fallback probe state. + * + * @param[in] pad : sink pad with installed probe + * @param[in] id : probe id + */ + virtual void setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) = 0; + + /** + * @brief Removes and clears audio first-frame fallback probe state. + */ + virtual void clearAudioFirstFrameFallbackProbe() = 0; + + /** + * @brief Clears audio first-frame fallback probe state without removing the probe. + */ + virtual void clearAudioFirstFrameFallbackProbeState() = 0; + /** * @brief Schedules all sources attached task. Called by the worker thread. */ @@ -80,6 +109,13 @@ class IGstGenericPlayerPrivate */ virtual bool setImmediateOutput() = 0; + /** + * @brief Sets report decode error. Called by the worker thread. + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors() = 0; + /** * @brief Sets the low latency property. Called by the worker thread. * @@ -141,6 +177,11 @@ class IGstGenericPlayerPrivate */ virtual void notifyNeedMediaData(const MediaSourceType mediaSource) = 0; + /** + * @brief Sends NeedMediaData notification with a delay. Called by the worker thread. + */ + virtual void notifyNeedMediaDataWithDelay(const MediaSourceType mediaSource) = 0; + /** * @brief Constructs a new buffer with data from media segment. Does not perform decryption. * Called by the worker thread. @@ -174,9 +215,9 @@ class IGstGenericPlayerPrivate * * @param[in] newState : The desired state. * - * @retval true on success. + * @retval state change status */ - virtual bool changePipelineState(GstState newState) = 0; + virtual GstStateChangeReturn changePipelineState(GstState newState) = 0; /** * @brief Gets the current position of the element @@ -197,6 +238,16 @@ class IGstGenericPlayerPrivate */ virtual void stopPositionReportingAndCheckAudioUnderflowTimer() = 0; + /** + * @brief Starts notify playback info timer. Called by the worker thread. + */ + virtual void startNotifyPlaybackInfoTimer() = 0; + + /** + * @brief Stops notify playback info timer. Called by the worker thread. + */ + virtual void stopNotifyPlaybackInfoTimer() = 0; + /** * @brief Starts subtitle clock resync. Called by the worker thread. */ @@ -271,21 +322,6 @@ class IGstGenericPlayerPrivate */ virtual GstElement *getSink(const MediaSourceType &mediaSourceType) const = 0; - /** - * @brief Sets the audio and video flags on the pipeline based on the input. - * - * @param[in] enableAudio : Whether to enable audio flags. - */ - virtual void setPlaybinFlags(bool enableAudio) = 0; - - /** - * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. - * - * @param[in] source : The Gst Source element, that should receive new sample - * @param[in] typeStr : The media source type string - */ - virtual void pushSampleIfRequired(GstElement *source, const std::string &typeStr) = 0; - /** * @brief Reattaches source (or switches it) * @@ -311,19 +347,6 @@ class IGstGenericPlayerPrivate */ virtual void setSourceFlushed(const MediaSourceType &mediaSourceType) = 0; - /** - * @brief Postpones flush for the given source type - * - * @param[in] mediaSourceType : the source type that has been flushed - * @param[in] resetTime : whether to reset the time after flush - */ - virtual void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) = 0; - - /** - * @brief Queues postponed flushes for execution - */ - virtual void executePostponedFlushes() = 0; - /** * @brief Sends PlaybackInfo notification. Called by the worker thread. */ diff --git a/media/server/gstplayer/include/IGstProfiler.h b/media/server/gstplayer/include/IGstProfiler.h new file mode 100644 index 000000000..8ea182005 --- /dev/null +++ b/media/server/gstplayer/include/IGstProfiler.h @@ -0,0 +1,75 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ +#define FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ + +#include "IGlibWrapper.h" +#include "IGstWrapper.h" +#include "IProfiler.h" + +#include + +#include +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class IGstProfiler; + +class IGstProfilerFactory +{ +public: + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + + IGstProfilerFactory() = default; + virtual ~IGstProfilerFactory() = default; + + static std::shared_ptr getFactory(); + + virtual std::unique_ptr createGstProfiler(GstElement *pipeline, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const = 0; +}; + +class IGstProfiler +{ +public: + using RecordId = std::uint64_t; + using Record = firebolt::rialto::common::IProfiler::Record; + + virtual ~IGstProfiler() = default; + + virtual std::optional createRecord(const std::string &stage) = 0; + virtual std::optional createRecord(const std::string &stage, const std::string &info) = 0; + + virtual void scheduleGstElementRecord(GstElement *element) = 0; + virtual std::vector getRecords() const = 0; + + virtual void logRecord(RecordId id) = 0; + virtual void dumpToFile() const = 0; + virtual void logPipelineSummary() const = 0; +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_H_ diff --git a/media/server/gstplayer/include/IGstProfilerPrivate.h b/media/server/gstplayer/include/IGstProfilerPrivate.h new file mode 100644 index 000000000..c5e5ccfc1 --- /dev/null +++ b/media/server/gstplayer/include/IGstProfilerPrivate.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ +#define FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ + +#include + +namespace firebolt::rialto::server +{ +class IGstProfilerPrivate +{ +public: + IGstProfilerPrivate() = default; + virtual ~IGstProfilerPrivate() = default; + + IGstProfilerPrivate(const IGstProfilerPrivate &) = delete; + IGstProfilerPrivate &operator=(const IGstProfilerPrivate &) = delete; + IGstProfilerPrivate(IGstProfilerPrivate &&) = delete; + IGstProfilerPrivate &operator=(IGstProfilerPrivate &&) = delete; + + /** + * @brief Handles pad probe callback. + * + * @param[in] pad : Pad where the probe is attached. + * @param[in] info : Probe info. + * + * @retval The probe return code. + */ + virtual GstPadProbeReturn handleProbeCb(GstPad *pad, GstPadProbeInfo *info) = 0; +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_I_GST_PROFILER_PRIVATE_H_ diff --git a/media/server/gstplayer/include/Utils.h b/media/server/gstplayer/include/Utils.h index 81037266d..c768bd624 100644 --- a/media/server/gstplayer/include/Utils.h +++ b/media/server/gstplayer/include/Utils.h @@ -32,6 +32,7 @@ namespace firebolt::rialto::server bool isVideoDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); @@ -40,6 +41,8 @@ bool isAudio(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstEleme bool isVideo(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); std::optional getUnderflowSignalName(const firebolt::rialto::wrappers::IGlibWrapper &glibWrapper, GstElement *element); +std::optional getFirstFrameSignalName(const firebolt::rialto::wrappers::IGlibWrapper &glibWrapper, + GstElement *element); GstCaps *createCapsFromMediaSource(const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::unique_ptr &source); diff --git a/media/server/gstplayer/include/WebAudioPlayerContext.h b/media/server/gstplayer/include/WebAudioPlayerContext.h index 3fda093c7..6394fe8c6 100644 --- a/media/server/gstplayer/include/WebAudioPlayerContext.h +++ b/media/server/gstplayer/include/WebAudioPlayerContext.h @@ -66,6 +66,11 @@ struct WebAudioPlayerContext */ uint32_t lastBytesWritten{}; + /** + * @brief Counter that increments each time a write operation completes. + */ + uint32_t writeCompletionCounter{}; + /** * @brief The number of bytes per sample. */ diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 527261652..9fcff6517 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_I_GENERIC_PLAYER_TASK_FACTORY_H_ #include "GenericPlayerContext.h" +#include "GstPlayerTypes.h" #include "IDataReader.h" #include "IFlushWatcher.h" #include "IGstGenericPlayerPrivate.h" @@ -389,19 +390,32 @@ class IGenericPlayerTaskFactory virtual std::unique_ptr createUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, bool underflowEnabled, MediaSourceType sourceType) const = 0; + /** + * @brief Creates a FirstFrameReceived task. + * + * @param[in] context : The GstGenericPlayer context + * @param[in] player : The GstPlayer instance + * @param[in] sourceType : Source type (audio or video). + * + * @retval the new FirstFrameReceived task instance. + */ + virtual std::unique_ptr + createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction = AudioFirstFrameAction::CLEAR_PROBE) const = 0; + /** * @brief Creates an UpdatePlaybackGroup task. * * @param[in] context : The GstGenericPlayer context * @param[in] player : The GstGenericPlayer instance * @param[in] typefind : The typefind element. - * @param[in] caps : The GstCaps of added element + * @param[in] caps : The GstCaps of added element. * * @retval the new UpdatePlaybackGroup task instance. */ virtual std::unique_ptr createUpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - GstElement *typefind, const GstCaps *caps) const = 0; + GstElement *typefind, GstCaps *caps) const = 0; /** * @brief Creates a RenderFrame task. @@ -429,12 +443,13 @@ class IGenericPlayerTaskFactory * @param[in] context : The GstPlayer context * @param[in] type : The media source type to flush * @param[in] resetTime : True if time should be reset + * @param[in] isAsync : True if flushed source is asynchronous * * @retval the new Flush task instance. */ virtual std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const = 0; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const = 0; /** * @brief Creates a SetSourcePosition task. @@ -494,6 +509,21 @@ class IGenericPlayerTaskFactory const firebolt::rialto::MediaSourceType &type, bool immediateOutput) const = 0; + /** + * @brief Creates a SetReportDecodeErrors task. + * + * @param[in] context : The GstPlayer context + * @param[in] player : The GstPlayer instance + * @param[in] type : The media source type + * @param[in] reportDecodeErrors : the value to set for report decode error + * + * @retval the new SetReportDecodeErrors task instance. + */ + virtual std::unique_ptr createSetReportDecodeErrors(GenericPlayerContext &context, + IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const = 0; + /** * @brief Creates a SetBufferingLimit task. * diff --git a/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h b/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h index 3f0122320..919ee1de4 100644 --- a/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h +++ b/media/server/gstplayer/include/tasks/generic/CheckAudioUnderflow.h @@ -33,7 +33,7 @@ class CheckAudioUnderflow : public IPlayerTask { public: CheckAudioUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper); + const std::shared_ptr &gstWrapper); ~CheckAudioUnderflow() override = default; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/Eos.h b/media/server/gstplayer/include/tasks/generic/Eos.h index da51c6741..b8176c7dc 100644 --- a/media/server/gstplayer/include/tasks/generic/Eos.h +++ b/media/server/gstplayer/include/tasks/generic/Eos.h @@ -33,7 +33,7 @@ class Eos : public IPlayerTask { public: Eos(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, + const std::shared_ptr &gstWrapper, const firebolt::rialto::MediaSourceType &type); ~Eos() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h b/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h new file mode 100644 index 000000000..59b3ebd0c --- /dev/null +++ b/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_ +#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_ + +#include "GenericPlayerContext.h" +#include "GstPlayerTypes.h" +#include "IGstGenericPlayerClient.h" +#include "IGstGenericPlayerPrivate.h" +#include "IPlayerTask.h" + +namespace firebolt::rialto::server::tasks::generic +{ +class FirstFrameReceived : public IPlayerTask +{ +public: + FirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, + MediaSourceType sourceType, + AudioFirstFrameAction audioAction = AudioFirstFrameAction::CLEAR_PROBE); + ~FirstFrameReceived() override; + + void execute() const override; + +private: + GenericPlayerContext &m_context; + IGstGenericPlayerPrivate &m_player; + IGstGenericPlayerClient *m_gstPlayerClient; + MediaSourceType m_sourceType; + AudioFirstFrameAction m_audioAction; +}; +} // namespace firebolt::rialto::server::tasks::generic + +#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_ diff --git a/media/server/gstplayer/include/tasks/generic/Flush.h b/media/server/gstplayer/include/tasks/generic/Flush.h index 6345b1d00..f3729068e 100644 --- a/media/server/gstplayer/include/tasks/generic/Flush.h +++ b/media/server/gstplayer/include/tasks/generic/Flush.h @@ -33,8 +33,8 @@ class Flush : public IPlayerTask { public: Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime); + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync); ~Flush() override; void execute() const override; @@ -45,6 +45,7 @@ class Flush : public IPlayerTask std::shared_ptr m_gstWrapper; MediaSourceType m_type; bool m_resetTime; + bool m_isAsync; }; } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 49ec0db1b..1363425a4 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -101,15 +101,18 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory IGstGenericPlayerPrivate &player) const override; std::unique_ptr createUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, bool underflowEnable, MediaSourceType sourceType) const override; + std::unique_ptr + createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction = AudioFirstFrameAction::CLEAR_PROBE) const override; std::unique_ptr createUpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, GstElement *typefind, - const GstCaps *caps) const override; + GstCaps *caps) const override; std::unique_ptr createRenderFrame(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const override; std::unique_ptr createPing(std::unique_ptr &&heartbeatHandler) const override; std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const override; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const override; std::unique_ptr createSetSourcePosition(GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, bool resetTime, double appliedRate, @@ -122,6 +125,10 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory std::unique_ptr createSetImmediateOutput(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, bool immediateOutput) const override; + std::unique_ptr createSetReportDecodeErrors(GenericPlayerContext &context, + IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const override; std::unique_ptr createSetBufferingLimit(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, std::uint32_t limit) const override; std::unique_ptr createSetUseBuffering(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, diff --git a/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h b/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h index 4f614172a..b21285edd 100644 --- a/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h +++ b/media/server/gstplayer/include/tasks/generic/ProcessAudioGap.h @@ -36,7 +36,7 @@ class ProcessAudioGap : public IPlayerTask ProcessAudioGap(GenericPlayerContext &context, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, - const std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, std::int64_t position, std::uint32_t duration, std::int64_t discontinuityGap, bool audioAac); ~ProcessAudioGap() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/RemoveSource.h b/media/server/gstplayer/include/tasks/generic/RemoveSource.h index 7d1923ff6..3ffc4bd53 100644 --- a/media/server/gstplayer/include/tasks/generic/RemoveSource.h +++ b/media/server/gstplayer/include/tasks/generic/RemoveSource.h @@ -33,7 +33,7 @@ class RemoveSource : public IPlayerTask { public: RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type); + const std::shared_ptr &gstWrapper, const MediaSourceType &type); ~RemoveSource() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetMute.h b/media/server/gstplayer/include/tasks/generic/SetMute.h index 17892dcc1..f2662d819 100644 --- a/media/server/gstplayer/include/tasks/generic/SetMute.h +++ b/media/server/gstplayer/include/tasks/generic/SetMute.h @@ -33,8 +33,8 @@ class SetMute : public IPlayerTask { public: SetMute(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const MediaSourceType &mediaSourceType, bool mute); ~SetMute() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h b/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h index 8adbddcf7..7f121fc7f 100644 --- a/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h +++ b/media/server/gstplayer/include/tasks/generic/SetPlaybackRate.h @@ -31,8 +31,9 @@ namespace firebolt::rialto::server::tasks::generic class SetPlaybackRate : public IPlayerTask { public: - SetPlaybackRate(GenericPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, double rate); + SetPlaybackRate(GenericPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, double rate); ~SetPlaybackRate() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetPosition.h b/media/server/gstplayer/include/tasks/generic/SetPosition.h index 0be5a265f..ec90760e2 100644 --- a/media/server/gstplayer/include/tasks/generic/SetPosition.h +++ b/media/server/gstplayer/include/tasks/generic/SetPosition.h @@ -34,7 +34,7 @@ class SetPosition : public IPlayerTask { public: SetPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, std::int64_t position); + const std::shared_ptr &gstWrapper, std::int64_t position); ~SetPosition() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h b/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h new file mode 100644 index 000000000..9cf30b310 --- /dev/null +++ b/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.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 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ +#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ + +#include "GenericPlayerContext.h" +#include "IGlibWrapper.h" +#include "IGstGenericPlayerPrivate.h" +#include "IGstWrapper.h" +#include "IPlayerTask.h" + +#include + +namespace firebolt::rialto::server::tasks::generic +{ +class SetReportDecodeErrors : public IPlayerTask +{ +public: + explicit SetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const MediaSourceType &type, bool reportDecodeErrors); + ~SetReportDecodeErrors() override; + void execute() const override; + +private: + GenericPlayerContext &m_context; + IGstGenericPlayerPrivate &m_player; + const MediaSourceType m_type; + bool m_reportDecodeErrors; +}; +} // namespace firebolt::rialto::server::tasks::generic + +#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ diff --git a/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h b/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h index b0554d310..bcb866fb1 100644 --- a/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h +++ b/media/server/gstplayer/include/tasks/generic/SetTextTrackIdentifier.h @@ -33,7 +33,7 @@ class SetTextTrackIdentifier : public IPlayerTask { public: SetTextTrackIdentifier(GenericPlayerContext &context, - std::shared_ptr glibWrapper, + const std::shared_ptr &glibWrapper, const std::string &textTrackIdentifier); ~SetTextTrackIdentifier() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetVolume.h b/media/server/gstplayer/include/tasks/generic/SetVolume.h index 202dac828..416cbfa19 100644 --- a/media/server/gstplayer/include/tasks/generic/SetVolume.h +++ b/media/server/gstplayer/include/tasks/generic/SetVolume.h @@ -33,9 +33,9 @@ class SetVolume : public IPlayerTask { public: SetVolume(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, - std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType); ~SetVolume() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/SetupElement.h b/media/server/gstplayer/include/tasks/generic/SetupElement.h index c50beddcf..c41e46274 100644 --- a/media/server/gstplayer/include/tasks/generic/SetupElement.h +++ b/media/server/gstplayer/include/tasks/generic/SetupElement.h @@ -33,8 +33,9 @@ namespace firebolt::rialto::server::tasks::generic class SetupElement : public IPlayerTask { public: - SetupElement(GenericPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + SetupElement(GenericPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, IGstGenericPlayerPrivate &player, GstElement *element); ~SetupElement() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h b/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h index f1cb25000..febdae2fd 100644 --- a/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h +++ b/media/server/gstplayer/include/tasks/generic/UpdatePlaybackGroup.h @@ -34,9 +34,9 @@ class UpdatePlaybackGroup : public IPlayerTask { public: UpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, GstElement *typefind, - const GstCaps *caps); + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + GstElement *typefind, GstCaps *caps); ~UpdatePlaybackGroup() override; void execute() const override; @@ -46,7 +46,7 @@ class UpdatePlaybackGroup : public IPlayerTask std::shared_ptr m_gstWrapper; std::shared_ptr m_glibWrapper; GstElement *m_typefind; - const GstCaps *m_caps; + GstCaps *m_caps; }; } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/include/tasks/webAudio/Eos.h b/media/server/gstplayer/include/tasks/webAudio/Eos.h index 230628fa4..2d7605bc6 100644 --- a/media/server/gstplayer/include/tasks/webAudio/Eos.h +++ b/media/server/gstplayer/include/tasks/webAudio/Eos.h @@ -32,7 +32,7 @@ namespace firebolt::rialto::server::tasks::webaudio class Eos : public IPlayerTask { public: - Eos(WebAudioPlayerContext &context, std::shared_ptr gstWrapper); + Eos(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper); ~Eos() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h b/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h index a45ccc97f..2820523de 100644 --- a/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h +++ b/media/server/gstplayer/include/tasks/webAudio/HandleBusMessage.h @@ -35,8 +35,8 @@ class HandleBusMessage : public IPlayerTask { public: HandleBusMessage(WebAudioPlayerContext &context, IGstWebAudioPlayerPrivate &player, IGstWebAudioPlayerClient *client, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, GstMessage *message); + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, GstMessage *message); ~HandleBusMessage() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/SetCaps.h b/media/server/gstplayer/include/tasks/webAudio/SetCaps.h index d4b7e76b1..0d74701de 100644 --- a/media/server/gstplayer/include/tasks/webAudio/SetCaps.h +++ b/media/server/gstplayer/include/tasks/webAudio/SetCaps.h @@ -32,9 +32,9 @@ namespace firebolt::rialto::server::tasks::webaudio class SetCaps : public IPlayerTask { public: - SetCaps(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const std::string &audioMimeType, - std::weak_ptr config); + SetCaps(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::string &audioMimeType, std::weak_ptr config); ~SetCaps() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/SetVolume.h b/media/server/gstplayer/include/tasks/webAudio/SetVolume.h index eddf127d7..42d34cfe5 100644 --- a/media/server/gstplayer/include/tasks/webAudio/SetVolume.h +++ b/media/server/gstplayer/include/tasks/webAudio/SetVolume.h @@ -30,8 +30,8 @@ namespace firebolt::rialto::server::tasks::webaudio class SetVolume : public IPlayerTask { public: - SetVolume(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - double volume); + SetVolume(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, double volume); ~SetVolume() override; void execute() const override; diff --git a/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h b/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h index c87e0a2ea..8588d811e 100644 --- a/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h +++ b/media/server/gstplayer/include/tasks/webAudio/WriteBuffer.h @@ -32,8 +32,9 @@ namespace firebolt::rialto::server::tasks::webaudio class WriteBuffer : public IPlayerTask { public: - WriteBuffer(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - uint8_t *mainPtr, uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength); + WriteBuffer(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, uint8_t *mainPtr, + uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength); ~WriteBuffer() override; void execute() const override; diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index e01edd364..6664f4b66 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -59,12 +59,13 @@ class IGstGenericPlayerFactory * @param[in] decryptionService : The decryption service. * @param[in] type : The media type the gstreamer player shall support. * @param[in] videoRequirements : The video requirements for the playback. + * @param[in] isLive : Indicates if the media is live. * * @retval the new player instance or null on error. */ virtual std::unique_ptr createGstGenericPlayer(IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) = 0; }; @@ -89,7 +90,7 @@ class IGstGenericPlayer virtual void attachSource(const std::unique_ptr &mediaSource) = 0; /** - * @brief Unattaches a source. + * @brief Removes a source from gstreamer. * * @param[in] mediaSourceType : The media source type. * @@ -105,14 +106,15 @@ class IGstGenericPlayer /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the - * media player client of playback state PlaybackState::PLAYING. + * media player client of playback state + * IMediaPipelineClient::PlaybackState::PLAYING. + * + * @param[out] async : True if play method call is asynchronous * + * @retval true on success. */ - virtual void play() = 0; + virtual void play(bool &async) = 0; /** * @brief Pauses playback of the media. @@ -193,6 +195,15 @@ class IGstGenericPlayer */ virtual bool getPosition(std::int64_t &position) = 0; + /** + * @brief Get the playback duration in nanoseconds. + * + * @param[out] duration : The playback duration in nanoseconds. + * + * @retval True on success + */ + virtual bool getDuration(std::int64_t &duration) = 0; + /** * @brief Sets the "Immediate Output" property for this source. * @@ -203,6 +214,25 @@ class IGstGenericPlayer */ virtual bool setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Error" property for this source. + * + * @param[in] mediaSourceType : The media source type + * @param[in] reportDecodeErrors : Set report decode error + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * @param[out] queuedFrames : Get queued frames mode on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * diff --git a/media/server/gstplayer/interface/IGstGenericPlayerClient.h b/media/server/gstplayer/interface/IGstGenericPlayerClient.h index 850f727a4..eaa7c52a4 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayerClient.h +++ b/media/server/gstplayer/interface/IGstGenericPlayerClient.h @@ -87,6 +87,26 @@ class IGstGenericPlayerClient */ virtual bool notifyNeedMediaData(MediaSourceType mediaSourceType) = 0; + /** + * @brief Notifies the client that we need media data with a delay. + * + * This method notifies the client that we need media data from the + * client. This is only used when Media Source Extensions are used. + * In that case media is read by JavaScript and buffered by the + * browser before being passed to this API for decoding. + * + * You cannot request data if a data request is currently pending. + * + * The frames the client sends should meet the criteria: + * numFramesSent <= frameCount + * numBytesSent <= maxBytes + * + * @param[in] mediaSourceType : The media type of source to read data from. + * + * @retval True on success. + */ + virtual bool notifyNeedMediaDataWithDelay(MediaSourceType mediaSourceType) = 0; + /** * @brief Notifies the client of the current playback position. * @@ -150,6 +170,15 @@ class IGstGenericPlayerClient */ virtual void notifyBufferUnderflow(MediaSourceType mediaSourceType) = 0; + /** + * @brief Notifies the client that the first frame has been received. + * + * Notification shall be sent whenever a video/audio first frame is received + * + * @param[in] mediaSourceType : The type of the source that received the first frame + */ + virtual void notifyFirstFrameReceived(MediaSourceType mediaSourceType) = 0; + /** * @brief Notifies the client that a non-fatal error has occurred in the player. * diff --git a/media/server/gstplayer/source/CapsBuilder.cpp b/media/server/gstplayer/source/CapsBuilder.cpp index 4491b3656..647b0353b 100644 --- a/media/server/gstplayer/source/CapsBuilder.cpp +++ b/media/server/gstplayer/source/CapsBuilder.cpp @@ -24,8 +24,8 @@ namespace firebolt::rialto::server { -MediaSourceCapsBuilder::MediaSourceCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, +MediaSourceCapsBuilder::MediaSourceCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const firebolt::rialto::IMediaPipeline::MediaSourceAV &source) : m_gstWrapper(gstWrapper), m_glibWrapper(glibWrapper), m_attachedSource(source) { @@ -92,8 +92,9 @@ void MediaSourceCapsBuilder::addStreamFormatToCaps(GstCaps *caps) const } MediaSourceAudioCapsBuilder::MediaSourceAudioCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const IMediaPipeline::MediaSourceAudio &source) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const IMediaPipeline::MediaSourceAudio &source) : MediaSourceCapsBuilder(gstWrapper, glibWrapper, source), m_attachedAudioSource(source) { } @@ -226,8 +227,9 @@ void MediaSourceAudioCapsBuilder::addFlacSpecificData(GstCaps *caps) const } MediaSourceVideoCapsBuilder::MediaSourceVideoCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, const IMediaPipeline::MediaSourceVideo &source) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const IMediaPipeline::MediaSourceVideo &source) : MediaSourceCapsBuilder(gstWrapper, glibWrapper, source), m_attachedVideoSource(source) { } @@ -244,8 +246,8 @@ GstCaps *MediaSourceVideoCapsBuilder::buildCaps() } MediaSourceVideoDolbyVisionCapsBuilder::MediaSourceVideoDolbyVisionCapsBuilder( - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const IMediaPipeline::MediaSourceVideoDolbyVision &source) : MediaSourceVideoCapsBuilder(gstWrapper, glibWrapper, source), m_attachedDolbySource(source) { diff --git a/media/server/gstplayer/source/FlushOnPrerollController.cpp b/media/server/gstplayer/source/FlushOnPrerollController.cpp index 8ffce5cbc..1218b892c 100644 --- a/media/server/gstplayer/source/FlushOnPrerollController.cpp +++ b/media/server/gstplayer/source/FlushOnPrerollController.cpp @@ -18,41 +18,67 @@ */ #include "FlushOnPrerollController.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" +#include namespace firebolt::rialto::server { -bool FlushOnPrerollController::shouldPostponeFlush(const MediaSourceType &type) const +void FlushOnPrerollController::waitIfRequired(const MediaSourceType &type) { std::unique_lock lock{m_mutex}; - return m_isPrerolled && m_flushingSources.find(type) != m_flushingSources.end(); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source entry", + common::convertMediaSourceType(type)); + m_conditionVariable.wait(lock, [this, &type]() + // coverity[MISSING_LOCK:FALSE] + { return !m_isPrerolled || m_flushingSources.find(type) == m_flushingSources.end(); }); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source exit", + common::convertMediaSourceType(type)); } -void FlushOnPrerollController::setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) +void FlushOnPrerollController::setFlushing(const MediaSourceType &type) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set flushing for: %s", common::convertMediaSourceType(type)); std::unique_lock lock{m_mutex}; m_flushingSources.insert(type); +} + +void FlushOnPrerollController::setPrerolling() +{ + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set prerolling"); + std::unique_lock lock{m_mutex}; m_isPrerolled = false; - if (!m_targetState.has_value()) - { - m_targetState = currentPipelineState; - } + m_conditionVariable.notify_all(); } void FlushOnPrerollController::stateReached(const GstState &newPipelineState) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: State reached %s", gst_element_state_get_name(newPipelineState)); std::unique_lock lock{m_mutex}; m_isPrerolled = true; if (m_targetState.has_value() && newPipelineState == m_targetState.value()) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Clear state after state reached %s", + gst_element_state_get_name(newPipelineState)); m_flushingSources.clear(); - m_targetState = std::nullopt; } + m_conditionVariable.notify_all(); +} + +void FlushOnPrerollController::setTargetState(const GstState &state) +{ + std::unique_lock lock{m_mutex}; + m_targetState = state; + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set target state %s", gst_element_state_get_name(state)); } void FlushOnPrerollController::reset() { + RIALTO_SERVER_LOG_DEBUG("Reset FlushOnPrerollController"); std::unique_lock lock{m_mutex}; + m_isPrerolled = false; m_flushingSources.clear(); m_targetState = std::nullopt; + m_conditionVariable.notify_all(); } } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstCapabilities.cpp b/media/server/gstplayer/source/GstCapabilities.cpp index 13173441d..0a0c827ae 100644 --- a/media/server/gstplayer/source/GstCapabilities.cpp +++ b/media/server/gstplayer/source/GstCapabilities.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "GstCapabilities.h" #include "GstMimeMapping.h" @@ -259,33 +260,31 @@ std::vector GstCapabilities::getSupportedProperties(MediaSourceType continue; } - GstElement *elementObj{nullptr}; - - // We instantiate an object because fetching the class, even after gstPluginFeatureLoad, - // was found to sometimes return a class with no properties. A code branch is - // kept with this feature "supportedPropertiesViaClass" - elementObj = m_gstWrapper->gstElementFactoryCreate(factory, nullptr); - if (elementObj) + GType elementType = m_gstWrapper->gstElementFactoryGetElementType(factory); + if (elementType == G_TYPE_INVALID) + continue; + gpointer elementClass = m_glibWrapper->gTypeClassRef(elementType); + if (elementClass) { GParamSpec **props; guint nProps; - props = m_glibWrapper->gObjectClassListProperties(G_OBJECT_GET_CLASS(elementObj), &nProps); + props = m_glibWrapper->gObjectClassListProperties(G_OBJECT_CLASS(elementClass), &nProps); if (props) { for (guint j = 0; j < nProps && !propertiesToLookFor.empty(); ++j) { - const std::string kPropName{props[j]->name}; - auto it = propertiesToLookFor.find(kPropName); + std::string propName{props[j]->name}; + auto it = propertiesToLookFor.find(propName); if (it != propertiesToLookFor.end()) { - RIALTO_SERVER_LOG_DEBUG("Found property '%s'", kPropName.c_str()); - propertiesFound.push_back(kPropName); + RIALTO_SERVER_LOG_DEBUG("Found property '%s'", propName.c_str()); + propertiesFound.push_back(std::move(propName)); propertiesToLookFor.erase(it); } } m_glibWrapper->gFree(props); } - m_gstWrapper->gstObjectUnref(elementObj); + m_glibWrapper->gTypeClassUnref(elementClass); } } diff --git a/media/server/gstplayer/source/GstDecryptor.cpp b/media/server/gstplayer/source/GstDecryptor.cpp index 0882b36d3..184f48d57 100644 --- a/media/server/gstplayer/source/GstDecryptor.cpp +++ b/media/server/gstplayer/source/GstDecryptor.cpp @@ -233,7 +233,7 @@ GstRialtoDecryptorPrivate::GstRialtoDecryptorPrivate( GstBaseTransform *parentElement, const std::shared_ptr &gstWrapperFactory, const std::shared_ptr &glibWrapperFactory) - : m_decryptorElement(parentElement) + : m_decryptorElement(parentElement), m_decryptionService(nullptr) { if ((!gstWrapperFactory) || (!(m_gstWrapper = gstWrapperFactory->getGstWrapper()))) { @@ -280,7 +280,7 @@ GstFlowReturn GstRialtoDecryptorPrivate::decrypt(GstBuffer *buffer, GstCaps *cap } } - if (protectionData->key && m_decryptionService->isNetflixPlayreadyKeySystem(protectionData->keySessionId)) + if (protectionData->key && m_decryptionService->isExtendedInterfaceUsed(protectionData->keySessionId)) { GstMapInfo keyMap; if (m_gstWrapper->gstBufferMap(protectionData->key, &keyMap, GST_MAP_READ)) @@ -308,14 +308,33 @@ GstFlowReturn GstRialtoDecryptorPrivate::decrypt(GstBuffer *buffer, GstCaps *cap { firebolt::rialto::MediaKeyErrorStatus status = m_decryptionService->decrypt(protectionData->keySessionId, buffer, caps); - if (firebolt::rialto::MediaKeyErrorStatus::OK != status) + if (firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED == status) + { + m_hdcpOutputRestricted = true; + m_metadataWrapper->removeProtectionMetadata(buffer); + return GST_BASE_TRANSFORM_FLOW_DROPPED; + } + else if (firebolt::rialto::MediaKeyErrorStatus::OK != status) { GST_ERROR_OBJECT(self, "Failed decrypt the buffer"); + m_hdcpOutputRestricted = false; } else { GST_TRACE_OBJECT(self, "Decryption successful"); returnStatus = GST_FLOW_OK; + + if (m_hdcpOutputRestricted) + { + GST_WARNING_OBJECT(self, "HDCP output protection failure"); + GstStructure *hdcpFailureMsg = + m_gstWrapper->gstStructureNew("HDCPProtectionFailure", "message", G_TYPE_STRING, + "HDCP Output Protection Error", NULL); + m_gstWrapper->gstElementPostMessage(GST_ELEMENT_CAST(self), + m_gstWrapper->gstMessageNewApplication(GST_OBJECT_CAST(self), + hdcpFailureMsg)); + m_hdcpOutputRestricted = false; + } } } } diff --git a/media/server/gstplayer/source/GstDispatcherThread.cpp b/media/server/gstplayer/source/GstDispatcherThread.cpp index 90953a27f..992a01352 100644 --- a/media/server/gstplayer/source/GstDispatcherThread.cpp +++ b/media/server/gstplayer/source/GstDispatcherThread.cpp @@ -24,14 +24,17 @@ namespace firebolt::rialto::server { std::unique_ptr GstDispatcherThreadFactory::createGstDispatcherThread( IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const { - return std::make_unique(client, pipeline, gstWrapper); + return std::make_unique(client, pipeline, flushOnPrerollController, gstWrapper); } GstDispatcherThread::GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) - : m_client{client}, m_gstWrapper{gstWrapper}, m_isGstreamerDispatcherActive{true} + : m_client{client}, m_flushOnPrerollController{flushOnPrerollController}, m_gstWrapper{gstWrapper}, + m_isGstreamerDispatcherActive{true} { RIALTO_SERVER_LOG_INFO("GstDispatcherThread is starting"); m_gstBusDispatcherThread = std::thread(&GstDispatcherThread::gstBusEventHandler, this, pipeline); @@ -60,9 +63,9 @@ void GstDispatcherThread::gstBusEventHandler(GstElement *pipeline) { GstMessage *message = m_gstWrapper->gstBusTimedPopFiltered(bus, 100 * GST_MSECOND, - static_cast(GST_MESSAGE_STATE_CHANGED | - GST_MESSAGE_QOS | GST_MESSAGE_EOS | - GST_MESSAGE_ERROR | GST_MESSAGE_WARNING)); + static_cast( + GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_QOS | GST_MESSAGE_EOS | + GST_MESSAGE_ERROR | GST_MESSAGE_WARNING | GST_MESSAGE_APPLICATION)); if (message) { @@ -80,11 +83,30 @@ void GstDispatcherThread::gstBusEventHandler(GstElement *pipeline) case GST_STATE_NULL: { m_isGstreamerDispatcherActive = false; + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->reset(); + } break; } case GST_STATE_PAUSED: + { + if (m_flushOnPrerollController && pending != GST_STATE_PAUSED) + { + m_flushOnPrerollController->stateReached(newState); + } + else if (m_flushOnPrerollController && pending == GST_STATE_PAUSED) + { + m_flushOnPrerollController->setPrerolling(); + } + break; + } case GST_STATE_PLAYING: { + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->stateReached(newState); + } break; } case GST_STATE_READY: diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 1b0976a73..9d6e94f6d 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -19,11 +19,15 @@ #include #include +#include +#include +#include #include #include "FlushWatcher.h" #include "GstDispatcherThread.h" #include "GstGenericPlayer.h" +#include "GstProfiler.h" #include "GstProtectionMetadata.h" #include "IGstTextTrackSinkFactory.h" #include "IMediaPipeline.h" @@ -32,6 +36,7 @@ #include "TypeConverters.h" #include "Utils.h" #include "WorkerThread.h" +#include "tasks/generic/FirstFrameReceived.h" #include "tasks/generic/GenericPlayerTaskFactory.h" namespace @@ -78,7 +83,7 @@ std::shared_ptr IGstGenericPlayerFactory::getFactory() std::unique_ptr GstGenericPlayerFactory::createGstGenericPlayer( IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory) { std::unique_ptr gstPlayer; @@ -103,10 +108,12 @@ std::unique_ptr GstGenericPlayerFactory::createGstGenericPlay { throw std::runtime_error("Cannot create RdkGstreamerUtilsWrapper"); } + gstPlayer = std::make_unique< - GstGenericPlayer>(client, decryptionService, type, videoRequirements, gstWrapper, glibWrapper, + GstGenericPlayer>(client, decryptionService, type, videoRequirements, isLive, gstWrapper, glibWrapper, rdkGstreamerUtilsWrapper, IGstInitialiser::instance(), std::make_unique(), - IGstSrcFactory::getFactory(), common::ITimerFactory::getFactory(), + IGstSrcFactory::getFactory(), IGstProfilerFactory::getFactory(), + common::ITimerFactory::getFactory(), std::make_unique(client, gstWrapper, glibWrapper, rdkGstreamerUtilsWrapper, IGstTextTrackSinkFactory::createFactory()), @@ -123,29 +130,35 @@ std::unique_ptr GstGenericPlayerFactory::createGstGenericPlay GstGenericPlayer::GstGenericPlayer( IGstGenericPlayerClient *client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::shared_ptr &rdkGstreamerUtilsWrapper, const IGstInitialiser &gstInitialiser, std::unique_ptr &&flushWatcher, - const std::shared_ptr &gstSrcFactory, std::shared_ptr timerFactory, + const std::shared_ptr &gstSrcFactory, + const std::shared_ptr &gstProfilerFactory, std::shared_ptr timerFactory, std::unique_ptr taskFactory, std::unique_ptr workerThreadFactory, std::unique_ptr gstDispatcherThreadFactory, std::shared_ptr gstProtectionMetadataFactory) : m_gstPlayerClient(client), m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, - m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_timerFactory{timerFactory}, - m_taskFactory{std::move(taskFactory)}, m_flushWatcher{std::move(flushWatcher)} + m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_gstProfilerFactory{gstProfilerFactory}, + m_timerFactory{timerFactory}, m_taskFactory{std::move(taskFactory)}, m_flushWatcher{std::move(flushWatcher)} { RIALTO_SERVER_LOG_DEBUG("GstGenericPlayer is constructed."); gstInitialiser.waitForInitialisation(); + m_context.isLive = isLive; m_context.decryptionService = &decryptionService; if ((!gstSrcFactory) || (!(m_context.gstSrc = gstSrcFactory->getGstSrc()))) { throw std::runtime_error("Cannot create GstSrc"); } + if (!m_gstProfilerFactory) + { + throw std::runtime_error("No gst profiler factory provided"); + } if (!timerFactory) { @@ -202,8 +215,9 @@ GstGenericPlayer::GstGenericPlayer( RIALTO_SERVER_LOG_MIL("Primary video playback selected"); } - m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper); + m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + m_context.flushOnPrerollController, + m_gstWrapper); } GstGenericPlayer::~GstGenericPlayer() @@ -211,9 +225,31 @@ GstGenericPlayer::~GstGenericPlayer() RIALTO_SERVER_LOG_DEBUG("GstGenericPlayer is destructed."); m_gstDispatcherThread.reset(); - resetWorkerThread(); + try + { + resetWorkerThread(); + } + catch (const std::exception &e) + { + RIALTO_SERVER_LOG_ERROR("Exception during resetWorkerThread in destructor: %s", e.what()); + } + catch (...) + { + RIALTO_SERVER_LOG_ERROR("Unknown exception during resetWorkerThread in destructor"); + } - termPipeline(); + try + { + termPipeline(); + } + catch (const std::exception &e) + { + RIALTO_SERVER_LOG_ERROR("Exception during termPipeline in destructor: %s", e.what()); + } + catch (...) + { + RIALTO_SERVER_LOG_ERROR("Unknown exception during termPipeline in destructor"); + } } void GstGenericPlayer::initMsePipeline() @@ -223,6 +259,12 @@ void GstGenericPlayer::initMsePipeline() // Set pipeline flags setPlaybinFlags(true); + m_context.gstProfiler = m_gstProfilerFactory->createGstProfiler(m_context.pipeline, m_gstWrapper, m_glibWrapper); + if (!m_context.gstProfiler) + { + throw std::runtime_error("Cannot create GstProfiler"); + } + // Set callbacks m_glibWrapper->gSignalConnect(m_context.pipeline, "source-setup", G_CALLBACK(&GstGenericPlayer::setupSource), this); m_glibWrapper->gSignalConnect(m_context.pipeline, "element-setup", G_CALLBACK(&GstGenericPlayer::setupElement), this); @@ -248,11 +290,13 @@ void GstGenericPlayer::initMsePipeline() GST_WARNING("Failed to set pipeline to READY state"); } RIALTO_SERVER_LOG_MIL("New RialtoServer's pipeline created"); + auto recordId = m_context.gstProfiler->createRecord("Pipeline Created"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); } void GstGenericPlayer::resetWorkerThread() { - m_postponedFlushes.clear(); // Shutdown task thread m_workerThread->enqueueTask(m_taskFactory->createShutdown(*this)); m_workerThread->join(); @@ -268,6 +312,9 @@ void GstGenericPlayer::termPipeline() m_finishSourceSetupTimer.reset(); + clearAudioFirstFrameFallbackProbe(); + stopNotifyPlaybackInfoTimer(); + for (auto &elem : m_context.streamInfo) { StreamInfo &streamInfo = elem.second; @@ -299,10 +346,23 @@ void GstGenericPlayer::termPipeline() m_gstWrapper->gstObjectUnref(m_context.videoSink); m_context.videoSink = nullptr; } + if (m_context.playbackGroup.m_curAudioPlaysinkBin) + { + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_context.playbackGroup.m_curAudioPlaysinkBin = nullptr; + } + + auto recordId = m_context.gstProfiler->createRecord("Pipeline Terminated"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + m_context.gstProfiler->dumpToFile(); // Delete the pipeline m_gstWrapper->gstObjectUnref(m_context.pipeline); + m_glibWrapper->gThreadPoolStopUnusedThreads(); + malloc_trim(0); + RIALTO_SERVER_LOG_MIL("RialtoServer's pipeline terminated"); } @@ -311,7 +371,9 @@ unsigned GstGenericPlayer::getGstPlayFlag(const char *nick) GFlagsClass *flagsClass = static_cast(m_glibWrapper->gTypeClassRef(m_glibWrapper->gTypeFromName("GstPlayFlags"))); GFlagsValue *flag = m_glibWrapper->gFlagsGetValueByNick(flagsClass, nick); - return flag ? flag->value : 0; + unsigned result = flag ? flag->value : 0; + m_glibWrapper->gTypeClassUnref(flagsClass); + return result; } void GstGenericPlayer::setupSource(GstElement *pipeline, GstElement *source, GstGenericPlayer *self) @@ -338,6 +400,7 @@ void GstGenericPlayer::deepElementAdded(GstBin *pipeline, GstBin *bin, GstElemen RIALTO_SERVER_LOG_DEBUG("Deep element %s added to the pipeline", GST_ELEMENT_NAME(element)); if (self->m_workerThread) { + self->m_gstWrapper->gstObjectRef(element); self->m_workerThread->enqueueTask( self->m_taskFactory->createDeepElementAdded(self->m_context, *self, pipeline, bin, element)); } @@ -406,13 +469,24 @@ bool GstGenericPlayer::getPosition(std::int64_t &position) position = getPosition(m_context.pipeline); if (position == -1) { - RIALTO_SERVER_LOG_WARN("Query position failed"); return false; } return true; } +bool GstGenericPlayer::getDuration(std::int64_t &duration) +{ + // We are on main thread here, but m_context.pipeline can be used, because it's modified only in GstGenericPlayer + // constructor and destructor. GstGenericPlayer is created/destructed on main thread, so we won't have a crash here. + if (!m_context.pipeline || !m_gstWrapper->gstElementQueryDuration(m_context.pipeline, GST_FORMAT_TIME, &duration)) + { + RIALTO_SERVER_LOG_WARN("Failed to query duration"); + return false; + } + return true; +} + GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) const { const char *kSinkName{nullptr}; @@ -425,6 +499,9 @@ GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) co case MediaSourceType::VIDEO: kSinkName = "video-sink"; break; + case MediaSourceType::SUBTITLE: + kSinkName = "text-sink"; + break; default: break; } @@ -443,7 +520,7 @@ GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) co RIALTO_SERVER_LOG_DEBUG("Pipeline is valid: %p", m_context.pipeline); } m_glibWrapper->gObjectGet(m_context.pipeline, kSinkName, &sink, nullptr); - if (sink) + if (sink && firebolt::rialto::MediaSourceType::SUBTITLE != mediaSourceType) { GstElement *autoSink{sink}; if (firebolt::rialto::MediaSourceType::VIDEO == mediaSourceType) @@ -469,28 +546,19 @@ void GstGenericPlayer::setSourceFlushed(const MediaSourceType &mediaSourceType) m_flushWatcher->setFlushed(mediaSourceType); } -void GstGenericPlayer::postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) -{ - m_postponedFlushes.emplace_back(std::make_pair(mediaSourceType, resetTime)); -} - -void GstGenericPlayer::executePostponedFlushes() -{ - if (m_workerThread) - { - for (const auto &[mediaSourceType, resetTime] : m_postponedFlushes) - { - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); - } - } - m_postponedFlushes.clear(); -} - void GstGenericPlayer::notifyPlaybackInfo() { PlaybackInfo info; getPosition(info.currentPosition); - getVolume(info.volume); + m_context.streamPosition.store(info.currentPosition); + if (m_context.audioFadeEnabled) + { + info.volume = m_context.audioFadeVolume; + } + else + { + getVolume(info.volume); + } m_gstPlayerClient->notifyPlaybackInfo(info); } @@ -645,6 +713,466 @@ GstGenericPlayer::createAudioAttributes(const std::unique_ptrm_codecParam.c_str(), pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + pAttrib->m_blockAlignment); + if (pAttrib->m_codecParam.compare(0, 4, std::string("mp4a")) == 0) + { + RIALTO_SERVER_LOG_DEBUG("Using AAC"); + capsString = m_glibWrapper->gStrdupPrintf("audio/mpeg, mpegversion=4, enable-svp=(string)%s", + svpenabled ? "true" : "false"); + *audioaac = true; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Using EAC3"); + capsString = m_glibWrapper->gStrdupPrintf("audio/x-eac3, framed=(boolean)true, rate=(int)%u, channels=(int)%u, " + "alignment=(string)frame, enable-svp=(string)%s", + pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + svpenabled ? "true" : "false"); + *audioaac = false; + } + *appsrcCaps = m_gstWrapper->gstCapsFromString(capsString); + m_glibWrapper->gFree(capsString); +} + +void GstGenericPlayer::haltAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("haltAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Transition Playsink to Ready + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioPlaysinkBin, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioPlaysinkBin to READY"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioPlaySinkBin State = %d", currentState); + // Transition Decodebin to Paused + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecodeBin, GST_STATE_PAUSED)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecodeBin to PAUSED"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current DecodeBin State = %d", currentState); +} + +void GstGenericPlayer::resumeAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("resumeAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioPlaysinkbin State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioDecodeBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> Decodebin State = %d Pending = %d", currentState, pending); +} + +void GstGenericPlayer::firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps) +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioTypefind || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("firstTimeSwitchFromAC3toAAC: audio typefind or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + GstPad *pTypfdSrcPad = NULL; + GstPad *pTypfdSrcPeerPad = NULL; + GstPad *pNewAudioDecoderSrcPad = NULL; + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstElement *newQueue = NULL; + gboolean linkRet = false; + + /* Get the SinkPad of ASink - pTypfdSrcPeerPad */ + if ((pTypfdSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioTypefind, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind SrcPad = %p", pTypfdSrcPad); + if ((pTypfdSrcPeerPad = m_gstWrapper->gstPadGetPeer(pTypfdSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind Src Downstream Element Pad = %p", pTypfdSrcPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(pTypfdSrcPad, pTypfdSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Downstream Unlink Failed"); + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + newQueue = m_gstWrapper->gstElementFactoryMake("queue", "aqueue"); + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + // Add new Queue to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newQueue) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New queue = %p", newQueue); + } + if ((pNewAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", pNewAudioDecoderSrcPad); + // Connect decoder to ASINK + if (m_gstWrapper->gstPadLink(pNewAudioDecoderSrcPad, pTypfdSrcPeerPad) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + linkRet = m_gstWrapper->gstElementLink(newAudioParse, newQueue) && + m_gstWrapper->gstElementLink(newQueue, newAudioDecoder); + if (!linkRet) + RIALTO_SERVER_LOG_DEBUG("OTF -> Downstream Link Failed for typefind, parser, decoder"); + /* Force Caps */ + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioTypefind, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY"); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; + } + m_glibWrapper->gObjectSet(G_OBJECT(m_context.playbackGroup.m_curAudioTypefind), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioTypefind); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioTypefind, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + /* Update the state */ + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newQueue); + m_gstWrapper->gstElementGetState(newQueue, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New queue State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; +} + +bool GstGenericPlayer::switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps) +{ // this function comes from rdk_gstreamer_utils + bool ret = false; + RIALTO_SERVER_LOG_DEBUG("Current Audio Codec AAC = %d Same as Incoming audio Codec AAC = %d", + m_context.playbackGroup.m_isAudioAAC, isAudioAAC); + if (m_context.playbackGroup.m_isAudioAAC == isAudioAAC) + { + return ret; + } + if ((m_context.playbackGroup.m_curAudioDecoder == NULL) && (!(m_context.playbackGroup.m_isAudioAAC)) && (isAudioAAC)) + { + firstTimeSwitchFromAC3toAAC(newAudioCaps); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; + } + if (!m_context.playbackGroup.m_curAudioDecoder || !m_context.playbackGroup.m_curAudioParse || + !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("switchAudioCodec: audio decoder, parser or decode bin is null"); + return false; + } + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstPad *newAudioParseSrcPad = NULL; + GstPad *newAudioParseSinkPad = NULL; + GstPad *newAudioDecoderSrcPad = NULL; + GstPad *newAudioDecoderSinkPad = NULL; + GstPad *audioDecSrcPad = NULL; + GstPad *audioDecSinkPad = NULL; + GstPad *audioDecSrcPeerPad = NULL; + GstPad *audioDecSinkPeerPad = NULL; + GstPad *audioParseSrcPad = NULL; + GstPad *audioParseSinkPad = NULL; + GstPad *audioParseSrcPeerPad = NULL; + GstPad *audioParseSinkPeerPad = NULL; + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Get AudioDecoder Src Pads + if ((audioDecSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Pad = %p", audioDecSrcPad); + // Get AudioDecoder Sink Pads + if ((audioDecSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Pad = %p", audioDecSinkPad); + // Get AudioDecoder Src Peer i.e. Downstream Element Pad + if ((audioDecSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Downstream Element Pad = %p", audioDecSrcPeerPad); + // Get AudioDecoder Sink Peer i.e. Upstream Element Pad + if ((audioDecSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSinkPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Upstream Element Pad = %p", audioDecSinkPeerPad); + // Get AudioParser Src Pads + if ((audioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Pad = %p", audioParseSrcPad); + // Get AudioParser Sink Pads + if ((audioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Pad = %p", audioParseSinkPad); + // Get AudioParser Src Peer i.e. Downstream Element Pad + if ((audioParseSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSrcPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Downstream Element Pad = %p", audioParseSrcPeerPad); + // Get AudioParser Sink Peer i.e. Upstream Element Pad + if ((audioParseSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSinkPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Upstream Element Pad = %p", audioParseSinkPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSrcPad, audioDecSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Downstream Unlink Failed"); + // AudioDecoder Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSinkPeerPad, audioDecSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Upstream Unlink Failed"); + // AudioParser Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSrcPad, audioParseSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Downstream Unlink Failed"); + // AudioParser Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSinkPeerPad, audioParseSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Upstream Unlink Failed"); + // Current Audio Decoder NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecoder, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecoder to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecoder, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder State = %d", currentState); + // Current Audio Parser NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioParse, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioParser to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioParse, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser State = %d", currentState); + // Remove Audio Decoder From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioDecoder = %p", m_context.playbackGroup.m_curAudioDecoder); + m_context.playbackGroup.m_curAudioDecoder = NULL; + } + // Remove Audio Parser From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioParser = %p", m_context.playbackGroup.m_curAudioParse); + m_context.playbackGroup.m_curAudioParse = NULL; + } + // Create new Audio Decoder and Parser. The inverse of the current + if (m_context.playbackGroup.m_isAudioAAC) + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("ac3parse", "ac3parse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("identity", "fake_aud_ac3dec"); + } + else + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + } + { + GstPadLinkReturn gstPadLinkRet = GST_PAD_LINK_OK; + GstElement *audioParseUpstreamEl = NULL; + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + if ((newAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", newAudioDecoderSrcPad); + if ((newAudioDecoderSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Sink Pad = %p", newAudioDecoderSinkPad); + // Link New Decoder to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioDecoderSrcPad, audioDecSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(audioDecSinkPeerPad, newAudioDecoderSinkPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Upstream Link Failed"); + if ((newAudioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Src Pad = %p", newAudioParseSrcPad); + if ((newAudioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "sink")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Sink Pad = %p", newAudioParseSinkPad); + // Link New Parser to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioParseSrcPad, audioParseSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Downstream Link Failed %d", gstPadLinkRet); + if ((audioParseUpstreamEl = GST_ELEMENT_CAST(m_gstWrapper->gstPadGetParent(audioParseSinkPeerPad))) == + m_context.playbackGroup.m_curAudioTypefind) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == m_gstWrapper->gstElementSetState(audioParseUpstreamEl, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY in switchAudioCodec"); + } + m_glibWrapper->gObjectSet(G_OBJECT(audioParseUpstreamEl), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(audioParseUpstreamEl); + m_gstWrapper->gstElementGetState(audioParseUpstreamEl, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + m_gstWrapper->gstObjectUnref(audioParseUpstreamEl); + } + m_gstWrapper->gstObjectUnref(newAudioDecoderSrcPad); + m_gstWrapper->gstObjectUnref(newAudioDecoderSinkPad); + m_gstWrapper->gstObjectUnref(newAudioParseSrcPad); + m_gstWrapper->gstObjectUnref(newAudioParseSinkPad); + } + m_gstWrapper->gstObjectUnref(audioParseSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSinkPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPad); + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; +} + +bool GstGenericPlayer::performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, + bool *audioaac, bool svpenabled, GstElement *aSrc, bool *ret) +{ + // this function comes from rdk_gstreamer_utils + if (!pStatus || !pui32Delay || !pAudioChangeTargetPts || !pcurrentDispPts || !audioChangeStage || !appsrcCaps || + !audioaac || !aSrc || !ret) + { + RIALTO_SERVER_LOG_ERROR("performAudioTrackCodecChannelSwitch: invalid null parameter"); + return false; + } + + constexpr uint32_t kOk = 0; + constexpr uint32_t kWaitWhileIdling = 100; + constexpr int kAudioChangeGapThresholdMS = 40; + constexpr unsigned int kAudchgAlign = 3; + + struct timespec ts, now; + unsigned int reconfigDelayMs; + clock_gettime(CLOCK_MONOTONIC, &ts); + if (*pStatus != kOk || pSampleAttr == nullptr) + { + RIALTO_SERVER_LOG_DEBUG("No audio data ready yet"); + *pui32Delay = kWaitWhileIdling; + *ret = false; + return true; + } + RIALTO_SERVER_LOG_DEBUG("Received first audio packet after a flush, PTS"); + if (pAudioAttr) + { + const char *pCodecStr = pAudioAttr->m_codecParam.c_str(); + const char *pCodecAcc = strstr(pCodecStr, "mp4a"); + bool isAudioAAC = (pCodecAcc) ? true : false; + bool isCodecSwitch = false; + RIALTO_SERVER_LOG_DEBUG("Audio Attribute format %s channel %d samp %d, bitrate %d blockAlignment %d", pCodecStr, + pAudioAttr->m_numberOfChannels, pAudioAttr->m_samplesPerSecond, pAudioAttr->m_bitrate, + pAudioAttr->m_blockAlignment); + *pAudioChangeTargetPts = *pcurrentDispPts; + *audioChangeStage = kAudchgAlign; + if (*appsrcCaps) + { + m_gstWrapper->gstCapsUnref(*appsrcCaps); + *appsrcCaps = NULL; + } + if (isAudioAAC != *audioaac) + isCodecSwitch = true; + configAudioCap(pAudioAttr, audioaac, svpenabled, appsrcCaps); + { + gboolean sendRet = FALSE; + GstEvent *flushStart = NULL; + GstEvent *flushStop = NULL; + flushStart = m_gstWrapper->gstEventNewFlushStart(); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStart); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-start event"); + flushStop = m_gstWrapper->gstEventNewFlushStop(TRUE); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStop); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-stop event"); + } + if (!isCodecSwitch) + { + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + } + else + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH mAudioAAC = %d", *audioaac); + haltAudioPlayback(); + if (switchAudioCodec(*audioaac, *appsrcCaps) == false) + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH FAILED switchAudioCodec mAudioAAC = %d", *audioaac); + } + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + resumeAudioPlayback(); + } + clock_gettime(CLOCK_MONOTONIC, &now); + reconfigDelayMs = now.tv_nsec > ts.tv_nsec ? (now.tv_nsec - ts.tv_nsec) / 1000000 + : (1000 - (ts.tv_nsec - now.tv_nsec) / 1000000); + (*pAudioChangeTargetPts) += (reconfigDelayMs + kAudioChangeGapThresholdMS); + } + else + { + RIALTO_SERVER_LOG_DEBUG("first audio after change no attribute drop!"); + *pui32Delay = 0; + *ret = false; + return true; + } + *ret = true; + return true; +} + bool GstGenericPlayer::setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutputParam) { if (!m_workerThread) @@ -655,6 +1183,41 @@ bool GstGenericPlayer::setImmediateOutput(const MediaSourceType &mediaSourceType return true; } +bool GstGenericPlayer::setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) +{ + if (!m_workerThread) + return false; + + m_workerThread->enqueueTask( + m_taskFactory->createSetReportDecodeErrors(m_context, *this, mediaSourceType, reportDecodeErrors)); + return true; +} + +bool GstGenericPlayer::getQueuedFrames(uint32_t &queuedFrames) +{ + bool returnValue{false}; + GstElement *decoder{getDecoder(MediaSourceType::VIDEO)}; + if (decoder) + { + if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(decoder), "queued-frames")) + { + m_glibWrapper->gObjectGet(decoder, "queued-frames", &queuedFrames, nullptr); + returnValue = true; + } + else + { + RIALTO_SERVER_LOG_ERROR("queued-frames not supported in element %s", GST_ELEMENT_NAME(decoder)); + } + m_gstWrapper->gstObjectUnref(decoder); + } + else + { + RIALTO_SERVER_LOG_ERROR("Failed to get queued-frames property, decoder is NULL"); + } + + return returnValue; +} + bool GstGenericPlayer::getImmediateOutput(const MediaSourceType &mediaSourceType, bool &immediateOutputRef) { bool returnValue{false}; @@ -808,6 +1371,26 @@ void GstGenericPlayer::notifyNeedMediaData(const MediaSourceType mediaSource) } } +void GstGenericPlayer::notifyNeedMediaDataWithDelay(const MediaSourceType mediaSource) +{ + auto elem = m_context.streamInfo.find(mediaSource); + if (elem != m_context.streamInfo.end()) + { + StreamInfo &streamInfo = elem->second; + streamInfo.isNeedDataPending = false; + + // Schedule new NeedMediaData if we still need it + if (m_gstPlayerClient && streamInfo.isDataNeeded) + { + streamInfo.isNeedDataPending = m_gstPlayerClient->notifyNeedMediaDataWithDelay(mediaSource); + } + } + else + { + RIALTO_SERVER_LOG_WARN("Media type %s could not be found", common::convertMediaSourceType(mediaSource)); + } +} + void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaType) { auto elem = m_context.streamInfo.find(mediaType); @@ -825,7 +1408,7 @@ void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaT } else { - pushSampleIfRequired(streamInfo.appSrc, common::convertMediaSourceType(mediaType)); + pushSampleIfRequired(streamInfo.appSrc, mediaType); } if (mediaType == firebolt::rialto::MediaSourceType::AUDIO) { @@ -969,8 +1552,10 @@ bool GstGenericPlayer::setCodecData(GstCaps *caps, const std::shared_ptrsecond) { GstSeekFlags seekFlag = resetTime ? GST_SEEK_FLAG_FLUSH : GST_SEEK_FLAG_NONE; - RIALTO_SERVER_LOG_DEBUG("Pushing new %s sample...", typeStr.c_str()); + RIALTO_SERVER_LOG_DEBUG("Pushing new %s sample...", kTypeStr.c_str()); GstSegment *segment{m_gstWrapper->gstSegmentNew()}; m_gstWrapper->gstSegmentInit(segment, GST_FORMAT_TIME); if (!m_gstWrapper->gstSegmentDoSeek(segment, m_context.playbackRate, GST_FORMAT_TIME, seekFlag, @@ -999,8 +1584,11 @@ void GstGenericPlayer::pushSampleIfRequired(GstElement *source, const std::strin segment->applied_rate = appliedRate; RIALTO_SERVER_LOG_MIL("New %s segment: [%" GST_TIME_FORMAT ", %" GST_TIME_FORMAT "], rate: %f, appliedRate %f, reset_time: %d\n", - typeStr.c_str(), GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), + kTypeStr.c_str(), GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), segment->rate, segment->applied_rate, resetTime); + auto recordId = m_context.gstProfiler->createRecord("First Segment Received", kTypeStr); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); GstCaps *currentCaps = m_gstWrapper->gstAppSrcGetCaps(GST_APP_SRC(source)); // We can't pass buffer in GstSample, because implementation of gst_app_src_push_sample @@ -1012,6 +1600,11 @@ void GstGenericPlayer::pushSampleIfRequired(GstElement *source, const std::strin m_gstWrapper->gstCapsUnref(currentCaps); m_gstWrapper->gstSegmentFree(segment); + + if (mediaSourceType == MediaSourceType::AUDIO) + { + m_context.audioGstSegmentPosition = position; + } } m_context.currentPosition[source] = initialPosition->second.back(); m_context.initialPositions.erase(initialPosition); @@ -1080,9 +1673,24 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrgetType()].appSrc)}; GstCaps *oldCaps = m_gstWrapper->gstAppSrcGetCaps(appSrc); + if ((!oldCaps) || (!m_gstWrapper->gstCapsIsEqual(caps, oldCaps))) { RIALTO_SERVER_LOG_DEBUG("Caps not equal. Perform audio track codec channel switch."); + + GstElement *sink = getSink(MediaSourceType::AUDIO); + if (!sink) + { + RIALTO_SERVER_LOG_ERROR("Failed to get audio sink"); + if (caps) + m_gstWrapper->gstCapsUnref(caps); + if (oldCaps) + m_gstWrapper->gstCapsUnref(oldCaps); + return false; + } + std::string sinkName = GST_ELEMENT_NAME(sink); + m_gstWrapper->gstObjectUnref(sink); + int sampleAttributes{ 0}; // rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch checks if this param != NULL only. std::uint32_t status{0}; // must be 0 to make rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch work @@ -1096,14 +1704,26 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrperformAudioTrackCodecChannelSwitch(&m_context.playbackGroup, &sampleAttributes, &(*audioAttributes), - &status, &ui32Delay, &audioChangeTargetPts, ¤tDispPts, - &audioChangeStage, - &caps, // may fail for amlogic - that implementation changes - // this parameter, it's probably used by Netflix later - &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + + bool result = false; + if (m_glibWrapper->gStrHasPrefix(sinkName.c_str(), "amlhalasink")) + { + // due to problems audio codec change in prerolling, temporarily moved the code from rdk gstreamer utils to + // Rialto and applied fixes + result = performAudioTrackCodecChannelSwitch(&sampleAttributes, &(*audioAttributes), &status, &ui32Delay, + &audioChangeTargetPts, ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + } + else + { + result = m_rdkGstreamerUtilsWrapper->performAudioTrackCodecChannelSwitch(&m_context.playbackGroup, + &sampleAttributes, + &(*audioAttributes), &status, + &ui32Delay, &audioChangeTargetPts, + ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, + GST_ELEMENT(appSrc), &retVal); + } if (!result || !retVal) { @@ -1165,6 +1785,71 @@ void GstGenericPlayer::scheduleVideoUnderflow() } } +void GstGenericPlayer::scheduleFirstVideoFrameReceived() +{ + if (m_workerThread) + { + m_workerThread->enqueueTask(m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::VIDEO)); + } +} + +void GstGenericPlayer::scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) +{ + if (m_workerThread) + { + m_workerThread->enqueueTask( + m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::AUDIO, audioAction)); + } +} + +void GstGenericPlayer::setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) +{ + GstPad *oldPad{m_context.audioFirstFrameProbePad}; + gulong oldId{m_context.audioFirstFrameProbeId}; + m_context.audioFirstFrameProbePad = pad; + m_context.audioFirstFrameProbeId = id; + + if (oldPad && oldId != 0) + { + m_gstWrapper->gstPadRemoveProbe(oldPad, oldId); + } + + if (oldPad) + { + m_gstWrapper->gstObjectUnref(oldPad); + } +} + +void GstGenericPlayer::clearAudioFirstFrameFallbackProbe() +{ + GstPad *pad{m_context.audioFirstFrameProbePad}; + gulong id{m_context.audioFirstFrameProbeId}; + m_context.audioFirstFrameProbePad = nullptr; + m_context.audioFirstFrameProbeId = 0; + + if (pad && id != 0) + { + m_gstWrapper->gstPadRemoveProbe(pad, id); + } + + if (pad) + { + m_gstWrapper->gstObjectUnref(pad); + } +} + +void GstGenericPlayer::clearAudioFirstFrameFallbackProbeState() +{ + GstPad *pad{m_context.audioFirstFrameProbePad}; + m_context.audioFirstFrameProbePad = nullptr; + m_context.audioFirstFrameProbeId = 0; + + if (pad) + { + m_gstWrapper->gstObjectUnref(pad); + } +} + void GstGenericPlayer::scheduleAllSourcesAttached() { allSourcesAttached(); @@ -1186,8 +1871,9 @@ void GstGenericPlayer::cancelUnderflow(firebolt::rialto::MediaSourceType mediaSo } } -void GstGenericPlayer::play() +void GstGenericPlayer::play(bool &async) { + async = true; if (m_workerThread) { m_workerThread->enqueueTask(m_taskFactory->createPlay(*this)); @@ -1210,23 +1896,24 @@ void GstGenericPlayer::stop() } } -bool GstGenericPlayer::changePipelineState(GstState newState) +GstStateChangeReturn GstGenericPlayer::changePipelineState(GstState newState) { if (!m_context.pipeline) { RIALTO_SERVER_LOG_ERROR("Change state failed - pipeline is nullptr"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; + return GST_STATE_CHANGE_FAILURE; } - if (m_gstWrapper->gstElementSetState(m_context.pipeline, newState) == GST_STATE_CHANGE_FAILURE) + m_context.flushOnPrerollController->setTargetState(newState); + const GstStateChangeReturn result{m_gstWrapper->gstElementSetState(m_context.pipeline, newState)}; + if (result == GST_STATE_CHANGE_FAILURE) { RIALTO_SERVER_LOG_ERROR("Change state failed - Gstreamer returned an error"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; } - return true; + return result; } int64_t GstGenericPlayer::getPosition(GstElement *element) @@ -1342,6 +2029,51 @@ bool GstGenericPlayer::setImmediateOutput() return result; } +bool GstGenericPlayer::setReportDecodeErrors() +{ + bool result{false}; + bool reportDecodeErrors{false}; + + { + std::unique_lock lock{m_context.propertyMutex}; + if (!m_context.pendingReportDecodeErrorsForVideo.has_value()) + { + return false; + } + reportDecodeErrors = m_context.pendingReportDecodeErrorsForVideo.value(); + } + + GstElement *decoder = getDecoder(MediaSourceType::VIDEO); + if (decoder) + { + RIALTO_SERVER_LOG_DEBUG("Set report decode errors to %s", reportDecodeErrors ? "TRUE" : "FALSE"); + + if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(decoder), "report-decode-errors")) + { + gboolean reportDecodeErrorsGboolean{reportDecodeErrors ? TRUE : FALSE}; + m_glibWrapper->gObjectSet(decoder, "report-decode-errors", reportDecodeErrorsGboolean, nullptr); + result = true; + } + else + { + RIALTO_SERVER_LOG_ERROR("Failed to set report-decode-errors property on decoder '%s'", + GST_ELEMENT_NAME(decoder)); + } + + m_gstWrapper->gstObjectUnref(decoder); + + { + std::unique_lock lock{m_context.propertyMutex}; + m_context.pendingReportDecodeErrorsForVideo.reset(); + } + } + else + { + RIALTO_SERVER_LOG_DEBUG("Pending report-decode-errors, decoder is NULL"); + } + return result; +} + bool GstGenericPlayer::setShowVideoWindow() { if (!m_context.pendingShowVideoWindow.has_value()) @@ -1448,7 +2180,7 @@ bool GstGenericPlayer::setSyncOff() if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(decoder), "sync-off")) { - gboolean syncOffGboolean{decoder ? TRUE : FALSE}; + gboolean syncOffGboolean{syncOff ? TRUE : FALSE}; m_glibWrapper->gObjectSet(decoder, "sync-off", syncOffGboolean, nullptr); result = true; } @@ -1546,8 +2278,13 @@ bool GstGenericPlayer::setRenderFrame() RIALTO_SERVER_LOG_INFO("Rendering preroll"); m_glibWrapper->gObjectSet(sink, kStepOnPrerollPropertyName.c_str(), 1, nullptr); - m_gstWrapper->gstElementSendEvent(sink, m_gstWrapper->gstEventNewStep(GST_FORMAT_BUFFERS, 1, 1.0, true, - false)); + gboolean sendRet = + m_gstWrapper->gstElementSendEvent(sink, m_gstWrapper->gstEventNewStep(GST_FORMAT_BUFFERS, 1, 1.0, + true, false)); + if (!sendRet) + { + RIALTO_SERVER_LOG_WARN("Failed to send step event for rendering preroll frame"); + } m_glibWrapper->gObjectSet(sink, kStepOnPrerollPropertyName.c_str(), 0, nullptr); result = true; } @@ -1693,7 +2430,6 @@ bool GstGenericPlayer::setErmContext() void GstGenericPlayer::startPositionReportingAndCheckAudioUnderflowTimer() { - static constexpr std::chrono::milliseconds kPlaybackInfoTimerMs{32}; if (m_positionReportingAndCheckAudioUnderflowTimer && m_positionReportingAndCheckAudioUnderflowTimer->isActive()) { return; @@ -1710,12 +2446,6 @@ void GstGenericPlayer::startPositionReportingAndCheckAudioUnderflowTimer() } }, firebolt::rialto::common::TimerType::PERIODIC); - - notifyPlaybackInfo(); - - m_playbackInfoTimer = - m_timerFactory - ->createTimer(kPlaybackInfoTimerMs, [this]() { notifyPlaybackInfo(); }, firebolt::rialto::common::TimerType::PERIODIC); } void GstGenericPlayer::stopPositionReportingAndCheckAudioUnderflowTimer() @@ -1725,7 +2455,25 @@ void GstGenericPlayer::stopPositionReportingAndCheckAudioUnderflowTimer() m_positionReportingAndCheckAudioUnderflowTimer->cancel(); m_positionReportingAndCheckAudioUnderflowTimer.reset(); } +} + +void GstGenericPlayer::startNotifyPlaybackInfoTimer() +{ + static constexpr std::chrono::milliseconds kPlaybackInfoTimerMs{32}; + if (m_playbackInfoTimer && m_playbackInfoTimer->isActive()) + { + return; + } + + notifyPlaybackInfo(); + + const auto kNotifyPlaybackInfo = [this]() { notifyPlaybackInfo(); }; + m_playbackInfoTimer = m_timerFactory->createTimer(kPlaybackInfoTimerMs, kNotifyPlaybackInfo, + firebolt::rialto::common::TimerType::PERIODIC); +} +void GstGenericPlayer::stopNotifyPlaybackInfoTimer() +{ if (m_playbackInfoTimer && m_playbackInfoTimer->isActive()) { m_playbackInfoTimer->cancel(); @@ -1827,6 +2575,7 @@ bool GstGenericPlayer::getVolume(double ¤tVolume) currentVolume = static_cast(fadeVolume) / 100.0; RIALTO_SERVER_LOG_INFO("Fade volume is supported: %f", currentVolume); } + m_context.audioFadeVolume = currentVolume; } else { @@ -2041,7 +2790,7 @@ void GstGenericPlayer::flush(const MediaSourceType &mediaSourceType, bool resetT { async = isAsync(mediaSourceType); m_flushWatcher->setFlushing(mediaSourceType, async); - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); + m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime, async)); } } @@ -2154,7 +2903,12 @@ void GstGenericPlayer::handleBusMessage(GstMessage *message) void GstGenericPlayer::updatePlaybackGroup(GstElement *typefind, const GstCaps *caps) { - m_workerThread->enqueueTask(m_taskFactory->createUpdatePlaybackGroup(m_context, *this, typefind, caps)); + if (m_workerThread) + { + m_gstWrapper->gstObjectRef(typefind); + GstCaps *ownedCaps{caps ? m_gstWrapper->gstCapsCopy(caps) : nullptr}; + m_workerThread->enqueueTask(m_taskFactory->createUpdatePlaybackGroup(m_context, *this, typefind, ownedCaps)); + } } void GstGenericPlayer::addAutoVideoSinkChild(GObject *object) diff --git a/media/server/gstplayer/source/GstInitialiser.cpp b/media/server/gstplayer/source/GstInitialiser.cpp index b701ed318..8c5aea35f 100644 --- a/media/server/gstplayer/source/GstInitialiser.cpp +++ b/media/server/gstplayer/source/GstInitialiser.cpp @@ -19,6 +19,7 @@ #include "GstInitialiser.h" #include "GstLogForwarding.h" +#include "IGlibWrapper.h" #include "RialtoServerLogging.h" namespace firebolt::rialto::server @@ -68,6 +69,15 @@ void GstInitialiser::initialise(int *argc, char ***argv) m_gstWrapper->gstInit(argc, argv); + auto glibWrapper = firebolt::rialto::wrappers::IGlibWrapperFactory::getFactory()->getGlibWrapper(); + if (!glibWrapper) + { + RIALTO_SERVER_LOG_ERROR("Failed to create the glib wrapper"); + return; + } + glibWrapper->gThreadPoolSetMaxUnusedThreads(2); + glibWrapper->gThreadPoolSetMaxIdleTime(5 * 1000); // 5 seconds + // remove rialto sinks from the registry GstPlugin *rialtoPlugin = m_gstWrapper->gstRegistryFindPlugin(m_gstWrapper->gstRegistryGet(), "rialtosinks"); diff --git a/media/server/gstplayer/source/GstProfiler.cpp b/media/server/gstplayer/source/GstProfiler.cpp new file mode 100644 index 000000000..3a87fe248 --- /dev/null +++ b/media/server/gstplayer/source/GstProfiler.cpp @@ -0,0 +1,410 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "GstProfiler.h" +#include "RialtoServerLogging.h" +#include "Utils.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace firebolt::rialto::server +{ +std::weak_ptr GstProfilerFactory::m_factory; + +std::shared_ptr IGstProfilerFactory::getFactory() +{ + std::shared_ptr factory = GstProfilerFactory::m_factory.lock(); + + if (!factory) + { + try + { + factory = std::make_shared(); + } + catch (const std::exception &e) + { + RIALTO_SERVER_LOG_ERROR("Failed to create the gst profiler factory, reason: %s", e.what()); + } + + GstProfilerFactory::m_factory = factory; + } + + return factory; +} + +std::unique_ptr GstProfilerFactory::createGstProfiler(GstElement *pipeline, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) const +{ + return std::make_unique(pipeline, gstWrapper, glibWrapper); +} + +namespace +{ +inline constexpr std::array kKlassTokens{ + std::string_view{"Source"}, + std::string_view{"Decryptor"}, + std::string_view{"Decoder"}, +}; +inline constexpr std::string_view kModuleName{"GstProfiler"}; + +using Clock = std::chrono::system_clock; +using IProfiler = firebolt::rialto::common::IProfiler; + +GstPadProbeReturn probeCb(GstPad *pad, GstPadProbeInfo *info, gpointer userData) +{ + firebolt::rialto::server::IGstProfilerPrivate *self = + static_cast(userData); + return self->handleProbeCb(pad, info); +} + +std::optional diffMs(const std::optional &end, const std::optional &start) +{ + if (!end || !start) + return std::nullopt; + + return std::chrono::duration_cast(*end - *start).count(); +} + +std::optional maxTime(const std::optional &a, + const std::optional &b) +{ + if (a && b) + return std::max(*a, *b); + if (a) + return a; + if (b) + return b; + return std::nullopt; +} +} // namespace + +GstProfiler::GstProfiler(GstElement *pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) + : m_pipeline{pipeline}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper} +{ + auto profilerFactory = firebolt::rialto::common::IProfilerFactory::createFactory(); + m_profiler = profilerFactory ? std::shared_ptr{profilerFactory->createProfiler(std::string{kModuleName})} + : nullptr; + m_enabled = (m_profiler != nullptr) && m_profiler->isEnabled(); + + if (m_enabled && m_pipeline) + m_gstWrapper->gstObjectRef(m_pipeline); +} + +GstProfiler::~GstProfiler() +{ + while (!m_probeCtxs.empty()) + { + auto &probeCtx = m_probeCtxs.back(); + if (probeCtx.id != 0) + { + m_gstWrapper->gstPadRemoveProbe(probeCtx.pad, probeCtx.id); + } + m_gstWrapper->gstObjectUnref(probeCtx.pad); + m_probeCtxs.pop_back(); + } + + if (m_enabled && m_pipeline) + m_gstWrapper->gstObjectUnref(m_pipeline); +} + +std::optional GstProfiler::createRecord(const std::string &stage) +{ + if (!m_enabled || !m_profiler) + return std::nullopt; + + auto id = m_profiler->record(stage); + if (!id) + return std::nullopt; + + return static_cast(*id); +} + +std::optional GstProfiler::createRecord(const std::string &stage, const std::string &info) +{ + if (!m_enabled || !m_profiler) + return std::nullopt; + + auto id = m_profiler->record(stage, info); + if (!id) + return std::nullopt; + + return static_cast(*id); +} + +void GstProfiler::scheduleGstElementRecord(GstElement *element) +{ + if (!m_enabled || !m_profiler) + return; + + if (!element) + return; + + auto stage = getFirstBufferExitStage(element); + if (!stage) + return; + + GstPad *pad = m_gstWrapper->gstElementGetStaticPad(element, "src"); + if (!pad) + return; + + std::string elementInfo; + if (isVideo(*m_gstWrapper, element)) + { + elementInfo = "Video"; + } + else if (isAudio(*m_gstWrapper, element)) + { + elementInfo = "Audio"; + } + else + { + gchar *rawName = m_gstWrapper->gstElementGetName(element); + elementInfo = deriveElementInfoFromName(rawName ? rawName : ""); + if (rawName) + m_glibWrapper->gFree(rawName); + } + + const auto probeId = m_gstWrapper->gstPadAddProbe(pad, GST_PAD_PROBE_TYPE_BUFFER, &probeCb, + static_cast(this), nullptr); + if (probeId == 0) + { + m_gstWrapper->gstObjectUnref(pad); + return; + } + + m_probeCtxs.emplace_back(ProbeCtx{m_profiler, stage.value(), std::move(elementInfo), pad, probeId}); +} + +std::vector GstProfiler::getRecords() const +{ + if (!m_profiler) + return {}; + + return m_profiler->getRecords(); +} + +void GstProfiler::logRecord(GstProfiler::RecordId id) +{ + if (!m_enabled || !m_profiler) + return; + + m_profiler->log(static_cast(id)); +} + +void GstProfiler::dumpToFile() const +{ + if (!m_enabled || !m_profiler) + return; + + if (!m_profiler->dumpToFile()) + { + RIALTO_SERVER_LOG_WARN("Failed to dump profiler records to file"); + } +} + +void GstProfiler::logPipelineSummary() const +{ + if (!m_enabled || !m_profiler) + return; + + const auto metrics = calculateMetrics(); + if (metrics) + { + RIALTO_SERVER_LOG_MIL("PROFILER | TUNETIME: %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, " + "%lld, %lld, %lld, %lld", + metrics->preparation ? static_cast(*metrics->preparation) : -1, // NOLINT + metrics->videoDownload ? static_cast(*metrics->videoDownload) : -1, // NOLINT + metrics->audioDownload ? static_cast(*metrics->audioDownload) : -1, // NOLINT + metrics->videoSource ? static_cast(*metrics->videoSource) : -1, // NOLINT + metrics->audioSource ? static_cast(*metrics->audioSource) : -1, // NOLINT + metrics->videoDecryption ? static_cast(*metrics->videoDecryption) : -1, // NOLINT + metrics->audioDecryption ? static_cast(*metrics->audioDecryption) : -1, // NOLINT + metrics->videoDecode ? static_cast(*metrics->videoDecode) : -1, // NOLINT + metrics->audioDecode ? static_cast(*metrics->audioDecode) : -1, // NOLINT + metrics->preRoll ? static_cast(*metrics->preRoll) : -1, // NOLINT + metrics->play ? static_cast(*metrics->play) : -1, // NOLINT + metrics->total ? static_cast(*metrics->total) : -1, // NOLINT + metrics->totalWithoutApp ? static_cast(*metrics->totalWithoutApp) : -1); // NOLINT + } +} + +GstPadProbeReturn GstProfiler::handleProbeCb(GstPad *pad, GstPadProbeInfo *info) +{ + if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER)) + return GST_PAD_PROBE_OK; + + if (!GST_PAD_PROBE_INFO_BUFFER(info)) + return GST_PAD_PROBE_OK; + + const auto probeCtx = + std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; }); + if (probeCtx == m_probeCtxs.end()) + return GST_PAD_PROBE_REMOVE; + + if (probeCtx->profiler) + { + const auto id = probeCtx->profiler->record(probeCtx->stage, probeCtx->info); + if (id) + { + probeCtx->profiler->log(id.value()); + } + } + + removeProbeCtx(pad); + return GST_PAD_PROBE_REMOVE; +} + +std::optional GstProfiler::getFirstBufferExitStage(GstElement *element) +{ + const gchar *klass = getElementClassMetadata(element); + if (!klass) + return std::nullopt; + + for (auto token : kKlassTokens) + { + if (m_glibWrapper->gStrrstr(klass, token.data()) != nullptr) + { + return std::string(token.data()) + " FB Exit"; + } + } + + return std::nullopt; +} + +const gchar *GstProfiler::getElementClassMetadata(GstElement *element) +{ + return m_gstWrapper->gstElementClassGetMetadata(GST_ELEMENT_CLASS(G_OBJECT_GET_CLASS(element)), + GST_ELEMENT_METADATA_KLASS); +} + +std::string GstProfiler::deriveElementInfoFromName(const std::string &name) const +{ + std::string lower = name; + std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (lower.find("vid") != std::string::npos || lower.find("video") != std::string::npos) + { + return "Video"; + } + + if (lower.find("aud") != std::string::npos || lower.find("audio") != std::string::npos) + { + return "Audio"; + } + + return name; +} + +void GstProfiler::removeProbeCtx(GstPad *pad) +{ + const auto probeCtx = + std::find_if(m_probeCtxs.begin(), m_probeCtxs.end(), [pad](const auto &ctx) { return ctx.pad == pad; }); + if (probeCtx == m_probeCtxs.end()) + return; + + m_gstWrapper->gstObjectUnref(probeCtx->pad); + m_probeCtxs.erase(probeCtx); +} + +std::optional GstProfiler::calculateMetrics() const +{ + const auto records = m_profiler->getRecords(); + + PipelineStageTimestamps timestamps; + + for (const auto &record : records) + { + const auto &stage = record.stage; + const auto &info = record.info; + + if (!timestamps.pipelineCreated && stage == "Pipeline Created") + timestamps.pipelineCreated = record.time; + else if (!timestamps.allSourcesAttached && stage == "All Sources Attached") + timestamps.allSourcesAttached = record.time; + else if (!timestamps.firstSegmentReceivedVideo && stage == "First Segment Received" && info == "Video") + timestamps.firstSegmentReceivedVideo = record.time; + else if (!timestamps.firstSegmentReceivedAudio && stage == "First Segment Received" && info == "Audio") + timestamps.firstSegmentReceivedAudio = record.time; + else if (!timestamps.sourceFbExitVideo && stage == "Source FB Exit" && info == "Video") + timestamps.sourceFbExitVideo = record.time; + else if (!timestamps.sourceFbExitAudio && stage == "Source FB Exit" && info == "Audio") + timestamps.sourceFbExitAudio = record.time; + else if (!timestamps.decryptorFbExitVideo && stage == "Decryptor FB Exit" && info == "Video") + timestamps.decryptorFbExitVideo = record.time; + else if (!timestamps.decryptorFbExitAudio && stage == "Decryptor FB Exit" && info == "Audio") + timestamps.decryptorFbExitAudio = record.time; + else if (!timestamps.decoderFbExitVideo && stage == "Decoder FB Exit" && info == "Video") + timestamps.decoderFbExitVideo = record.time; + else if (!timestamps.decoderFbExitAudio && stage == "Decoder FB Exit" && info == "Audio") + timestamps.decoderFbExitAudio = record.time; + else if (!timestamps.pipelinePaused && stage == "Pipeline State Changed" && info == "PAUSED") + timestamps.pipelinePaused = record.time; + else if (!timestamps.pipelinePlaying && stage == "Pipeline State Changed" && info == "PLAYING") + timestamps.pipelinePlaying = record.time; + } + + if (!timestamps.pipelineCreated || !timestamps.allSourcesAttached || !timestamps.firstSegmentReceivedVideo || + !timestamps.firstSegmentReceivedAudio || !timestamps.sourceFbExitVideo || !timestamps.sourceFbExitAudio || + !timestamps.decoderFbExitVideo || !timestamps.decoderFbExitAudio || !timestamps.pipelinePaused || + !timestamps.pipelinePlaying) + { + return std::nullopt; + } + + PipelineMetrics metrics; + + metrics.preparation = diffMs(timestamps.allSourcesAttached, timestamps.pipelineCreated); + metrics.videoDownload = diffMs(timestamps.firstSegmentReceivedVideo, timestamps.allSourcesAttached); + metrics.audioDownload = diffMs(timestamps.firstSegmentReceivedAudio, timestamps.allSourcesAttached); + metrics.videoSource = diffMs(timestamps.sourceFbExitVideo, timestamps.firstSegmentReceivedVideo); + metrics.audioSource = diffMs(timestamps.sourceFbExitAudio, timestamps.firstSegmentReceivedAudio); + + if (timestamps.decryptorFbExitVideo && timestamps.decryptorFbExitAudio) + { + metrics.videoDecryption = diffMs(timestamps.decryptorFbExitVideo, timestamps.sourceFbExitVideo); + metrics.audioDecryption = diffMs(timestamps.decryptorFbExitAudio, timestamps.sourceFbExitAudio); + metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.decryptorFbExitVideo); + metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.decryptorFbExitAudio); + } + else + { + metrics.videoDecode = diffMs(timestamps.decoderFbExitVideo, timestamps.sourceFbExitVideo); + metrics.audioDecode = diffMs(timestamps.decoderFbExitAudio, timestamps.sourceFbExitAudio); + } + + const auto firstMediaReady = maxTime(timestamps.firstSegmentReceivedVideo, timestamps.firstSegmentReceivedAudio); + + metrics.preRoll = diffMs(timestamps.pipelinePaused, firstMediaReady); + metrics.play = diffMs(timestamps.pipelinePlaying, timestamps.pipelinePaused); + metrics.total = diffMs(timestamps.pipelinePlaying, timestamps.pipelineCreated); + metrics.totalWithoutApp = diffMs(timestamps.pipelinePlaying, firstMediaReady); + + return metrics; +} + +} // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstSrc.cpp b/media/server/gstplayer/source/GstSrc.cpp index ff7b1e227..c544fa04a 100644 --- a/media/server/gstplayer/source/GstSrc.cpp +++ b/media/server/gstplayer/source/GstSrc.cpp @@ -48,7 +48,9 @@ static void gstRialtoSrcFinalize(GObject *object) { GstRialtoSrc *src = GST_RIALTO_SRC(object); GstRialtoSrcPrivate *priv = src->priv; + GST_OBJECT_LOCK(src); g_free(priv->uri); + GST_OBJECT_UNLOCK(src); priv->~GstRialtoSrcPrivate(); GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); } diff --git a/media/server/gstplayer/source/GstTextTrackSink.cpp b/media/server/gstplayer/source/GstTextTrackSink.cpp index b98c5b93f..16e137854 100644 --- a/media/server/gstplayer/source/GstTextTrackSink.cpp +++ b/media/server/gstplayer/source/GstTextTrackSink.cpp @@ -159,19 +159,12 @@ static void gst_rialto_text_track_sink_finalize(GObject *object) // NOLINT(build static gboolean gst_rialto_text_track_sink_start(GstBaseSink *sink) // NOLINT(build/function_format) { - const char *wayland_display = std::getenv("WAYLAND_DISPLAY"); - if (!wayland_display) - { - GST_ERROR_OBJECT(sink, "Failed to get WAYLAND_DISPLAY env variable"); - return false; - } - - std::string display{wayland_display}; + const std::string kDisplay{"westeros-asplayer-subtitles"}; GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(sink); try { self->priv->m_textTrackSession = - firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(display); + firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(kDisplay); } catch (const std::exception &e) { diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index ef6aa666a..ab2582120 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -125,8 +125,8 @@ GstWebAudioPlayer::GstWebAudioPlayer(IGstWebAudioPlayerClient *client, const uin } if ((!gstDispatcherThreadFactory) || - (!(m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper)))) + (!(m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + nullptr, m_gstWrapper)))) { termWebAudioPipeline(); resetWorkerThread(); @@ -410,9 +410,13 @@ uint32_t GstWebAudioPlayer::writeBuffer(uint8_t *mainPtr, uint32_t mainLength, u { // Must block and wait for the data to be written from the shared buffer. std::unique_lock lock(m_context.writeBufferMutex); + const uint32_t initialCompletionCounter = m_context.writeCompletionCounter; m_workerThread->enqueueTask(m_taskFactory->createWriteBuffer(m_context, mainPtr, mainLength, wrapPtr, wrapLength)); - std::cv_status status = m_context.writeBufferCond.wait_for(lock, std::chrono::milliseconds(kMaxWriteBufferTimeoutMs)); - if (std::cv_status::timeout == status) + bool success = + m_context.writeBufferCond.wait_for(lock, std::chrono::milliseconds(kMaxWriteBufferTimeoutMs), + [this, initialCompletionCounter]() + { return m_context.writeCompletionCounter != initialCompletionCounter; }); + if (!success) { RIALTO_SERVER_LOG_ERROR("Timed out writing to the gstreamer buffers"); return 0; @@ -470,4 +474,4 @@ void GstWebAudioPlayer::ping(std::unique_ptr &&heartbeatHandl } } -}; // namespace firebolt::rialto::server +} // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/Utils.cpp b/media/server/gstplayer/source/Utils.cpp index 243aa060d..9d8ecdcd5 100644 --- a/media/server/gstplayer/source/Utils.cpp +++ b/media/server/gstplayer/source/Utils.cpp @@ -29,6 +29,7 @@ namespace { const char *underflowSignals[]{"buffer-underflow-callback", "vidsink-underflow-callback", "underrun-callback"}; +const char *firstFrameSignals[]{"first-video-frame-callback", "first-audio-frame", "first-audio-frame-callback"}; bool isType(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element, GstElementFactoryListType type) { @@ -64,6 +65,11 @@ bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, Gs return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); } +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) +{ + return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO); +} + bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) { return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); @@ -120,6 +126,32 @@ std::optional getUnderflowSignalName(const firebolt::rialto::wrappe return std::nullopt; } +std::optional getFirstFrameSignalName(const firebolt::rialto::wrappers::IGlibWrapper &glibWrapper, + GstElement *element) +{ + GType type = glibWrapper.gObjectType(element); + guint nsignals{0}; + guint *signals = glibWrapper.gSignalListIds(type, &nsignals); + + for (guint i = 0; i < nsignals; i++) + { + GSignalQuery query; + glibWrapper.gSignalQuery(signals[i], &query); + const auto signalNameIt = std::find_if(std::begin(firstFrameSignals), std::end(firstFrameSignals), + [&](const auto *signalName) + { return strcmp(signalName, query.signal_name) == 0; }); + + if (std::end(firstFrameSignals) != signalNameIt) + { + glibWrapper.gFree(signals); + return std::string(*signalNameIt); + } + } + glibWrapper.gFree(signals); + + return std::nullopt; +} + GstCaps *createCapsFromMediaSource(const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, const std::unique_ptr &source) diff --git a/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp b/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp index ef4605d4e..8b148c9ae 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSamples.cpp @@ -22,6 +22,7 @@ #include "IGstGenericPlayerPrivate.h" #include "RialtoServerLogging.h" #include "TypeConverters.h" +#include namespace firebolt::rialto::server::tasks::generic { @@ -42,12 +43,13 @@ AttachSamples::AttachSamples(GenericPlayerContext &context, dynamic_cast(*mediaSegment); VideoData videoData = {gstBuffer, videoSegment.getWidth(), videoSegment.getHeight(), videoSegment.getFrameRate(), videoSegment.getCodecData()}; - m_videoData.push_back(videoData); + m_videoData.push_back(std::move(videoData)); } catch (const std::exception &e) { // Catching error, but continuing as best as we can RIALTO_SERVER_LOG_ERROR("Failed to get the video segment, reason: %s", e.what()); + m_gstWrapper->gstBufferUnref(gstBuffer); } } else if (mediaSegment->getType() == firebolt::rialto::MediaSourceType::AUDIO) @@ -62,12 +64,13 @@ AttachSamples::AttachSamples(GenericPlayerContext &context, audioSegment.getCodecData(), audioSegment.getClippingStart(), audioSegment.getClippingEnd()}; - m_audioData.push_back(audioData); + m_audioData.push_back(std::move(audioData)); } catch (const std::exception &e) { // Catching error, but continuing as best as we can RIALTO_SERVER_LOG_ERROR("Failed to get the audio segment, reason: %s", e.what()); + m_gstWrapper->gstBufferUnref(gstBuffer); } } else if (mediaSegment->getType() == firebolt::rialto::MediaSourceType::SUBTITLE) diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index f65daa212..32237463d 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -59,7 +59,7 @@ void AttachSource::execute() const { addSource(); } - else if (m_attachedSource->getType() == MediaSourceType::AUDIO && m_context.audioSourceRemoved) + else if (m_attachedSource->getType() == MediaSourceType::AUDIO) { reattachAudioSource(); } @@ -77,22 +77,26 @@ void AttachSource::addSource() const RIALTO_SERVER_LOG_ERROR("Failed to create caps from media source"); return; } + std::string profilerInfo; gchar *capsStr = m_gstWrapper->gstCapsToString(caps); GstElement *appSrc = nullptr; if (m_attachedSource->getType() == MediaSourceType::AUDIO) { RIALTO_SERVER_LOG_MIL("Adding Audio appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "audsrc"); + profilerInfo = "audsrc"; } else if (m_attachedSource->getType() == MediaSourceType::VIDEO) { RIALTO_SERVER_LOG_MIL("Adding Video appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "vidsrc"); + profilerInfo = "vidsrc"; } else if (m_attachedSource->getType() == MediaSourceType::SUBTITLE) { RIALTO_SERVER_LOG_MIL("Adding Subtitle appsrc with caps %s", capsStr); appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "subsrc"); + profilerInfo = "subsrc"; if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_context.pipeline), "text-sink")) { @@ -102,6 +106,13 @@ void AttachSource::addSource() const m_glibWrapper->gObjectSet(m_context.pipeline, "text-sink", elem, nullptr); } } + if (appSrc) + { + auto recordId = m_context.gstProfiler->createRecord("Created AppSrc Element", profilerInfo); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + } + m_glibWrapper->gFree(capsStr); m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(appSrc), caps); @@ -113,15 +124,14 @@ void AttachSource::addSource() const void AttachSource::reattachAudioSource() const { + m_context.firstAudioFrameReceived = false; + if (!m_player.reattachSource(m_attachedSource)) { RIALTO_SERVER_LOG_ERROR("Reattaching source failed!"); return; } - // Restart audio sink - m_player.setPlaybinFlags(true); - m_context.streamInfo[m_attachedSource->getType()].isDataNeeded = true; m_context.audioSourceRemoved = false; m_player.notifyNeedMediaData(MediaSourceType::AUDIO); diff --git a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp index c027c69a2..84f9e0623 100644 --- a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp +++ b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp @@ -31,7 +31,7 @@ namespace firebolt::rialto::server::tasks::generic { CheckAudioUnderflow::CheckAudioUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper) + const std::shared_ptr &gstWrapper) : m_context{context}, m_player(player), m_gstPlayerClient{client}, m_gstWrapper{gstWrapper} { } diff --git a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp index 83e6bef12..df24966c6 100644 --- a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp +++ b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp @@ -19,6 +19,7 @@ #include "tasks/generic/DeepElementAdded.h" #include "RialtoServerLogging.h" +#include "Utils.h" namespace { @@ -53,6 +54,8 @@ DeepElementAdded::DeepElementAdded(GenericPlayerContext &context, IGstGenericPla m_glibWrapper->gSignalConnect(G_OBJECT(m_element), "have-type", G_CALLBACK(onHaveType), &m_player); m_callbackRegistered = true; } + + m_context.gstProfiler->scheduleGstElementRecord(m_element); } } } @@ -61,6 +64,7 @@ DeepElementAdded::~DeepElementAdded() { RIALTO_SERVER_LOG_DEBUG("DeepElementAdded finished"); m_glibWrapper->gFree(m_elementName); + m_gstWrapper->gstObjectUnref(m_element); } void DeepElementAdded::execute() const @@ -69,42 +73,48 @@ void DeepElementAdded::execute() const m_context.playbackGroup.m_gstPipeline = GST_ELEMENT(m_pipeline); RIALTO_SERVER_LOG_DEBUG("Element = %p Bin = %p Pipeline = %p", m_element, m_bin, m_pipeline); - if (m_callbackRegistered) - { - m_context.playbackGroup.m_curAudioTypefind = m_element; - } if (m_elementName) { if (m_gstWrapper->gstObjectCast(m_bin) == m_gstWrapper->gstObjectCast(m_context.playbackGroup.m_curAudioDecodeBin)) { - if (m_glibWrapper->gStrrstr(m_elementName, "parse")) + if (isAudioParser(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioParse = %s", m_elementName); m_context.playbackGroup.m_curAudioParse = m_element; } - else if (m_glibWrapper->gStrrstr(m_elementName, "dec")) + else if (isAudioDecoder(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioDecoder = %s", m_elementName); m_context.playbackGroup.m_curAudioDecoder = m_element; } + else if (m_callbackRegistered && m_glibWrapper->gStrrstr(m_elementName, "typefind")) + { + RIALTO_SERVER_LOG_DEBUG("curAudioTypefind = %s", m_elementName); + m_context.playbackGroup.m_curAudioTypefind = m_element; + } } - else + else if (isAudioSink(*m_gstWrapper, m_element)) { - if (m_glibWrapper->gStrrstr(m_elementName, "audiosink")) + GstElement *audioSinkParent = reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); + if (audioSinkParent) { - GstElement *audioSinkParent = - reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); - if (audioSinkParent) + gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); + RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); + if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) { - gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); - RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); - if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) + RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); + if (m_context.playbackGroup.m_curAudioPlaysinkBin) { - RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); - m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + // Unref previous audio playsink bin, if exists + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); } - m_glibWrapper->gFree(audioSinkParentName); + m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + } + else + { + m_gstWrapper->gstObjectUnref(audioSinkParent); } + m_glibWrapper->gFree(audioSinkParentName); } } } diff --git a/media/server/gstplayer/source/tasks/generic/Eos.cpp b/media/server/gstplayer/source/tasks/generic/Eos.cpp index 371487570..af2a8193e 100644 --- a/media/server/gstplayer/source/tasks/generic/Eos.cpp +++ b/media/server/gstplayer/source/tasks/generic/Eos.cpp @@ -26,7 +26,7 @@ namespace firebolt::rialto::server::tasks::generic { Eos::Eos(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, + const std::shared_ptr &gstWrapper, const firebolt::rialto::MediaSourceType &type) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_type{type} { diff --git a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp index f2a5cf323..101f2e823 100644 --- a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp @@ -67,7 +67,6 @@ void appSrcEnoughData(GstAppSrc *src, gpointer user_data) */ gboolean appSrcSeekData(GstAppSrc *src, guint64 offset, gpointer user_data) { - appSrcEnoughData(src, user_data); return TRUE; } } // namespace @@ -124,5 +123,8 @@ void FinishSetupSource::execute() const m_context.setupSourceFinished = true; RIALTO_SERVER_LOG_MIL("All sources attached."); + auto recordId = m_context.gstProfiler->createRecord("All Sources Attached"); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp b/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp new file mode 100644 index 000000000..65f5ba27e --- /dev/null +++ b/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp @@ -0,0 +1,75 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "tasks/generic/FirstFrameReceived.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" + +namespace firebolt::rialto::server::tasks::generic +{ +FirstFrameReceived::FirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + IGstGenericPlayerClient *client, MediaSourceType sourceType, + AudioFirstFrameAction audioAction) + : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_sourceType{sourceType}, + m_audioAction{audioAction} +{ + RIALTO_SERVER_LOG_DEBUG("Constructing FirstFrameReceived"); +} + +FirstFrameReceived::~FirstFrameReceived() +{ + RIALTO_SERVER_LOG_DEBUG("FirstFrameReceived finished"); +} + +void FirstFrameReceived::execute() const +{ + RIALTO_SERVER_LOG_DEBUG("Executing FirstFrameReceived for %s source", common::convertMediaSourceType(m_sourceType)); + + if (m_sourceType == MediaSourceType::AUDIO) + { + if (m_audioAction == AudioFirstFrameAction::CLEAR_PROBE) + { + m_player.clearAudioFirstFrameFallbackProbe(); + } + else if (m_audioAction == AudioFirstFrameAction::CLEAR_PROBE_STATE) + { + m_player.clearAudioFirstFrameFallbackProbeState(); + } + + if (m_context.audioSourceRemoved) + { + RIALTO_SERVER_LOG_DEBUG("Ignoring first audio frame notification - audio source removed"); + return; + } + + if (m_context.firstAudioFrameReceived) + { + RIALTO_SERVER_LOG_DEBUG("First audio frame notification already sent"); + return; + } + + m_context.firstAudioFrameReceived = true; + } + + if (m_gstPlayerClient) + { + m_gstPlayerClient->notifyFirstFrameReceived(m_sourceType); + } +} +} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 9c7c5196f..f755535c0 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -25,10 +25,10 @@ namespace firebolt::rialto::server::tasks::generic { Flush::Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime) + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, - m_resetTime{resetTime} + m_resetTime{resetTime}, m_isAsync{isAsync} { RIALTO_SERVER_LOG_DEBUG("Constructing Flush"); } @@ -40,13 +40,6 @@ Flush::~Flush() void Flush::execute() const { - if (m_context.flushOnPrerollController.shouldPostponeFlush(m_type)) - { - RIALTO_SERVER_LOG_WARN("Postponing Flush for %s source", common::convertMediaSourceType(m_type)); - m_player.postponeFlush(m_type, m_resetTime); - return; - } - RIALTO_SERVER_LOG_DEBUG("Executing Flush for %s source", common::convertMediaSourceType(m_type)); // Get source first @@ -79,13 +72,19 @@ void Flush::execute() const streamInfo.buffers.clear(); m_context.initialPositions.erase(sourceElem->second.appSrc); + if (m_type == MediaSourceType::AUDIO) + { + m_player.clearAudioFirstFrameFallbackProbe(); + m_context.firstAudioFrameReceived = false; + } + m_gstPlayerClient->invalidateActiveRequests(m_type); if (GST_STATE(m_context.pipeline) >= GST_STATE_PAUSED) { - m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - m_context.flushOnPrerollController.setFlushing(m_type, GST_STATE(m_context.pipeline)); + m_context.flushOnPrerollController->waitIfRequired(m_type); + RIALTO_SERVER_LOG_MIL("Sending flush event for %s source.", common::convertMediaSourceType(m_type)); // Flush source GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) @@ -98,6 +97,11 @@ void Flush::execute() const { RIALTO_SERVER_LOG_WARN("failed to send flush-stop event for %s", common::convertMediaSourceType(m_type)); } + + if (m_isAsync) + { + m_context.flushOnPrerollController->setFlushing(m_type); + } } else { @@ -109,6 +113,11 @@ void Flush::execute() const m_context.endOfStreamInfo.erase(m_type); m_context.eosNotified = false; + if (m_resetTime) + { + m_context.streamPosition.store(-1); + } + // Notify client, that flush has been finished m_gstPlayerClient->notifySourceFlushed(m_type); diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 97b2d3452..682e31cbe 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -25,6 +25,7 @@ #include "tasks/generic/EnoughData.h" #include "tasks/generic/Eos.h" #include "tasks/generic/FinishSetupSource.h" +#include "tasks/generic/FirstFrameReceived.h" #include "tasks/generic/Flush.h" #include "tasks/generic/HandleBusMessage.h" #include "tasks/generic/NeedData.h" @@ -42,6 +43,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSubtitleOffset.h" @@ -272,10 +274,17 @@ std::unique_ptr GenericPlayerTaskFactory::createUnderflow(GenericPl return std::make_unique(context, player, m_client, underflowEnabled, sourceType); } +std::unique_ptr GenericPlayerTaskFactory::createFirstFrameReceived(GenericPlayerContext &context, + IGstGenericPlayerPrivate &player, + MediaSourceType sourceType, + AudioFirstFrameAction audioAction) const +{ + return std::make_unique(context, player, m_client, sourceType, audioAction); +} + std::unique_ptr GenericPlayerTaskFactory::createUpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - GstElement *typefind, - const GstCaps *caps) const + GstElement *typefind, GstCaps *caps) const { return std::make_unique(context, player, m_gstWrapper, m_glibWrapper, typefind, caps); @@ -295,9 +304,9 @@ std::unique_ptr GenericPlayerTaskFactory::createPing(std::unique_pt std::unique_ptr GenericPlayerTaskFactory::createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, - bool resetTime) const + bool resetTime, bool isAsync) const { - return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime); + return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime, isAsync); } std::unique_ptr @@ -334,6 +343,14 @@ GenericPlayerTaskFactory::createSetImmediateOutput(GenericPlayerContext &context return std::make_unique(context, player, type, immediateOutput); } +std::unique_ptr +GenericPlayerTaskFactory::createSetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const +{ + return std::make_unique(context, player, type, reportDecodeErrors); +} + std::unique_ptr GenericPlayerTaskFactory::createSetBufferingLimit(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, std::uint32_t limit) const diff --git a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp index a6bbdcb8f..7616721e6 100644 --- a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp +++ b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp @@ -54,13 +54,18 @@ void HandleBusMessage::execute() const { GstState oldState, newState, pending; m_gstWrapper->gstMessageParseStateChanged(m_message, &oldState, &newState, &pending); - RIALTO_SERVER_LOG_MIL("State changed (old: %s, new: %s, pending: %s)", - m_gstWrapper->gstElementStateGetName(oldState), - m_gstWrapper->gstElementStateGetName(newState), - m_gstWrapper->gstElementStateGetName(pending)); + const char *oldStateName = m_gstWrapper->gstElementStateGetName(oldState); + const char *newStateName = m_gstWrapper->gstElementStateGetName(newState); + const char *pendingStateName = m_gstWrapper->gstElementStateGetName(pending); + RIALTO_SERVER_LOG_MIL("State changed (old: %s, new: %s, pending: %s)", oldStateName, newStateName, + pendingStateName); + auto recordId = m_context.gstProfiler->createRecord("Pipeline State Changed", newStateName); + if (recordId) + m_context.gstProfiler->logRecord(recordId.value()); + if (newState == GST_STATE_PLAYING) + m_context.gstProfiler->logPipelineSummary(); - std::string filename = std::string(m_gstWrapper->gstElementStateGetName(oldState)) + "-" + - std::string(m_gstWrapper->gstElementStateGetName(newState)); + std::string filename = std::string(oldStateName) + "-" + std::string(newStateName); m_gstWrapper->gstDebugBinToDotFileWithTs(GST_BIN(m_context.pipeline), GST_DEBUG_GRAPH_SHOW_ALL, filename.c_str()); if (!m_gstPlayerClient) @@ -71,15 +76,15 @@ void HandleBusMessage::execute() const { case GST_STATE_NULL: { - m_context.flushOnPrerollController.reset(); m_gstPlayerClient->notifyPlaybackState(PlaybackState::STOPPED); break; } case GST_STATE_PAUSED: { + m_player.startNotifyPlaybackInfoTimer(); + m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); if (pending != GST_STATE_PAUSED) { - m_context.flushOnPrerollController.stateReached(newState); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. @@ -93,8 +98,8 @@ void HandleBusMessage::execute() const // Subsequent newState==GST_STATE_PAUSED, pending!=GST_STATE_PAUSED transition will // indicate that the pipeline is prerolled and it reached GST_STATE_PAUSED state after seek. m_gstPlayerClient->notifyPlaybackState(PlaybackState::PAUSED); - m_player.notifyPlaybackInfo(); } + if (m_player.hasSourceType(MediaSourceType::SUBTITLE)) { m_player.stopSubtitleClockResyncTimer(); @@ -103,8 +108,6 @@ void HandleBusMessage::execute() const } case GST_STATE_PLAYING: { - m_context.flushOnPrerollController.stateReached(newState); - m_player.executePostponedFlushes(); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. @@ -128,8 +131,12 @@ void HandleBusMessage::execute() const break; } case GST_STATE_VOID_PENDING: + { + break; + } case GST_STATE_READY: { + m_player.stopNotifyPlaybackInfoTimer(); break; } } @@ -277,6 +284,32 @@ void HandleBusMessage::execute() const m_glibWrapper->gErrorFree(err); break; } + case GST_MESSAGE_APPLICATION: + { + const GstStructure *structure = gst_message_get_structure(m_message); + if (structure && m_gstWrapper->gstStructureHasName(structure, "HDCPProtectionFailure")) + { + if (m_gstPlayerClient) + { + const gchar *kElementName = GST_ELEMENT_NAME(GST_ELEMENT(GST_MESSAGE_SRC(m_message))); + if (g_strrstr(kElementName, "video")) + { + m_gstPlayerClient->notifyPlaybackError(firebolt::rialto::MediaSourceType::VIDEO, + PlaybackError::OUTPUT_PROTECTION); + } + else if (g_strrstr(kElementName, "audio")) + { + m_gstPlayerClient->notifyPlaybackError(firebolt::rialto::MediaSourceType::AUDIO, + PlaybackError::OUTPUT_PROTECTION); + } + else + { + RIALTO_SERVER_LOG_WARN("Unknown source for HDCPProtectionFailure from '%s'", kElementName); + } + } + } + break; + } default: break; } diff --git a/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp b/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp index 13f5b870b..049f8b89a 100644 --- a/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp +++ b/media/server/gstplayer/source/tasks/generic/ProcessAudioGap.cpp @@ -25,7 +25,7 @@ namespace firebolt::rialto::server::tasks::generic ProcessAudioGap::ProcessAudioGap( GenericPlayerContext &context, const std::shared_ptr &gstWrapper, const std::shared_ptr &glibWrapper, - const std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, std::int64_t position, std::uint32_t duration, std::int64_t discontinuityGap, bool audioAac) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_position{position}, m_duration{duration}, diff --git a/media/server/gstplayer/source/tasks/generic/ReadShmDataAndAttachSamples.cpp b/media/server/gstplayer/source/tasks/generic/ReadShmDataAndAttachSamples.cpp index 1df7213a7..1be6acc9d 100644 --- a/media/server/gstplayer/source/tasks/generic/ReadShmDataAndAttachSamples.cpp +++ b/media/server/gstplayer/source/tasks/generic/ReadShmDataAndAttachSamples.cpp @@ -25,6 +25,11 @@ #include "RialtoServerLogging.h" #include "TypeConverters.h" +namespace +{ +constexpr auto kDelayThreshold{5 * GST_SECOND}; +} // namespace + namespace firebolt::rialto::server::tasks::generic { ReadShmDataAndAttachSamples::ReadShmDataAndAttachSamples( @@ -104,6 +109,16 @@ void ReadShmDataAndAttachSamples::execute() const RIALTO_SERVER_LOG_DEBUG("%s data received. First ts: %" GST_TIME_FORMAT " last ts: %" GST_TIME_FORMAT, common::convertMediaSourceType(kMediaType), GST_TIME_ARGS(kFirstTimestamp), GST_TIME_ARGS(kLastTimestamp)); + if (!m_dataReader->isBufferFull() && m_context.streamPosition.load() != -1 && + kLastTimestamp >= m_context.streamPosition.load() + kDelayThreshold) + { + RIALTO_SERVER_LOG_DEBUG("Received %zu segments, current pos: %" GST_TIME_FORMAT + " last received ts: %" GST_TIME_FORMAT ", scheduling NeedMediaData with delay", + mediaSegments.size(), GST_TIME_ARGS(m_context.streamPosition.load()), + GST_TIME_ARGS(kLastTimestamp)); + m_player.notifyNeedMediaDataWithDelay(kMediaType); + return; + } m_player.notifyNeedMediaData(kMediaType); } } diff --git a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp b/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp index 29127c08c..f09e8f3cb 100644 --- a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp @@ -25,7 +25,7 @@ namespace firebolt::rialto::server::tasks::generic { RemoveSource::RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, + const std::shared_ptr &gstWrapper, const MediaSourceType &type) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type} { @@ -45,36 +45,32 @@ void RemoveSource::execute() const RIALTO_SERVER_LOG_DEBUG("RemoveSource not supported for type != AUDIO"); return; } + m_player.clearAudioFirstFrameFallbackProbe(); + m_context.firstAudioFrameReceived = false; m_context.audioSourceRemoved = true; - m_gstPlayerClient->invalidateActiveRequests(m_type); - GstElement *source{nullptr}; - auto sourceElem = m_context.streamInfo.find(m_type); - if (sourceElem != m_context.streamInfo.end()) + if (m_gstPlayerClient) { - source = sourceElem->second.appSrc; + m_gstPlayerClient->invalidateActiveRequests(m_type); } - if (!source) + auto sourceElem = m_context.streamInfo.find(m_type); + if (sourceElem == m_context.streamInfo.end()) { - RIALTO_SERVER_LOG_WARN("failed to flush - source is NULL"); + RIALTO_SERVER_LOG_WARN("Failed to remove source - streamInfo not found"); return; } - sourceElem->second.buffers.clear(); - sourceElem->second.isDataNeeded = false; - sourceElem->second.isNeedDataPending = false; - m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); - if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) - { - RIALTO_SERVER_LOG_WARN("failed to send flush-start event"); - } - GstEvent *flushStop = m_gstWrapper->gstEventNewFlushStop(FALSE); - if (!m_gstWrapper->gstElementSendEvent(source, flushStop)) + StreamInfo &streamInfo = sourceElem->second; + for (auto &buffer : streamInfo.buffers) { - RIALTO_SERVER_LOG_WARN("failed to send flush-stop event"); + m_gstWrapper->gstBufferUnref(buffer); } + streamInfo.buffers.clear(); + streamInfo.isDataNeeded = false; + streamInfo.isNeedDataPending = false; + m_context.initialPositions.erase(streamInfo.appSrc); - // Turn audio off, removing audio sink from playsink - m_player.setPlaybinFlags(false); + // Reset Eos info + m_context.endOfStreamInfo.erase(m_type); + m_context.eosNotified = false; RIALTO_SERVER_LOG_MIL("%s source removed", common::convertMediaSourceType(m_type)); } diff --git a/media/server/gstplayer/source/tasks/generic/SetMute.cpp b/media/server/gstplayer/source/tasks/generic/SetMute.cpp index da66764a8..5bd4743e1 100644 --- a/media/server/gstplayer/source/tasks/generic/SetMute.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetMute.cpp @@ -25,8 +25,8 @@ namespace firebolt::rialto::server::tasks::generic { SetMute::SetMute(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const MediaSourceType &mediaSourceType, bool mute) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_mediaSourceType{mediaSourceType}, m_mute{mute} diff --git a/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp b/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp index f5f8f609c..cdcbc19a7 100644 --- a/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetPlaybackRate.cpp @@ -21,6 +21,7 @@ #include "IGlibWrapper.h" #include "IGstWrapper.h" #include "RialtoServerLogging.h" +#include "TypeConverters.h" #include namespace @@ -31,8 +32,9 @@ const char kCustomInstantRateChangeEventName[] = "custom-instant-rate-change"; namespace firebolt::rialto::server::tasks::generic { SetPlaybackRate::SetPlaybackRate(GenericPlayerContext &context, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, double rate) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + double rate) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rate{rate} { RIALTO_SERVER_LOG_DEBUG("Constructing SetPlaybackRate"); @@ -75,7 +77,7 @@ void SetPlaybackRate::execute() const GstSegment *segment{m_gstWrapper->gstSegmentNew()}; m_gstWrapper->gstSegmentInit(segment, GST_FORMAT_TIME); segment->rate = m_rate; - segment->start = GST_CLOCK_TIME_NONE; + segment->start = m_context.audioGstSegmentPosition; segment->position = GST_CLOCK_TIME_NONE; success = m_gstWrapper->gstPadSendEvent(GST_BASE_SINK_PAD(audioSink), m_gstWrapper->gstEventNewSegment(segment)); RIALTO_SERVER_LOG_DEBUG("Sent new segment, success = %s", success ? "true" : "false"); diff --git a/media/server/gstplayer/source/tasks/generic/SetPosition.cpp b/media/server/gstplayer/source/tasks/generic/SetPosition.cpp index df9d1b72b..adddbd0f3 100644 --- a/media/server/gstplayer/source/tasks/generic/SetPosition.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetPosition.cpp @@ -28,7 +28,8 @@ namespace firebolt::rialto::server::tasks::generic { SetPosition::SetPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, std::int64_t position) + const std::shared_ptr &gstWrapper, + std::int64_t position) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_position{position} { RIALTO_SERVER_LOG_DEBUG("Constructing SetPosition"); @@ -90,6 +91,8 @@ void SetPosition::execute() const m_context.endOfStreamInfo.clear(); m_context.eosNotified = false; + m_context.streamPosition.store(m_position); + m_gstPlayerClient->notifyPlaybackState(PlaybackState::SEEK_DONE); // Trigger NeedMediaData for all attached sources diff --git a/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp b/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp new file mode 100644 index 000000000..6815df0ce --- /dev/null +++ b/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp @@ -0,0 +1,52 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * 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 "SetReportDecodeErrors.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" + +namespace firebolt::rialto::server::tasks::generic +{ +SetReportDecodeErrors::SetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const MediaSourceType &type, bool reportDecodeErrors) + : m_context{context}, m_player(player), m_type{type}, m_reportDecodeErrors{reportDecodeErrors} +{ + RIALTO_SERVER_LOG_DEBUG("Constructing SetReportDecodeErrors"); +} + +SetReportDecodeErrors::~SetReportDecodeErrors() +{ + RIALTO_SERVER_LOG_DEBUG("SetReportDecodeErrors finished"); +} + +void SetReportDecodeErrors::execute() const +{ + RIALTO_SERVER_LOG_DEBUG("Executing SetReportDecodeErrors for %s source", common::convertMediaSourceType(m_type)); + + m_context.pendingReportDecodeErrorsForVideo = m_reportDecodeErrors; + + if (!m_context.pipeline) + { + RIALTO_SERVER_LOG_WARN("Pipeline not available yet - cannot apply report-decode-errors setting"); + return; + } + + m_player.setReportDecodeErrors(); +} +} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp b/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp index b29ab0572..7b72b08d4 100644 --- a/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetTextTrackIdentifier.cpp @@ -24,7 +24,7 @@ namespace firebolt::rialto::server::tasks::generic { SetTextTrackIdentifier::SetTextTrackIdentifier(GenericPlayerContext &context, - std::shared_ptr glibWrapper, + const std::shared_ptr &glibWrapper, const std::string &textTrackIdentifier) : m_context{context}, m_glibWrapper{glibWrapper}, m_textTrackIdentifier{textTrackIdentifier} { diff --git a/media/server/gstplayer/source/tasks/generic/SetVolume.cpp b/media/server/gstplayer/source/tasks/generic/SetVolume.cpp index 0ceeae94f..675dea52c 100644 --- a/media/server/gstplayer/source/tasks/generic/SetVolume.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetVolume.cpp @@ -27,9 +27,9 @@ namespace firebolt::rialto::server::tasks::generic { SetVolume::SetVolume(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, - std::shared_ptr rdkGstreamerUtilsWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + const std::shared_ptr &rdkGstreamerUtilsWrapper, double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_rdkGstreamerUtilsWrapper{rdkGstreamerUtilsWrapper}, m_targetVolume{targetVolume}, @@ -102,6 +102,7 @@ void SetVolume::execute() const RIALTO_SERVER_LOG_DEBUG("Fade String: %s", fadeStr); m_glibWrapper->gObjectSet(audioSink, "audio-fade", fadeStr, nullptr); + m_context.audioFadeVolume = m_targetVolume; m_context.audioFadeEnabled = true; } else if (m_rdkGstreamerUtilsWrapper->isSocAudioFadeSupported()) @@ -109,6 +110,7 @@ void SetVolume::execute() const RIALTO_SERVER_LOG_DEBUG("SOC audio fading is supported, applying SOC audio fade"); auto rguEaseType = convertEaseTypeToRguEase(m_easeType); m_rdkGstreamerUtilsWrapper->doAudioEasingonSoc(m_targetVolume, m_volumeDuration, rguEaseType); + m_context.audioFadeVolume = m_targetVolume; m_context.audioFadeEnabled = true; } else diff --git a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index 99588430a..1da193120 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -61,6 +61,52 @@ void videoUnderflowCallback(GstElement *object, guint fifoDepth, gpointer queueD player->scheduleVideoUnderflow(); } +/** + * @brief Callback for first video frame event from the emitting video element. Called by the Gstreamer thread. + * + * @param[in] object : the object that emitted the signal + * @param[in] fifoDepth : the fifo depth (may be 0) + * @param[in] queueDepth : the queue depth (may be NULL) + * @param[in] self : The pointer to IGstGenericPlayerPrivate + */ +void firstVideoFrameCallback(GstElement *object, guint fifoDepth, gpointer queueDepth, gpointer self) +{ + firebolt::rialto::server::IGstGenericPlayerPrivate *player = + static_cast(self); + player->scheduleFirstVideoFrameReceived(); +} + +/** + * @brief Callback for first audio frame event from the emitting audio element. Called by the Gstreamer thread. + * + * @param[in] object : the object that emitted the signal + * @param[in] fifoDepth : the fifo depth (may be 0) + * @param[in] queueDepth : the queue depth (may be NULL) + * @param[in] self : The pointer to IGstGenericPlayerPrivate + */ +void firstAudioFrameCallback(GstElement *object, guint fifoDepth, gpointer queueDepth, gpointer self) +{ + firebolt::rialto::server::IGstGenericPlayerPrivate *player = + static_cast(self); + player->scheduleFirstAudioFrameReceived(firebolt::rialto::server::AudioFirstFrameAction::CLEAR_PROBE); +} + +/** + * @brief Fallback probe callback for first audio frame on sink pad. + */ +GstPadProbeReturn firstAudioFrameProbeCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) +{ + if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER) || !GST_PAD_PROBE_INFO_BUFFER(info)) + { + return GST_PAD_PROBE_OK; + } + + firebolt::rialto::server::IGstGenericPlayerPrivate *player = + static_cast(self); + player->scheduleFirstAudioFrameReceived(firebolt::rialto::server::AudioFirstFrameAction::CLEAR_PROBE_STATE); + return GST_PAD_PROBE_REMOVE; +} + /** * @brief Callback for a autovideosink when a child has been added to the sink. * @@ -129,8 +175,8 @@ void autoAudioSinkChildRemovedCallback(GstChildProxy *obj, GObject *object, gcha namespace firebolt::rialto::server::tasks::generic { SetupElement::SetupElement(GenericPlayerContext &context, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, IGstGenericPlayerPrivate &player, GstElement *element) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_player{player}, m_element{element} { @@ -246,6 +292,10 @@ void SetupElement::execute() const RIALTO_SERVER_LOG_INFO("Setting video decoder handle for subtitle sink: %p", m_element); m_context.isVideoHandleSet = true; } + if (m_context.pendingReportDecodeErrorsForVideo.has_value()) + { + m_player.setReportDecodeErrors(); + } } } @@ -269,6 +319,44 @@ void SetupElement::execute() const G_CALLBACK(videoUnderflowCallback), &m_player); } } + + std::optional firstFrameSignalName = getFirstFrameSignalName(*m_glibWrapper, m_element); + if (firstFrameSignalName) + { + if (isVideo(*m_gstWrapper, m_element)) + { + RIALTO_SERVER_LOG_INFO("Connecting first video frame callback for signal: %s", + firstFrameSignalName.value().c_str()); + m_glibWrapper->gSignalConnect(m_element, firstFrameSignalName.value().c_str(), + G_CALLBACK(firstVideoFrameCallback), &m_player); + } + else if (isAudio(*m_gstWrapper, m_element)) + { + RIALTO_SERVER_LOG_INFO("Connecting first audio frame callback for signal: %s", + firstFrameSignalName.value().c_str()); + m_glibWrapper->gSignalConnect(m_element, firstFrameSignalName.value().c_str(), + G_CALLBACK(firstAudioFrameCallback), &m_player); + } + } + else if (isAudioSink(*m_gstWrapper, m_element)) + { + GstPad *sinkPad = m_gstWrapper->gstElementGetStaticPad(m_element, "sink"); + if (sinkPad) + { + gulong probeId = m_gstWrapper->gstPadAddProbe(sinkPad, GST_PAD_PROBE_TYPE_BUFFER, + firstAudioFrameProbeCallback, &m_player, nullptr); + + if (probeId != 0) + { + RIALTO_SERVER_LOG_INFO("Installed first audio frame fallback probe on sink"); + m_player.setAudioFirstFrameFallbackProbe(sinkPad, probeId); + } + else + { + m_gstWrapper->gstObjectUnref(sinkPad); + } + } + } } if (isVideoSink(*m_gstWrapper, m_element)) @@ -309,6 +397,12 @@ void SetupElement::execute() const { m_player.setBufferingLimit(); } + if (m_context.isLive && + m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_element), "enable-rate-correction")) + { + RIALTO_SERVER_LOG_INFO("Enabling rate correction for broadcom decoder."); + m_glibWrapper->gObjectSet(m_element, "enable-rate-correction", TRUE, nullptr); + } } else if (isAudioSink(*m_gstWrapper, m_element)) { @@ -333,7 +427,6 @@ void SetupElement::execute() const { m_gstWrapper->gstBaseParseSetPtsInterpolation(GST_BASE_PARSE(m_element), FALSE); } - m_gstWrapper->gstObjectUnref(m_element); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/Stop.cpp b/media/server/gstplayer/source/tasks/generic/Stop.cpp index 3f82478b5..7bfaaeaf4 100644 --- a/media/server/gstplayer/source/tasks/generic/Stop.cpp +++ b/media/server/gstplayer/source/tasks/generic/Stop.cpp @@ -37,7 +37,10 @@ Stop::~Stop() void Stop::execute() const { RIALTO_SERVER_LOG_DEBUG("Executing Stop"); + m_context.firstAudioFrameReceived = false; + m_player.clearAudioFirstFrameFallbackProbe(); m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); + m_player.stopNotifyPlaybackInfoTimer(); m_player.changePipelineState(GST_STATE_NULL); for (auto &streamInfo : m_context.streamInfo) { diff --git a/media/server/gstplayer/source/tasks/generic/SynchroniseSubtitleClock.cpp b/media/server/gstplayer/source/tasks/generic/SynchroniseSubtitleClock.cpp index 705440431..b7e3a5726 100644 --- a/media/server/gstplayer/source/tasks/generic/SynchroniseSubtitleClock.cpp +++ b/media/server/gstplayer/source/tasks/generic/SynchroniseSubtitleClock.cpp @@ -42,49 +42,44 @@ SynchroniseSubtitleClock::~SynchroniseSubtitleClock() void SynchroniseSubtitleClock::execute() const { RIALTO_SERVER_LOG_DEBUG("Executing SynchroniseSubtitleClock"); - if (m_context.videoSink) + gint64 position = m_player.getPosition(m_context.pipeline); + if (position == -1) { - gint64 position = 0; - if (m_gstWrapper->gstElementQueryPosition(m_context.videoSink, GST_FORMAT_TIME, &position)) - { - RIALTO_SERVER_LOG_DEBUG("Videosink position: %" PRId64 " ns", position); - } + RIALTO_SERVER_LOG_WARN("Getting the position failed"); + return; + } - auto sourceElem = m_context.streamInfo.find(MediaSourceType::SUBTITLE); - GstElement *source{nullptr}; + auto sourceElem = m_context.streamInfo.find(MediaSourceType::SUBTITLE); + GstElement *source{nullptr}; - if (sourceElem != m_context.streamInfo.end()) - { - source = sourceElem->second.appSrc; - } - else - { - RIALTO_SERVER_LOG_WARN("subtitle source not found"); - return; - } + if (sourceElem != m_context.streamInfo.end()) + { + source = sourceElem->second.appSrc; + } + else + { + RIALTO_SERVER_LOG_WARN("subtitle source not found"); + return; + } - GstStructure *structure = m_gstWrapper->gstStructureNew("current-pts", "pts", G_TYPE_UINT64, position, nullptr); - GstEvent *event = m_gstWrapper->gstEventNewCustom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure); + GstStructure *structure = m_gstWrapper->gstStructureNew("current-pts", "pts", G_TYPE_UINT64, position, nullptr); + GstEvent *event = m_gstWrapper->gstEventNewCustom(GST_EVENT_CUSTOM_DOWNSTREAM_OOB, structure); - if (event) + if (event) + { + if (m_gstWrapper->gstElementSendEvent(source, event)) { - if (m_gstWrapper->gstElementSendEvent(source, event)) - { - RIALTO_SERVER_LOG_DEBUG("Sent current-pts event to subtitlesource"); - } - else - { - RIALTO_SERVER_LOG_ERROR("Failed to send current-pts event to source"); - } + RIALTO_SERVER_LOG_DEBUG("Sent current-pts event to subtitlesource"); } else { - RIALTO_SERVER_LOG_ERROR("Failed to create current-pts event"); + RIALTO_SERVER_LOG_ERROR("Failed to send current-pts event to source"); } } else { - RIALTO_SERVER_LOG_ERROR("video-sink is NULL"); + RIALTO_SERVER_LOG_ERROR("Failed to create current-pts event"); + m_gstWrapper->gstStructureFree(structure); } } diff --git a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp index 36f3f79b7..b944545da 100644 --- a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp +++ b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp @@ -23,9 +23,9 @@ namespace firebolt::rialto::server::tasks::generic { UpdatePlaybackGroup::UpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, - GstElement *typefind, const GstCaps *caps) + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, + GstElement *typefind, GstCaps *caps) : m_context{context}, m_player{player}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_typefind{typefind}, m_caps{caps} { @@ -35,6 +35,11 @@ UpdatePlaybackGroup::UpdatePlaybackGroup(GenericPlayerContext &context, IGstGene UpdatePlaybackGroup::~UpdatePlaybackGroup() { RIALTO_SERVER_LOG_DEBUG("UpdatePlaybackGroup finished"); + m_gstWrapper->gstObjectUnref(m_typefind); + if (m_caps) + { + m_gstWrapper->gstCapsUnref(m_caps); + } } void UpdatePlaybackGroup::execute() const @@ -68,6 +73,20 @@ void UpdatePlaybackGroup::execute() const { m_player.setUseBuffering(); } + if (m_context.playbackGroup.m_linkTypefindParser && m_context.playbackGroup.m_curAudioTypefind && + m_context.playbackGroup.m_curAudioParse) + { + if (m_gstWrapper->gstElementLink(m_context.playbackGroup.m_curAudioTypefind, + m_context.playbackGroup.m_curAudioParse)) + { + RIALTO_SERVER_LOG_DEBUG("Linked typefind to parser"); + m_context.playbackGroup.m_linkTypefindParser = false; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Failed to link typefind to parser"); + } + } } m_glibWrapper->gFree(elementName); m_gstWrapper->gstObjectUnref(typeFindParent); diff --git a/media/server/gstplayer/source/tasks/webAudio/Eos.cpp b/media/server/gstplayer/source/tasks/webAudio/Eos.cpp index be11e5405..33e2d91e4 100644 --- a/media/server/gstplayer/source/tasks/webAudio/Eos.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/Eos.cpp @@ -24,7 +24,7 @@ namespace firebolt::rialto::server::tasks::webaudio { -Eos::Eos(WebAudioPlayerContext &context, std::shared_ptr gstWrapper) +Eos::Eos(WebAudioPlayerContext &context, const std::shared_ptr &gstWrapper) : m_context{context}, m_gstWrapper{gstWrapper} { RIALTO_SERVER_LOG_DEBUG("Constructing Eos"); diff --git a/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp b/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp index e8ffad051..12de30447 100644 --- a/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/HandleBusMessage.cpp @@ -27,8 +27,8 @@ namespace firebolt::rialto::server::tasks::webaudio { HandleBusMessage::HandleBusMessage(WebAudioPlayerContext &context, IGstWebAudioPlayerPrivate &player, IGstWebAudioPlayerClient *client, - std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, GstMessage *message) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_message{message} diff --git a/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp b/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp index 5d42d4c31..533158f02 100644 --- a/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/SetCaps.cpp @@ -33,8 +33,8 @@ namespace class WebAudioCapsBuilder { public: - WebAudioCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper) + WebAudioCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper) : m_gstWrapper(gstWrapper), m_glibWrapper(glibWrapper) { } @@ -49,8 +49,8 @@ class WebAudioCapsBuilder class WebAudioPcmCapsBuilder : public WebAudioCapsBuilder { public: - WebAudioPcmCapsBuilder(std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, + WebAudioPcmCapsBuilder(const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const WebAudioPcmConfig &pcmConfig) : WebAudioCapsBuilder(gstWrapper, glibWrapper), m_pcmConfig(pcmConfig) { @@ -105,8 +105,9 @@ class WebAudioPcmCapsBuilder : public WebAudioCapsBuilder }; }; // namespace -SetCaps::SetCaps(WebAudioPlayerContext &context, std::shared_ptr gstWrapper, - std::shared_ptr glibWrapper, +SetCaps::SetCaps(WebAudioPlayerContext &context, + const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper, const std::string &audioMimeType, std::weak_ptr webAudioConfig) : m_context{context}, m_gstWrapper{gstWrapper}, m_glibWrapper{glibWrapper}, m_audioMimeType{audioMimeType} { diff --git a/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp b/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp index 88c94bfe6..84b2605a6 100644 --- a/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/SetVolume.cpp @@ -25,7 +25,7 @@ namespace firebolt::rialto::server::tasks::webaudio { SetVolume::SetVolume(WebAudioPlayerContext &context, - std::shared_ptr gstWrapper, double volume) + const std::shared_ptr &gstWrapper, double volume) : m_context{context}, m_gstWrapper{gstWrapper}, m_volume{volume} { RIALTO_SERVER_LOG_DEBUG("Constructing SetVolume"); diff --git a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp index 8d7aebd32..2827817c7 100644 --- a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp @@ -27,7 +27,7 @@ namespace firebolt::rialto::server::tasks::webaudio { WriteBuffer::WriteBuffer(WebAudioPlayerContext &context, - std::shared_ptr gstWrapper, uint8_t *mainPtr, + const std::shared_ptr &gstWrapper, uint8_t *mainPtr, uint32_t mainLength, uint8_t *wrapPtr, uint32_t wrapLength) : m_context{context}, m_gstWrapper{gstWrapper}, m_mainPtr{mainPtr}, m_mainLength{mainLength}, m_wrapPtr{wrapPtr}, m_wrapLength{wrapLength} @@ -102,6 +102,7 @@ void WriteBuffer::execute() const { std::unique_lock lock(m_context.writeBufferMutex); m_context.lastBytesWritten = bytesWritten; + ++m_context.writeCompletionCounter; } m_context.writeBufferCond.notify_one(); } diff --git a/media/server/ipc/include/MediaKeysCapabilitiesModuleService.h b/media/server/ipc/include/MediaKeysCapabilitiesModuleService.h index d7713b548..942990549 100644 --- a/media/server/ipc/include/MediaKeysCapabilitiesModuleService.h +++ b/media/server/ipc/include/MediaKeysCapabilitiesModuleService.h @@ -62,6 +62,10 @@ class MediaKeysCapabilitiesModuleService : public IMediaKeysCapabilitiesModuleSe const ::firebolt::rialto::IsServerCertificateSupportedRequest *request, ::firebolt::rialto::IsServerCertificateSupportedResponse *response, ::google::protobuf::Closure *done) override; + void getSupportedRobustnessLevels(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetSupportedRobustnessLevelsRequest *request, + ::firebolt::rialto::GetSupportedRobustnessLevelsResponse *response, + ::google::protobuf::Closure *done) override; private: service::ICdmService &m_cdmService; diff --git a/media/server/ipc/include/MediaPipelineClient.h b/media/server/ipc/include/MediaPipelineClient.h index 10af76a1e..685f2d325 100644 --- a/media/server/ipc/include/MediaPipelineClient.h +++ b/media/server/ipc/include/MediaPipelineClient.h @@ -45,6 +45,7 @@ class MediaPipelineClient : public IMediaPipelineClient void notifyCancelNeedMediaData(int32_t sourceId) override; void notifyQos(int32_t sourceId, const QosInfo &qosInfo) override; void notifyBufferUnderflow(int32_t sourceId) override; + void notifyFirstFrameReceived(int32_t sourceId) override; void notifyPlaybackError(int32_t sourceId, PlaybackError error) override; void notifySourceFlushed(int32_t sourceId) override; void notifyPlaybackInfo(const PlaybackInfo &playbackInfo) override; diff --git a/media/server/ipc/include/MediaPipelineModuleService.h b/media/server/ipc/include/MediaPipelineModuleService.h index f0321827b..91787419a 100644 --- a/media/server/ipc/include/MediaPipelineModuleService.h +++ b/media/server/ipc/include/MediaPipelineModuleService.h @@ -84,10 +84,20 @@ class MediaPipelineModuleService : public IMediaPipelineModuleService ::google::protobuf::Closure *done) override; void getPosition(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done) override; + void getDuration(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done) override; void setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, ::google::protobuf::Closure *done) override; + void setReportDecodeErrors(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, + ::google::protobuf::Closure *done) override; + void getQueuedFrames(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, + ::google::protobuf::Closure *done) override; void getImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, diff --git a/media/server/ipc/source/MediaKeysCapabilitiesModuleService.cpp b/media/server/ipc/source/MediaKeysCapabilitiesModuleService.cpp index b33a2bdf4..5be892097 100644 --- a/media/server/ipc/source/MediaKeysCapabilitiesModuleService.cpp +++ b/media/server/ipc/source/MediaKeysCapabilitiesModuleService.cpp @@ -168,4 +168,36 @@ void MediaKeysCapabilitiesModuleService::isServerCertificateSupported( done->Run(); } +void MediaKeysCapabilitiesModuleService::getSupportedRobustnessLevels( + ::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetSupportedRobustnessLevelsRequest *request, + ::firebolt::rialto::GetSupportedRobustnessLevelsResponse *response, ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + auto ipcController = dynamic_cast(controller); + if (!ipcController) + { + RIALTO_SERVER_LOG_ERROR("ipc library provided incompatible controller object"); + controller->SetFailed("ipc library provided incompatible controller object"); + done->Run(); + return; + } + + std::vector robustnessLevels; + bool status = m_cdmService.getSupportedRobustnessLevels(request->key_system(), robustnessLevels); + if (status) + { + for (const auto &level : robustnessLevels) + { + response->add_robustness_levels(level); + } + } + else + { + RIALTO_SERVER_LOG_ERROR("Failed to get supported robustness levels"); + controller->SetFailed("Operation failed"); + } + + done->Run(); +} + } // namespace firebolt::rialto::server::ipc diff --git a/media/server/ipc/source/MediaKeysModuleService.cpp b/media/server/ipc/source/MediaKeysModuleService.cpp index 4cb5f6293..5c6e2edfe 100644 --- a/media/server/ipc/source/MediaKeysModuleService.cpp +++ b/media/server/ipc/source/MediaKeysModuleService.cpp @@ -66,6 +66,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::MediaKeyErrorStatus &errorSta { return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return firebolt::rialto::ProtoMediaKeyErrorStatus::OUTPUT_RESTRICTED; + } } return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } @@ -100,6 +104,22 @@ firebolt::rialto::InitDataType covertInitDataType(firebolt::rialto::GenerateRequ return firebolt::rialto::InitDataType::UNKNOWN; } } + +firebolt::rialto::LimitedDurationLicense +covertLimitedDurationLicense(firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense protoLimitedDurationLicense) +{ + switch (protoLimitedDurationLicense) + { + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED: + return firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED; + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_ENABLED: + return firebolt::rialto::LimitedDurationLicense::ENABLED; + case firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense::GenerateRequestRequest_LimitedDurationLicense_DISABLED: + return firebolt::rialto::LimitedDurationLicense::DISABLED; + default: + return firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED; + } +} } // namespace namespace firebolt::rialto::server::ipc @@ -262,7 +282,7 @@ void MediaKeysModuleService::createKeySession(::google::protobuf::RpcController m_cdmService.createKeySession(request->media_keys_handle(), convertKeySessionType(request->session_type()), std::make_shared(request->media_keys_handle(), ipcController->getClient()), - request->is_ldl(), keySessionId); + keySessionId); if (MediaKeyErrorStatus::OK == status) { response->set_key_session_id(keySessionId); @@ -278,10 +298,11 @@ void MediaKeysModuleService::generateRequest(::google::protobuf::RpcController * { RIALTO_SERVER_LOG_DEBUG("entry:"); - MediaKeyErrorStatus status = m_cdmService.generateRequest(request->media_keys_handle(), request->key_session_id(), - covertInitDataType(request->init_data_type()), - std::vector{request->init_data().begin(), - request->init_data().end()}); + MediaKeyErrorStatus status = + m_cdmService.generateRequest(request->media_keys_handle(), request->key_session_id(), + covertInitDataType(request->init_data_type()), + std::vector{request->init_data().begin(), request->init_data().end()}, + covertLimitedDurationLicense(request->ldl_state())); response->set_error_status(convertMediaKeyErrorStatus(status)); done->Run(); } diff --git a/media/server/ipc/source/MediaPipelineClient.cpp b/media/server/ipc/source/MediaPipelineClient.cpp index 655fa8f19..a020d8ac6 100644 --- a/media/server/ipc/source/MediaPipelineClient.cpp +++ b/media/server/ipc/source/MediaPipelineClient.cpp @@ -126,6 +126,10 @@ firebolt::rialto::PlaybackErrorEvent_PlaybackError convertPlaybackError(const fi { return firebolt::rialto::PlaybackErrorEvent_PlaybackError_DECRYPTION; } + case firebolt::rialto::PlaybackError::OUTPUT_PROTECTION: + { + return firebolt::rialto::PlaybackErrorEvent_PlaybackError_OUTPUT_PROTECTION; + } } return firebolt::rialto::PlaybackErrorEvent_PlaybackError_UNKNOWN; } @@ -240,6 +244,17 @@ void MediaPipelineClient::notifyBufferUnderflow(int32_t sourceId) m_ipcClient->sendEvent(event); } +void MediaPipelineClient::notifyFirstFrameReceived(int32_t sourceId) +{ + RIALTO_SERVER_LOG_DEBUG("Sending FirstFrameReceivedEvent..."); + + auto event = std::make_shared(); + event->set_session_id(m_sessionId); + event->set_source_id(sourceId); + + m_ipcClient->sendEvent(event); +} + void MediaPipelineClient::notifyPlaybackError(int32_t sourceId, PlaybackError error) { RIALTO_SERVER_LOG_DEBUG("Sending notifyPlaybackError..."); diff --git a/media/server/ipc/source/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 77c1387aa..1b2d29a12 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace { @@ -100,6 +101,10 @@ convertMediaSourceStatus(const firebolt::rialto::HaveDataRequest_MediaSourceStat { return firebolt::rialto::MediaSourceStatus::NO_AVAILABLE_SAMPLES; } + case firebolt::rialto::HaveDataRequest_MediaSourceStatus_NO_SPACE_FOR_SAMPLES: + { + return firebolt::rialto::MediaSourceStatus::NO_SPACE_FOR_SAMPLES; + } } return firebolt::rialto::MediaSourceStatus::ERROR; } @@ -391,7 +396,7 @@ void MediaPipelineModuleService::load(::google::protobuf::RpcController *control { RIALTO_SERVER_LOG_DEBUG("entry:"); if (!m_mediaPipelineService.load(request->session_id(), convertMediaType(request->type()), request->mime_type(), - request->url())) + request->url(), request->is_live())) { RIALTO_SERVER_LOG_ERROR("Load failed"); controller->SetFailed("Operation failed"); @@ -424,10 +429,10 @@ void MediaPipelineModuleService::attachSource(::google::protobuf::RpcController std::shared_ptr codecData{}; if (request->has_codec_data()) { - auto codecDataProto = request->codec_data(); + const auto &kCodecDataProto = request->codec_data(); codecData = std::make_shared(); - codecData->data = std::vector(codecDataProto.data().begin(), codecDataProto.data().end()); - codecData->type = convertCodecDataType(codecDataProto.type()); + codecData->data = std::vector(kCodecDataProto.data().begin(), kCodecDataProto.data().end()); + codecData->type = convertCodecDataType(kCodecDataProto.type()); } std::unique_ptr mediaSource; firebolt::rialto::SourceConfigType configType = convertConfigType(request->config_type()); @@ -471,8 +476,8 @@ void MediaPipelineModuleService::attachSource(::google::protobuf::RpcController { framed = kConfig.framed(); } - AudioConfig audioConfig{numberofchannels, sampleRate, codecSpecificConfig, format, - layout, channelMask, streamHeaders, framed}; + AudioConfig audioConfig{numberofchannels, sampleRate, std::move(codecSpecificConfig), format, + layout, channelMask, std::move(streamHeaders), framed}; mediaSource = std::make_unique(request->mime_type(), hasDrm, audioConfig, @@ -566,11 +571,13 @@ void MediaPipelineModuleService::play(::google::protobuf::RpcController *control ::firebolt::rialto::PlayResponse *response, ::google::protobuf::Closure *done) { RIALTO_SERVER_LOG_DEBUG("entry:"); - if (!m_mediaPipelineService.play(request->session_id())) + bool async{false}; + if (!m_mediaPipelineService.play(request->session_id(), async)) { RIALTO_SERVER_LOG_ERROR("Play failed"); controller->SetFailed("Operation failed"); } + response->set_async(async); done->Run(); } @@ -662,6 +669,25 @@ void MediaPipelineModuleService::getPosition(::google::protobuf::RpcController * done->Run(); } +void MediaPipelineModuleService::getDuration(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + int64_t duration{}; + if (!m_mediaPipelineService.getDuration(request->session_id(), duration)) + { + RIALTO_SERVER_LOG_ERROR("Get duration failed"); + controller->SetFailed("Operation failed"); + } + else + { + response->set_duration(duration); + } + done->Run(); +} + void MediaPipelineModuleService::setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, @@ -677,6 +703,40 @@ void MediaPipelineModuleService::setImmediateOutput(::google::protobuf::RpcContr done->Run(); } +void MediaPipelineModuleService::setReportDecodeErrors(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + if (!m_mediaPipelineService.setReportDecodeErrors(request->session_id(), request->source_id(), + request->report_decode_errors())) + { + RIALTO_SERVER_LOG_ERROR("Set Report Decode Error failed"); + controller->SetFailed("Operation failed"); + } + done->Run(); +} + +void MediaPipelineModuleService::getQueuedFrames(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + uint32_t queuedFramesNumber; + if (!m_mediaPipelineService.getQueuedFrames(request->session_id(), request->source_id(), queuedFramesNumber)) + { + RIALTO_SERVER_LOG_ERROR("Get queued frames failed"); + controller->SetFailed("Operation failed"); + } + else + { + response->set_queued_frames(queuedFramesNumber); + } + done->Run(); +} + void MediaPipelineModuleService::getImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, diff --git a/media/server/main/CMakeLists.txt b/media/server/main/CMakeLists.txt index ad9d9bd2e..6f203631b 100644 --- a/media/server/main/CMakeLists.txt +++ b/media/server/main/CMakeLists.txt @@ -48,6 +48,7 @@ add_library( source/HeartbeatProcedure.cpp source/TextTrackAccessor.cpp source/TextTrackSession.cpp + source/NeedDataDelayCalculator.cpp ) target_include_directories( diff --git a/media/server/main/include/ActiveRequests.h b/media/server/main/include/ActiveRequests.h index b90ef73a7..73fd9e28e 100644 --- a/media/server/main/include/ActiveRequests.h +++ b/media/server/main/include/ActiveRequests.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace firebolt::rialto::server { @@ -33,8 +34,8 @@ class ActiveRequests : public IActiveRequests class ActiveRequestsData { public: - ActiveRequestsData(MediaSourceType type, std::uint32_t maxMediaBytes) - : m_type(type), m_bytesWritten(0), m_maxMediaBytes(maxMediaBytes) + ActiveRequestsData(MediaSourceType type, std::uint32_t maxMediaBytes, std::uint32_t maxFrames) + : m_type(type), m_bytesWritten(0), m_maxMediaBytes(maxMediaBytes), m_maxFrames(maxFrames) { } ~ActiveRequestsData(); @@ -47,13 +48,16 @@ class ActiveRequests : public IActiveRequests AddSegmentStatus addSegment(const std::unique_ptr &segment); MediaSourceType getType() const { return m_type; } + std::uint32_t getMaxFrames() const { return m_maxFrames; } const IMediaPipeline::MediaSegmentVector &getSegments() const { return m_segments; } private: MediaSourceType m_type; std::uint32_t m_bytesWritten; std::uint32_t m_maxMediaBytes; + std::uint32_t m_maxFrames; IMediaPipeline::MediaSegmentVector m_segments; + std::vector> m_segmentBuffers; }; ActiveRequests(); @@ -63,8 +67,10 @@ class ActiveRequests : public IActiveRequests ActiveRequests &operator=(const ActiveRequests &) = delete; ActiveRequests &operator=(ActiveRequests &&) = delete; - std::uint32_t insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes) override; + std::uint32_t insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes, + std::uint32_t maxFrames) override; MediaSourceType getType(std::uint32_t requestId) const override; + std::uint32_t getMaxFrames(std::uint32_t requestId) const override; void erase(std::uint32_t requestId) override; void erase(const MediaSourceType &mediaSourceType) override; void clear() override; diff --git a/media/server/main/include/DataReaderFactory.h b/media/server/main/include/DataReaderFactory.h index 7bdd75c8d..8d5b65d15 100644 --- a/media/server/main/include/DataReaderFactory.h +++ b/media/server/main/include/DataReaderFactory.h @@ -31,7 +31,8 @@ class DataReaderFactory : public IDataReaderFactory DataReaderFactory() = default; ~DataReaderFactory() override = default; std::shared_ptr createDataReader(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, - std::uint32_t dataOffset, std::uint32_t numFrames) const override; + std::uint32_t dataOffset, std::uint32_t numFrames, + bool isBufferFull) const override; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/include/DataReaderV1.h b/media/server/main/include/DataReaderV1.h index ef9f02ca1..14f4b188f 100644 --- a/media/server/main/include/DataReaderV1.h +++ b/media/server/main/include/DataReaderV1.h @@ -56,10 +56,11 @@ class DataReaderV1 : public IDataReader public: DataReaderV1(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, std::uint32_t metadataOffset, - std::uint32_t numFrames); + std::uint32_t numFrames, bool isBufferFull); ~DataReaderV1() override = default; IMediaPipeline::MediaSegmentVector readData() const override; + bool isBufferFull() const override; private: std::vector readMetadata() const; @@ -69,6 +70,7 @@ class DataReaderV1 : public IDataReader std::uint8_t *m_buffer; std::uint32_t m_metadataOffset; std::uint32_t m_numFrames; + bool m_isBufferFull; template std::unique_ptr createSegment(const MetadataV1 &metadata) const { diff --git a/media/server/main/include/DataReaderV2.h b/media/server/main/include/DataReaderV2.h index 7a76f6599..71f82fb50 100644 --- a/media/server/main/include/DataReaderV2.h +++ b/media/server/main/include/DataReaderV2.h @@ -30,16 +30,18 @@ class DataReaderV2 : public IDataReader { public: DataReaderV2(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, std::uint32_t dataOffset, - std::uint32_t numFrames); + std::uint32_t numFrames, bool isBufferFull); ~DataReaderV2() override = default; IMediaPipeline::MediaSegmentVector readData() const override; + bool isBufferFull() const override; private: MediaSourceType m_mediaSourceType; std::uint8_t *m_buffer; std::uint32_t m_dataOffset; std::uint32_t m_numFrames; + bool m_isBufferFull; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/include/IActiveRequests.h b/media/server/main/include/IActiveRequests.h index 0ffa20081..91c4c82e2 100644 --- a/media/server/main/include/IActiveRequests.h +++ b/media/server/main/include/IActiveRequests.h @@ -38,8 +38,10 @@ class IActiveRequests IActiveRequests &operator=(const IActiveRequests &) = delete; IActiveRequests &operator=(IActiveRequests &&) = delete; - virtual std::uint32_t insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes) = 0; + virtual std::uint32_t insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes, + std::uint32_t maxFrames) = 0; virtual MediaSourceType getType(std::uint32_t requestId) const = 0; + virtual std::uint32_t getMaxFrames(std::uint32_t requestId) const = 0; virtual void erase(std::uint32_t requestId) = 0; virtual void erase(const MediaSourceType &mediaSourceType) = 0; virtual void clear() = 0; diff --git a/media/server/main/include/IDataReaderFactory.h b/media/server/main/include/IDataReaderFactory.h index 5980d747e..977672a4b 100644 --- a/media/server/main/include/IDataReaderFactory.h +++ b/media/server/main/include/IDataReaderFactory.h @@ -38,7 +38,8 @@ class IDataReaderFactory virtual ~IDataReaderFactory() = default; virtual std::shared_ptr createDataReader(const MediaSourceType &mediaSourceType, std::uint8_t *data, - std::uint32_t dataOffset, std::uint32_t numFrames) const = 0; + std::uint32_t dataOffset, std::uint32_t numFrames, + bool isBufferFull) const = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/include/IMediaKeySession.h b/media/server/main/include/IMediaKeySession.h index bddb27155..5bb552cc1 100644 --- a/media/server/main/include/IMediaKeySession.h +++ b/media/server/main/include/IMediaKeySession.h @@ -55,14 +55,13 @@ class IMediaKeySessionFactory * @param[in] ocdmSystem : The ocdm system object to create the session on. * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks. - * @param[in] isLDL : Is this an LDL. * * @retval the new media keys instance or null on error. */ virtual std::unique_ptr createMediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL) const = 0; + std::weak_ptr client) const = 0; }; /** @@ -84,11 +83,12 @@ class IMediaKeySession * * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter. * * @retval an error status. */ - virtual MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData) = 0; - + virtual MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) = 0; /** * @brief Loads the existing key session. * @@ -179,13 +179,6 @@ class IMediaKeySession * @retval an error status. */ virtual MediaKeyErrorStatus selectKeyId(const std::vector &keyId) = 0; - - /** - * @brief Checks, if key system of media key session is Netflix playready. - * - * @retval true if key system is Netflix playready - */ - virtual bool isNetflixPlayreadyKeySystem() const = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/include/MainThread.h b/media/server/main/include/MainThread.h index b7fef969a..afb55ec62 100644 --- a/media/server/main/include/MainThread.h +++ b/media/server/main/include/MainThread.h @@ -68,9 +68,9 @@ class MainThread : public IMainThread int32_t registerClient() override; void unregisterClient(uint32_t clientId) override; - void enqueueTask(uint32_t clientId, Task task) override; - void enqueueTaskAndWait(uint32_t clientId, Task task) override; - void enqueuePriorityTaskAndWait(uint32_t clientId, Task task) override; + void enqueueTask(uint32_t clientId, const Task &task) override; + void enqueueTaskAndWait(uint32_t clientId, const Task &task) override; + void enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) override; private: /** @@ -78,6 +78,7 @@ class MainThread : public IMainThread */ struct TaskInfo { + bool done{false}; /**< A flag indicating whether the task has completed. */ uint32_t clientId; /**< The id of the client creating the task. */ Task task; /**< The task to execute. */ std::unique_ptr mutex; /**< Mutex for the task condition variable. */ diff --git a/media/server/main/include/MediaKeySession.h b/media/server/main/include/MediaKeySession.h index 0a05fee9c..3d601f5ca 100644 --- a/media/server/main/include/MediaKeySession.h +++ b/media/server/main/include/MediaKeySession.h @@ -43,8 +43,7 @@ class MediaKeySessionFactory : public IMediaKeySessionFactory std::unique_ptr createMediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, - bool isLDL) const override; + std::weak_ptr client) const override; }; /** @@ -61,20 +60,19 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe * @param[in] ocdmSystem : The ocdm system object to create the session on. * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks. - * @param[in] isLDL : Is this an LDL. * @param[in] mainThreadFactory : The main thread factory. */ MediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL, - const std::shared_ptr &mainThreadFactory); + std::weak_ptr client, const std::shared_ptr &mainThreadFactory); /** * @brief Virtual destructor. */ virtual ~MediaKeySession(); - MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData) override; + MediaKeyErrorStatus generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession() override; @@ -96,8 +94,6 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe MediaKeyErrorStatus selectKeyId(const std::vector &keyId) override; - bool isNetflixPlayreadyKeySystem() const override; - void onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength) override; void onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength) override; @@ -137,11 +133,6 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe */ std::shared_ptr m_mainThread; - /** - * @brief Is the session LDL. - */ - const bool m_kIsLDL; - /** * @brief Is the ocdm session constructed. */ @@ -182,17 +173,34 @@ class MediaKeySession : public IMediaKeySession, public firebolt::rialto::wrappe */ bool m_ocdmError; + /** + * @brief True when a decrypt error has already been logged and no successful decrypt happened since. + */ + bool m_decryptErrorLogged; + /** * @brief Mutex protecting the ocdm error checking. */ std::mutex m_ocdmErrorMutex; + /** + * @brief Drm header to be set once the session is constructed + */ + std::vector m_queuedDrmHeader; + + /** + * @brief Flag used to check if extended interface is used + */ + bool m_extendedInterfaceInUse{false}; + /** * @brief Posts a getChallenge task onto the main thread. * + * @param[in] ldlState : The Limited Duration License state. + * * The challenge data is retrieved from ocdm and notified on a onLicenseRequest. */ - void getChallenge(); + void getChallenge(const LimitedDurationLicense &ldlState); /** * @brief Initalises the ocdm error data which checks for onError callbacks. diff --git a/media/server/main/include/MediaKeysCapabilities.h b/media/server/main/include/MediaKeysCapabilities.h index 524d1d664..ddffeb20a 100644 --- a/media/server/main/include/MediaKeysCapabilities.h +++ b/media/server/main/include/MediaKeysCapabilities.h @@ -57,8 +57,8 @@ class MediaKeysCapabilities : public IMediaKeysCapabilities * @param[in] ocdmFactory : The ocdm factory. * @param[in] ocdmSystemFactory : The ocdmSystem factory. */ - MediaKeysCapabilities(std::shared_ptr ocdmFactory, - std::shared_ptr ocdmSystemFactory); + MediaKeysCapabilities(const std::shared_ptr &ocdmFactory, + const std::shared_ptr &ocdmSystemFactory); /** * @brief Virtual destructor. @@ -73,6 +73,8 @@ class MediaKeysCapabilities : public IMediaKeysCapabilities bool isServerCertificateSupported(const std::string &keySystem) override; + bool getSupportedRobustnessLevels(const std::string &keySystem, std::vector &robustnessLevels) override; + private: /** * @brief The IOcdm instance. diff --git a/media/server/main/include/MediaKeysServerInternal.h b/media/server/main/include/MediaKeysServerInternal.h index 6e934e004..12924decd 100644 --- a/media/server/main/include/MediaKeysServerInternal.h +++ b/media/server/main/include/MediaKeysServerInternal.h @@ -64,8 +64,8 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * */ MediaKeysServerInternal(const std::string &keySystem, const std::shared_ptr &mainThreadFactory, - std::shared_ptr ocdmSystemFactory, - std::shared_ptr mediaKeySessionFactory); + const std::shared_ptr &ocdmSystemFactory, + const std::shared_ptr &mediaKeySessionFactory); /** * @brief Virtual destructor. @@ -76,11 +76,12 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal bool containsKey(int32_t keySessionId, const std::vector &keyId) override; - MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, bool isLDL, + MediaKeyErrorStatus createKeySession(KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int32_t keySessionId) override; @@ -114,8 +115,6 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal MediaKeyErrorStatus getMetricSystemData(std::vector &buffer) override; - bool isNetflixPlayreadyKeySystem() const override; - void ping(std::unique_ptr &&heartbeatHandler) override; private: @@ -154,13 +153,12 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * * @param[in] sessionType : The session type. * @param[in] client : Client object for callbacks - * @param[in] isLDL : Is this an LDL * @param[out] keySessionId: The key session id * * @retval an error status. */ MediaKeyErrorStatus createKeySessionInternal(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId); + int32_t &keySessionId); /** * @brief Generate internally, only to be called on the main thread. @@ -168,11 +166,13 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal * @param[in] keySessionId : The key session id for the session. * @param[in] initDataType : The init data type. * @param[in] initData : The init data. + * @param[in] ldlState : The Limited Duration License state. Most of key systems do not need this parameter. * * @retval an error status. */ MediaKeyErrorStatus generateRequestInternal(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData); + const std::vector &initData, + const LimitedDurationLicense &ldlState); /** * @brief Load internally, only to be called on the main thread. diff --git a/media/server/main/include/MediaPipelineCapabilities.h b/media/server/main/include/MediaPipelineCapabilities.h index be5a34451..da5ec541b 100644 --- a/media/server/main/include/MediaPipelineCapabilities.h +++ b/media/server/main/include/MediaPipelineCapabilities.h @@ -55,7 +55,7 @@ class MediaPipelineCapabilities : public IMediaPipelineCapabilities * * @param[in] gstCapabilitiesFactory : The gstreamer capabilities factory. */ - explicit MediaPipelineCapabilities(std::shared_ptr gstCapabilitiesFactory); + explicit MediaPipelineCapabilities(const std::shared_ptr &gstCapabilitiesFactory); /** * @brief Virtual destructor. diff --git a/media/server/main/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index e546ee795..7b7db524e 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -26,6 +26,7 @@ #include "IMainThread.h" #include "IMediaPipelineServerInternal.h" #include "ITimer.h" +#include "NeedDataDelayCalculator.h" #include #include #include @@ -78,11 +79,12 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * @param[in] activeRequests : The active requests * @param[in] decryptionService : The decryption service */ - MediaPipelineServerInternal(std::shared_ptr client, const VideoRequirements &videoRequirements, + MediaPipelineServerInternal(const std::shared_ptr &client, + const VideoRequirements &videoRequirements, const std::shared_ptr &gstPlayerFactory, int sessionId, const std::shared_ptr &shmBuffer, const std::shared_ptr &mainThreadFactory, - std::shared_ptr timerFactory, + const std::shared_ptr &timerFactory, std::unique_ptr &&dataReaderFactory, std::unique_ptr &&activeRequests, IDecryptionService &decryptionService); @@ -91,7 +93,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public */ virtual ~MediaPipelineServerInternal(); - bool load(MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(const std::unique_ptr &source) override; @@ -99,7 +101,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -113,6 +115,10 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -170,6 +176,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + AddSegmentStatus addSegment(uint32_t needDataRequestId, const std::unique_ptr &mediaSegment) override; std::weak_ptr getClient() override; @@ -178,6 +186,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool notifyNeedMediaData(MediaSourceType mediaSourceType) override; + bool notifyNeedMediaDataWithDelay(MediaSourceType mediaSourceType) override; + void notifyPosition(std::int64_t position) override; void notifyNetworkState(NetworkState state) override; @@ -190,6 +200,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public void notifyBufferUnderflow(MediaSourceType mediaSourceType) override; + void notifyFirstFrameReceived(MediaSourceType mediaSourceType) override; + void notifyPlaybackError(MediaSourceType mediaSourceType, PlaybackError error) override; void notifySourceFlushed(MediaSourceType mediaSourceType) override; @@ -303,9 +315,9 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public std::shared_mutex m_getPropertyMutex; /** - * @brief Flag to check, if setting volume is in progress + * @brief Object to calculate the delay for scheduling NeedMediaData when no segments were received in haveData() call */ - std::atomic_bool m_isSetVolumeInProgress{false}; + NeedDataDelayCalculator m_needDataDelayCalculator; /** * @brief Load internally, only to be called on the main thread. @@ -313,10 +325,11 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * @param[in] type : The media type. * @param[in] mimeType : The MIME type. * @param[in] url : The URL. + * @param[in] isLive : Indicates if the media is live. * * @retval true on success. */ - bool loadInternal(MediaType type, const std::string &mimeType, const std::string &url); + bool loadInternal(MediaType type, const std::string &mimeType, const std::string &url, bool isLive); /** * @brief Attach source internally, only to be called on the main thread. @@ -346,9 +359,11 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public /** * @brief Play internally, only to be called on the main thread. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - bool playInternal(); + bool playInternal(bool &async); /** * @brief Pause internally, only to be called on the main thread. @@ -394,6 +409,30 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public */ bool setImmediateOutputInternal(int32_t sourceId, bool immediateOutput); + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : The desired Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + bool setReportDecodeErrorsInternal(int32_t sourceId, bool reportDecodeErrors); + + /** + * @brief Gets the queued frames for this source. + * + * This method is asynchronous + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] queuedFrames : Number of queued frames + * + * @retval true on success. + */ + bool getQueuedFramesInternal(int32_t sourceId, uint32_t &queuedFrames); + /** * @brief Gets the "Immediate Output" property for this source. * @@ -476,6 +515,13 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public */ bool notifyNeedMediaDataInternal(MediaSourceType mediaSourceType); + /** + * @brief Notify need media data with delay internally, only to be called on the main thread. + * + * @param[in] mediaSourceType : The media source type. + */ + bool notifyNeedMediaDataWithDelayInternal(MediaSourceType mediaSourceType); + /** * @brief Schedules resending of NeedMediaData after a short delay. Used when no segments were received in the * haveData() call to prevent a storm of needData()/haveData() calls, only to be called on the main thread. @@ -719,7 +765,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * * @retval NeedMediaData timeout */ - std::chrono::milliseconds getNeedMediaDataTimeout(MediaSourceType mediaSourceType) const; + std::chrono::milliseconds getNeedMediaDataTimeout(MediaSourceType mediaSourceType); }; }; // namespace firebolt::rialto::server diff --git a/media/server/main/include/NeedDataDelayCalculator.h b/media/server/main/include/NeedDataDelayCalculator.h new file mode 100644 index 000000000..08f3a7771 --- /dev/null +++ b/media/server/main/include/NeedDataDelayCalculator.h @@ -0,0 +1,46 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_NEED_DATA_DELAY_CALCULATOR_H_ +#define FIREBOLT_RIALTO_SERVER_NEED_DATA_DELAY_CALCULATOR_H_ + +#include "MediaCommon.h" +#include +#include + +namespace firebolt::rialto::server +{ +class NeedDataDelayCalculator +{ +public: + NeedDataDelayCalculator() = default; + ~NeedDataDelayCalculator() = default; + + std::chrono::milliseconds getNeedMediaDataDelay(MediaSourceType mediaSourceType); + void increaseNeedMediaDataDelay(MediaSourceType mediaSourceType); + void decreaseNeedMediaDataDelay(MediaSourceType mediaSourceType); + void resetMediaDataDelay(MediaSourceType mediaSourceType); + void resetMediaDataDelay(); + +private: + std::map m_delays; +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_NEED_DATA_DELAY_CALCULATOR_H_ diff --git a/media/server/main/include/ShmUtils.h b/media/server/main/include/ShmUtils.h index 6ebf425a6..ede9aa5a4 100644 --- a/media/server/main/include/ShmUtils.h +++ b/media/server/main/include/ShmUtils.h @@ -25,6 +25,7 @@ namespace firebolt::rialto::server { +constexpr std::uint32_t kPrerollNumFrames{3}; constexpr std::uint32_t kMaxFrames{24}; constexpr std::uint32_t getMaxMetadataBytes() { diff --git a/media/server/main/interface/IDataReader.h b/media/server/main/interface/IDataReader.h index 0388f5db6..2bc3f7572 100644 --- a/media/server/main/interface/IDataReader.h +++ b/media/server/main/interface/IDataReader.h @@ -29,6 +29,7 @@ class IDataReader public: virtual ~IDataReader() = default; virtual IMediaPipeline::MediaSegmentVector readData() const = 0; + virtual bool isBufferFull() const = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/interface/IDecryptionService.h b/media/server/main/interface/IDecryptionService.h index 322b7aad4..3d52056f5 100644 --- a/media/server/main/interface/IDecryptionService.h +++ b/media/server/main/interface/IDecryptionService.h @@ -32,7 +32,7 @@ class IDecryptionService public: virtual ~IDecryptionService() = default; virtual MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) = 0; - virtual bool isNetflixPlayreadyKeySystem(int32_t keySessionId) = 0; + virtual bool isExtendedInterfaceUsed(int32_t keySessionId) = 0; virtual MediaKeyErrorStatus selectKeyId(int32_t keySessionId, const std::vector &keyId) = 0; virtual void incrementSessionIdUsageCounter(int32_t keySessionId) = 0; virtual void decrementSessionIdUsageCounter(int32_t keySessionId) = 0; diff --git a/media/server/main/interface/IMainThread.h b/media/server/main/interface/IMainThread.h index 6a4ac3490..3a920a67a 100644 --- a/media/server/main/interface/IMainThread.h +++ b/media/server/main/interface/IMainThread.h @@ -20,7 +20,6 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_MAIN_THREAD_H_ #define FIREBOLT_RIALTO_SERVER_I_MAIN_THREAD_H_ -#include "IMainThread.h" #include #include #include @@ -94,7 +93,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueueTask(uint32_t clientId, Task task) = 0; + virtual void enqueueTask(uint32_t clientId, const Task &task) = 0; /** * @brief Enqueue a task on the main thread and wait for it to finish before returning. @@ -102,7 +101,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueueTaskAndWait(uint32_t clientId, Task task) = 0; + virtual void enqueueTaskAndWait(uint32_t clientId, const Task &task) = 0; /** * @brief Enqueue a priority task on the main thread and wait for it to finish before returning. @@ -110,7 +109,7 @@ class IMainThread * @param[in] clientId : The id of the registered client. * @param[in] task : Task to queue. */ - virtual void enqueuePriorityTaskAndWait(uint32_t clientId, Task task) = 0; + virtual void enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/main/interface/IMediaKeysServerInternal.h b/media/server/main/interface/IMediaKeysServerInternal.h index 45069b19a..5de4ffc60 100644 --- a/media/server/main/interface/IMediaKeysServerInternal.h +++ b/media/server/main/interface/IMediaKeysServerInternal.h @@ -89,13 +89,6 @@ class IMediaKeysServerInternal : public IMediaKeys */ virtual MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) = 0; - /** - * @brief Checks, if key system of media key session is Netflix Playready. - * - * @retval true if key system is Playready - */ - virtual bool isNetflixPlayreadyKeySystem() const = 0; - /** * @brief Checks, if MediaKeys main thread is not deadlocked * diff --git a/media/server/main/source/ActiveRequests.cpp b/media/server/main/source/ActiveRequests.cpp index 3431fc672..711eb348f 100644 --- a/media/server/main/source/ActiveRequests.cpp +++ b/media/server/main/source/ActiveRequests.cpp @@ -18,18 +18,14 @@ */ #include "ActiveRequests.h" +#include "RialtoServerLogging.h" #include +#include #include namespace firebolt::rialto::server { -ActiveRequests::ActiveRequestsData::~ActiveRequestsData() -{ - for (std::unique_ptr &segment : m_segments) - { - delete[] segment->getData(); - } -} +ActiveRequests::ActiveRequestsData::~ActiveRequestsData() {} AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::unique_ptr &segment) { @@ -38,10 +34,10 @@ AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::uniqu std::unique_ptr copiedSegment = segment->copy(); - uint8_t *data = new uint8_t[segment->getDataLength()]; - std::memcpy(data, segment->getData(), segment->getDataLength()); + m_segmentBuffers.emplace_back(segment->getDataLength()); + std::memcpy(m_segmentBuffers.back().data(), segment->getData(), segment->getDataLength()); - copiedSegment->setData(segment->getDataLength(), data); + copiedSegment->setData(m_segmentBuffers.back().size(), m_segmentBuffers.back().data()); m_segments.push_back(std::move(copiedSegment)); m_bytesWritten += segment->getDataLength(); @@ -50,10 +46,28 @@ AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::uniqu ActiveRequests::ActiveRequests() : m_currentId{0} {} -std::uint32_t ActiveRequests::insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes) +std::uint32_t ActiveRequests::insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes, + std::uint32_t maxFrames) { std::unique_lock lock{m_mutex}; - m_requestMap.insert(std::make_pair(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes))); + + if (m_currentId == std::numeric_limits::max()) + { + m_currentId = 1; + } + + auto [it, inserted] = + m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes, maxFrames)); + if (!inserted) + { + do + { + ++m_currentId; + } while (m_requestMap.find(m_currentId) != m_requestMap.end()); + + m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes, maxFrames)); + } + return m_currentId++; } @@ -68,6 +82,18 @@ MediaSourceType ActiveRequests::getType(std::uint32_t requestId) const return MediaSourceType::UNKNOWN; } +std::uint32_t ActiveRequests::getMaxFrames(std::uint32_t requestId) const +{ + constexpr std::uint32_t kDefaultMaxFrames{24}; + std::unique_lock lock{m_mutex}; + auto requestIter{m_requestMap.find(requestId)}; + if (requestIter != m_requestMap.end()) + { + return requestIter->second.getMaxFrames(); + } + return kDefaultMaxFrames; +} + void ActiveRequests::erase(std::uint32_t requestId) { std::unique_lock lock{m_mutex}; @@ -104,6 +130,7 @@ AddSegmentStatus ActiveRequests::addSegment(std::uint32_t requestId, return AddSegmentStatus::ERROR; } + std::unique_lock lock{m_mutex}; auto requestIter{m_requestMap.find(requestId)}; if (requestIter != m_requestMap.end()) { @@ -115,6 +142,7 @@ AddSegmentStatus ActiveRequests::addSegment(std::uint32_t requestId, const IMediaPipeline::MediaSegmentVector &ActiveRequests::getSegments(std::uint32_t requestId) const { + std::unique_lock lock{m_mutex}; auto requestIter{m_requestMap.find(requestId)}; if (requestIter != m_requestMap.end()) { diff --git a/media/server/main/source/DataReaderFactory.cpp b/media/server/main/source/DataReaderFactory.cpp index 4be9bde8b..9b975d7a2 100644 --- a/media/server/main/source/DataReaderFactory.cpp +++ b/media/server/main/source/DataReaderFactory.cpp @@ -36,7 +36,7 @@ namespace firebolt::rialto::server { std::shared_ptr DataReaderFactory::createDataReader(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, std::uint32_t dataOffset, - std::uint32_t numFrames) const + std::uint32_t numFrames, bool isBufferFull) const { // Version is always first 4 bytes of data std::uint8_t *metadata = buffer + dataOffset; @@ -44,12 +44,13 @@ std::shared_ptr DataReaderFactory::createDataReader(const MediaSour if (1 == version) { std::uint32_t metadataOffsetWithoutVersion = dataOffset + common::VERSION_SIZE_BYTES; - return std::make_shared(mediaSourceType, buffer, metadataOffsetWithoutVersion, numFrames); + return std::make_shared(mediaSourceType, buffer, metadataOffsetWithoutVersion, numFrames, + isBufferFull); } if (2 == version) { std::uint32_t v2DataOffset = dataOffset + getMaxMetadataBytes(); - return std::make_shared(mediaSourceType, buffer, v2DataOffset, numFrames); + return std::make_shared(mediaSourceType, buffer, v2DataOffset, numFrames, isBufferFull); } return nullptr; } diff --git a/media/server/main/source/DataReaderV1.cpp b/media/server/main/source/DataReaderV1.cpp index ef8e64328..2df1f5b3d 100644 --- a/media/server/main/source/DataReaderV1.cpp +++ b/media/server/main/source/DataReaderV1.cpp @@ -25,8 +25,9 @@ namespace firebolt::rialto::server { DataReaderV1::DataReaderV1(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, std::uint32_t metadataOffset, - std::uint32_t numFrames) - : m_mediaSourceType{mediaSourceType}, m_buffer{buffer}, m_metadataOffset{metadataOffset}, m_numFrames{numFrames} + std::uint32_t numFrames, bool isBufferFull) + : m_mediaSourceType{mediaSourceType}, m_buffer{buffer}, m_metadataOffset{metadataOffset}, m_numFrames{numFrames}, + m_isBufferFull{isBufferFull} { RIALTO_SERVER_LOG_DEBUG("Detected Metadata in Version 1. Media source type: %s", common::convertMediaSourceType(m_mediaSourceType)); @@ -53,6 +54,11 @@ IMediaPipeline::MediaSegmentVector DataReaderV1::readData() const return mediaSegments; } +bool DataReaderV1::isBufferFull() const +{ + return m_isBufferFull; +} + std::vector DataReaderV1::readMetadata() const { std::vector result; diff --git a/media/server/main/source/DataReaderV2.cpp b/media/server/main/source/DataReaderV2.cpp index 5659045f0..78a7585ce 100644 --- a/media/server/main/source/DataReaderV2.cpp +++ b/media/server/main/source/DataReaderV2.cpp @@ -203,8 +203,9 @@ createSegment(const firebolt::rialto::MediaSegmentMetadata &metadata, const fire namespace firebolt::rialto::server { DataReaderV2::DataReaderV2(const MediaSourceType &mediaSourceType, std::uint8_t *buffer, std::uint32_t dataOffset, - std::uint32_t numFrames) - : m_mediaSourceType{mediaSourceType}, m_buffer{buffer}, m_dataOffset{dataOffset}, m_numFrames{numFrames} + std::uint32_t numFrames, bool isBufferFull) + : m_mediaSourceType{mediaSourceType}, m_buffer{buffer}, m_dataOffset{dataOffset}, m_numFrames{numFrames}, + m_isBufferFull{isBufferFull} { RIALTO_SERVER_LOG_DEBUG("Detected Metadata in Version 2. Media source type: %s", common::convertMediaSourceType(m_mediaSourceType)); @@ -237,4 +238,9 @@ IMediaPipeline::MediaSegmentVector DataReaderV2::readData() const } return mediaSegments; } + +bool DataReaderV2::isBufferFull() const +{ + return m_isBufferFull; +} } // namespace firebolt::rialto::server diff --git a/media/server/main/source/MainThread.cpp b/media/server/main/source/MainThread.cpp index 30c2661d0..8b51f30eb 100644 --- a/media/server/main/source/MainThread.cpp +++ b/media/server/main/source/MainThread.cpp @@ -99,6 +99,7 @@ void MainThread::mainThreadLoop() if (nullptr != kTaskInfo->cv) { std::unique_lock lockTask(*(kTaskInfo->mutex)); + kTaskInfo->done = true; kTaskInfo->cv->notify_one(); } } @@ -111,9 +112,9 @@ const std::shared_ptr MainThread::waitForTask() { m_taskQueueCv.wait(lock, [this] { return !m_taskQueue.empty(); }); } - const std::shared_ptr kTaskInfo = m_taskQueue.front(); + auto taskInfo = std::move(m_taskQueue.front()); m_taskQueue.pop_front(); - return kTaskInfo; + return taskInfo; } int32_t MainThread::registerClient() @@ -136,19 +137,19 @@ void MainThread::unregisterClient(uint32_t clientId) m_registeredClients.erase(clientId); } -void MainThread::enqueueTask(uint32_t clientId, Task task) +void MainThread::enqueueTask(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; newTask->task = task; { std::unique_lock lock(m_taskQueueMutex); - m_taskQueue.push_back(newTask); + m_taskQueue.push_back(std::move(newTask)); } m_taskQueueCv.notify_one(); } -void MainThread::enqueueTaskAndWait(uint32_t clientId, Task task) +void MainThread::enqueueTaskAndWait(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; @@ -164,11 +165,11 @@ void MainThread::enqueueTaskAndWait(uint32_t clientId, Task task) } m_taskQueueCv.notify_one(); - newTask->cv->wait(lockTask); + newTask->cv->wait(lockTask, [&] { return newTask->done; }); } } -void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, Task task) +void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, const Task &task) { std::shared_ptr newTask = std::make_shared(); newTask->clientId = clientId; @@ -184,7 +185,7 @@ void MainThread::enqueuePriorityTaskAndWait(uint32_t clientId, Task task) } m_taskQueueCv.notify_one(); - newTask->cv->wait(lockTask); + newTask->cv->wait(lockTask, [&] { return newTask->done; }); } } } // namespace firebolt::rialto::server diff --git a/media/server/main/source/MediaKeySession.cpp b/media/server/main/source/MediaKeySession.cpp index 414856cd8..61e6edcab 100644 --- a/media/server/main/source/MediaKeySession.cpp +++ b/media/server/main/source/MediaKeySession.cpp @@ -18,6 +18,7 @@ */ #include +#include #include "MediaKeySession.h" #include "MediaKeysCommon.h" @@ -41,15 +42,16 @@ std::shared_ptr IMediaKeySessionFactory::createFactory( return factory; } -std::unique_ptr MediaKeySessionFactory::createMediaKeySession( - const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, - KeySessionType sessionType, std::weak_ptr client, bool isLDL) const +std::unique_ptr +MediaKeySessionFactory::createMediaKeySession(const std::string &keySystem, int32_t keySessionId, + const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, + KeySessionType sessionType, std::weak_ptr client) const { std::unique_ptr mediaKeys; try { mediaKeys = std::make_unique(keySystem, keySessionId, ocdmSystem, sessionType, client, - isLDL, server::IMainThreadFactory::createFactory()); + server::IMainThreadFactory::createFactory()); } catch (const std::exception &e) { @@ -61,11 +63,11 @@ std::unique_ptr MediaKeySessionFactory::createMediaKeySession( MediaKeySession::MediaKeySession(const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL, + std::weak_ptr client, const std::shared_ptr &mainThreadFactory) : m_kKeySystem(keySystem), m_kKeySessionId(keySessionId), m_kSessionType(sessionType), m_mediaKeysClient(client), - m_kIsLDL(isLDL), m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), - m_ongoingOcdmOperation(false), m_ocdmError(false) + m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), m_ongoingOcdmOperation(false), + m_ocdmError(false), m_decryptErrorLogged(false) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -106,19 +108,28 @@ MediaKeySession::~MediaKeySession() m_mainThread->unregisterClient(m_mainThreadClientId); } -MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, const std::vector &initData) +MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); - // Set the request flag for the onLicenseRequest callback - m_licenseRequested = true; + if (LimitedDurationLicense::NOT_SPECIFIED != ldlState) + { + m_extendedInterfaceInUse = true; + } + else + { + // Set the request flag for the onLicenseRequest callback + m_licenseRequested = true; + } + + auto status = MediaKeyErrorStatus::OK; // Only construct session if it hasnt previously been constructed if (!m_isSessionConstructed) { initOcdmErrorChecking(); - MediaKeyErrorStatus status = - m_ocdmSession->constructSession(m_kSessionType, initDataType, &initData[0], initData.size()); + status = m_ocdmSession->constructSession(m_kSessionType, initDataType, &initData[0], initData.size()); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to construct the key session"); @@ -127,11 +138,11 @@ MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, else { m_isSessionConstructed = true; - if (isNetflixPlayreadyKeySystem()) + if (!m_queuedDrmHeader.empty()) { - // Ocdm-playready does not notify onProcessChallenge when complete. - // Fetch the challenge manually. - getChallenge(); + RIALTO_SERVER_LOG_DEBUG("Setting queued drm header after session construction"); + setDrmHeader(m_queuedDrmHeader); + m_queuedDrmHeader.clear(); } } @@ -139,27 +150,33 @@ MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, { status = MediaKeyErrorStatus::FAIL; } + } - return status; + if (m_isSessionConstructed && m_extendedInterfaceInUse) + { + // Ocdm-playready does not notify onProcessChallenge when complete. + // Fetch the challenge manually. + getChallenge(ldlState); } - return MediaKeyErrorStatus::OK; + return status; } -void MediaKeySession::getChallenge() +void MediaKeySession::getChallenge(const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); auto task = [&]() { + const bool kIsLdl{LimitedDurationLicense::ENABLED == ldlState}; uint32_t challengeSize = 0; - MediaKeyErrorStatus status = m_ocdmSession->getChallengeData(m_kIsLDL, nullptr, &challengeSize); + MediaKeyErrorStatus status = m_ocdmSession->getChallengeData(kIsLdl, nullptr, &challengeSize); if (challengeSize == 0) { RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data size, no onLicenseRequest will be generated"); return; } std::vector challenge(challengeSize, 0x00); - status = m_ocdmSession->getChallengeData(m_kIsLDL, &challenge[0], &challengeSize); + status = m_ocdmSession->getChallengeData(kIsLdl, &challenge[0], &challengeSize); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data, no onLicenseRequest will be generated"); @@ -167,6 +184,7 @@ void MediaKeySession::getChallenge() } std::string url; + m_licenseRequested = true; onProcessChallenge(url.c_str(), &challenge[0], challengeSize); }; m_mainThread->enqueueTask(m_mainThreadClientId, task); @@ -195,7 +213,7 @@ MediaKeyErrorStatus MediaKeySession::updateSession(const std::vector &r initOcdmErrorChecking(); MediaKeyErrorStatus status; - if (isNetflixPlayreadyKeySystem()) + if (m_extendedInterfaceInUse) { status = m_ocdmSession->storeLicenseData(&responseData[0], responseData.size()); if (MediaKeyErrorStatus::OK != status) @@ -222,12 +240,35 @@ MediaKeyErrorStatus MediaKeySession::updateSession(const std::vector &r MediaKeyErrorStatus MediaKeySession::decrypt(GstBuffer *encrypted, GstCaps *caps) { + constexpr uint32_t kHdcpOutputProtectionFailure{4427}; + initOcdmErrorChecking(); MediaKeyErrorStatus status = m_ocdmSession->decryptBuffer(encrypted, caps); - if (MediaKeyErrorStatus::OK != status) + if (MediaKeyErrorStatus::OUTPUT_RESTRICTED == status) { - RIALTO_SERVER_LOG_ERROR("Failed to decrypt buffer"); + RIALTO_SERVER_LOG_WARN("Decrypt failed due to HDCP output protection"); + } + else if (MediaKeyErrorStatus::OK != status) + { + uint32_t lastDrmError{0}; + m_ocdmSession->getLastDrmError(lastDrmError); + if (lastDrmError == kHdcpOutputProtectionFailure) + { + RIALTO_SERVER_LOG_WARN("Decrypt failed due to HDCP output protection (DRM error %u)", lastDrmError); + status = MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + + // Log decrypt failures once and re-enable logging only after a successful decrypt. + if ((MediaKeyErrorStatus::OUTPUT_RESTRICTED != status) && !m_decryptErrorLogged) + { + RIALTO_SERVER_LOG_ERROR("Failed to decrypt buffer"); + m_decryptErrorLogged = true; + } + } + else + { + m_decryptErrorLogged = false; } if ((checkForOcdmErrors("decrypt")) && (MediaKeyErrorStatus::OK == status)) @@ -243,7 +284,7 @@ MediaKeyErrorStatus MediaKeySession::closeKeySession() initOcdmErrorChecking(); MediaKeyErrorStatus status; - if (isNetflixPlayreadyKeySystem()) + if (m_extendedInterfaceInUse) { if (MediaKeyErrorStatus::OK != m_ocdmSession->cancelChallengeData()) { @@ -322,6 +363,14 @@ MediaKeyErrorStatus MediaKeySession::setDrmHeader(const std::vector &re { initOcdmErrorChecking(); + if (!m_isSessionConstructed) + { + RIALTO_SERVER_LOG_INFO("Session not yet constructed, queueing drm header to be set after construction"); + m_extendedInterfaceInUse = true; + m_queuedDrmHeader = requestData; + return MediaKeyErrorStatus::OK; + } + MediaKeyErrorStatus status = m_ocdmSession->setDrmHeader(requestData.data(), requestData.size()); if (MediaKeyErrorStatus::OK != status) { @@ -363,6 +412,7 @@ MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector &key initOcdmErrorChecking(); + m_extendedInterfaceInUse = true; MediaKeyErrorStatus status = m_ocdmSession->selectKeyId(keyId.size(), keyId.data()); if (MediaKeyErrorStatus::OK == status) { @@ -378,16 +428,11 @@ MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector &key return status; } -bool MediaKeySession::isNetflixPlayreadyKeySystem() const -{ - return m_kKeySystem.find("netflix") != std::string::npos; -} - void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength) { std::string urlStr = url; std::vector challengeVec = std::vector{challenge, challenge + challengeLength}; - auto task = [&, urlStr, challengeVec]() + auto task = [&, urlStr = std::move(urlStr), challengeVec = std::move(challengeVec)]() { std::shared_ptr client = m_mediaKeysClient.lock(); if (client) @@ -409,7 +454,7 @@ void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challen void MediaKeySession::onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength) { std::vector keyIdVec = std::vector{keyId, keyId + keyIdLength}; - auto task = [&, keyIdVec]() + auto task = [&, keyIdVec = std::move(keyIdVec)]() { std::shared_ptr client = m_mediaKeysClient.lock(); if (client) diff --git a/media/server/main/source/MediaKeysCapabilities.cpp b/media/server/main/source/MediaKeysCapabilities.cpp index f0ad87a81..8a2920c19 100644 --- a/media/server/main/source/MediaKeysCapabilities.cpp +++ b/media/server/main/source/MediaKeysCapabilities.cpp @@ -47,6 +47,8 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &status) return "NOT_SUPPORTED"; case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: return "INVALID_STATE"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; } return "Unknown"; } @@ -91,8 +93,9 @@ std::shared_ptr MediaKeysCapabilitiesFactory::getMediaKe namespace firebolt::rialto::server { -MediaKeysCapabilities::MediaKeysCapabilities(std::shared_ptr ocdmFactory, - std::shared_ptr ocdmSystemFactory) +MediaKeysCapabilities::MediaKeysCapabilities( + const std::shared_ptr &ocdmFactory, + const std::shared_ptr &ocdmSystemFactory) : m_ocdmSystemFactory{ocdmSystemFactory} { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -172,4 +175,18 @@ bool MediaKeysCapabilities::isServerCertificateSupported(const std::string &keyS return ocdmSystem->supportsServerCertificate(); } +bool MediaKeysCapabilities::getSupportedRobustnessLevels(const std::string &keySystem, + std::vector &robustnessLevels) +{ + robustnessLevels.clear(); + std::shared_ptr ocdmSystem = + m_ocdmSystemFactory->createOcdmSystem(keySystem); + if (!ocdmSystem) + { + RIALTO_SERVER_LOG_ERROR("Failed to create the ocdm system object"); + return false; + } + return ocdmSystem->getSupportedRobustnessLevels(robustnessLevels); +} + }; // namespace firebolt::rialto::server diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 7fd1b58bf..128e41e3a 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -38,6 +38,8 @@ const char *mediaKeyErrorStatusToString(const MediaKeyErrorStatus &status) return "BUFFER_TOO_SMALL"; case firebolt::rialto::MediaKeyErrorStatus::NOT_SUPPORTED: return "NOT_SUPPORTED"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; default: return "FAIL"; } @@ -103,8 +105,8 @@ namespace firebolt::rialto::server { MediaKeysServerInternal::MediaKeysServerInternal( const std::string &keySystem, const std::shared_ptr &mainThreadFactory, - std::shared_ptr ocdmSystemFactory, - std::shared_ptr mediaKeySessionFactory) + const std::shared_ptr &ocdmSystemFactory, + const std::shared_ptr &mediaKeySessionFactory) : m_mediaKeySessionFactory(mediaKeySessionFactory), m_kKeySystem(keySystem) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -210,13 +212,13 @@ bool MediaKeysServerInternal::containsKeyInternal(int32_t keySessionId, const st } MediaKeyErrorStatus MediaKeysServerInternal::createKeySession(KeySessionType sessionType, - std::weak_ptr client, bool isLDL, + std::weak_ptr client, int32_t &keySessionId) { RIALTO_SERVER_LOG_DEBUG("entry:"); MediaKeyErrorStatus status; - auto task = [&]() { status = createKeySessionInternal(sessionType, client, isLDL, keySessionId); }; + auto task = [&]() { status = createKeySessionInternal(sessionType, client, keySessionId); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; @@ -224,12 +226,12 @@ MediaKeyErrorStatus MediaKeysServerInternal::createKeySession(KeySessionType ses MediaKeyErrorStatus MediaKeysServerInternal::createKeySessionInternal(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + int32_t &keySessionId) { int32_t keySessionIdTemp = generateSessionId(); std::unique_ptr mediaKeySession = m_mediaKeySessionFactory->createMediaKeySession(m_kKeySystem, keySessionIdTemp, *m_ocdmSystem, sessionType, - client, isLDL); + client); if (!mediaKeySession) { RIALTO_SERVER_LOG_ERROR("Failed to create a new media key session"); @@ -242,19 +244,21 @@ MediaKeyErrorStatus MediaKeysServerInternal::createKeySessionInternal(KeySession } MediaKeyErrorStatus MediaKeysServerInternal::generateRequest(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("entry:"); MediaKeyErrorStatus status; - auto task = [&]() { status = generateRequestInternal(keySessionId, initDataType, initData); }; + auto task = [&]() { status = generateRequestInternal(keySessionId, initDataType, initData, ldlState); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; } MediaKeyErrorStatus MediaKeysServerInternal::generateRequestInternal(int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { auto sessionIter = m_mediaKeySessions.find(keySessionId); if (sessionIter == m_mediaKeySessions.end()) @@ -263,7 +267,7 @@ MediaKeyErrorStatus MediaKeysServerInternal::generateRequestInternal(int32_t key return MediaKeyErrorStatus::BAD_SESSION_ID; } - MediaKeyErrorStatus status = sessionIter->second->generateRequest(initDataType, initData); + MediaKeyErrorStatus status = sessionIter->second->generateRequest(initDataType, initData, ldlState); if (MediaKeyErrorStatus::OK != status) { RIALTO_SERVER_LOG_ERROR("Failed to generate request for the key session %d", keySessionId); @@ -571,10 +575,11 @@ MediaKeyErrorStatus MediaKeysServerInternal::decrypt(int32_t keySessionId, GstBu { RIALTO_SERVER_LOG_DEBUG("entry:"); - MediaKeyErrorStatus status; - auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; + MediaKeyErrorStatus status{MediaKeyErrorStatus::FAIL}; + auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return status; } @@ -590,7 +595,7 @@ MediaKeyErrorStatus MediaKeysServerInternal::decryptInternal(int32_t keySessionI MediaKeyErrorStatus status = sessionIter->second->decrypt(encrypted, caps); if (MediaKeyErrorStatus::OK != status) { - RIALTO_SERVER_LOG_ERROR("Failed to decrypt buffer."); + RIALTO_SERVER_LOG_DEBUG("Failed to decrypt buffer."); return status; } RIALTO_SERVER_LOG_INFO("Successfully decrypted buffer."); @@ -598,12 +603,6 @@ MediaKeyErrorStatus MediaKeysServerInternal::decryptInternal(int32_t keySessionI return status; } -bool MediaKeysServerInternal::isNetflixPlayreadyKeySystem() const -{ - RIALTO_SERVER_LOG_DEBUG("entry:"); - return m_kKeySystem.find("netflix") != std::string::npos; -} - void MediaKeysServerInternal::ping(std::unique_ptr &&heartbeatHandler) { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/main/source/MediaPipelineCapabilities.cpp b/media/server/main/source/MediaPipelineCapabilities.cpp index 67cd7c19a..ffd5ec585 100644 --- a/media/server/main/source/MediaPipelineCapabilities.cpp +++ b/media/server/main/source/MediaPipelineCapabilities.cpp @@ -61,7 +61,7 @@ std::unique_ptr MediaPipelineCapabilitiesFactory::cr namespace firebolt::rialto::server { -MediaPipelineCapabilities::MediaPipelineCapabilities(std::shared_ptr gstCapabilitiesFactory) +MediaPipelineCapabilities::MediaPipelineCapabilities(const std::shared_ptr &gstCapabilitiesFactory) : m_kGstCapabilitiesFactory{gstCapabilitiesFactory} { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index f76396bec..3aa8b4293 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -46,6 +46,8 @@ const char *toString(const firebolt::rialto::MediaSourceStatus &status) return "CODEC_CHANGED"; case firebolt::rialto::MediaSourceStatus::NO_AVAILABLE_SAMPLES: return "NO_AVAILABLE_SAMPLES"; + case firebolt::rialto::MediaSourceStatus::NO_SPACE_FOR_SAMPLES: + return "NO_SPACE_FOR_SAMPLES"; } return "Unknown"; } @@ -129,10 +131,10 @@ std::unique_ptr MediaPipelineServerInterna } MediaPipelineServerInternal::MediaPipelineServerInternal( - std::shared_ptr client, const VideoRequirements &videoRequirements, + const std::shared_ptr &client, const VideoRequirements &videoRequirements, const std::shared_ptr &gstPlayerFactory, int sessionId, const std::shared_ptr &shmBuffer, const std::shared_ptr &mainThreadFactory, - std::shared_ptr timerFactory, std::unique_ptr &&dataReaderFactory, + const std::shared_ptr &timerFactory, std::unique_ptr &&dataReaderFactory, std::unique_ptr &&activeRequests, IDecryptionService &decryptionService) : m_mediaPipelineClient(client), m_kGstPlayerFactory(gstPlayerFactory), m_kVideoRequirements(videoRequirements), m_sessionId{sessionId}, m_shmBuffer{shmBuffer}, m_dataReaderFactory{std::move(dataReaderFactory)}, @@ -192,18 +194,19 @@ MediaPipelineServerInternal::~MediaPipelineServerInternal() m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); } -bool MediaPipelineServerInternal::load(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineServerInternal::load(MediaType type, const std::string &mimeType, const std::string &url, bool isLive) { RIALTO_SERVER_LOG_DEBUG("entry:"); bool result; - auto task = [&]() { result = loadInternal(type, mimeType, url); }; + auto task = [&]() { result = loadInternal(type, mimeType, url, isLive); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return result; } -bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string &mimeType, const std::string &url, + bool isLive) { std::unique_lock lock{m_getPropertyMutex}; /* If gstreamer player already created, destroy the old one first */ @@ -214,7 +217,7 @@ bool MediaPipelineServerInternal::loadInternal(MediaType type, const std::string m_gstPlayer = m_kGstPlayerFactory - ->createGstGenericPlayer(this, m_decryptionService, type, m_kVideoRequirements, + ->createGstGenericPlayer(this, m_decryptionService, type, m_kVideoRequirements, isLive, firebolt::rialto::wrappers::IRdkGstreamerUtilsWrapperFactory::getFactory()); if (!m_gstPlayer) { @@ -299,8 +302,13 @@ bool MediaPipelineServerInternal::removeSourceInternal(int32_t id) return false; } - m_gstPlayer->removeSource(sourceIter->first); - m_needMediaDataTimers.erase(sourceIter->first); + MediaSourceType type = sourceIter->first; + + m_gstPlayer->removeSource(type); + m_needMediaDataTimers.erase(type); + m_noAvailableSamplesCounter.erase(type); + m_isMediaTypeEosMap.erase(type); + m_attachedSources.erase(sourceIter); return true; } @@ -335,18 +343,18 @@ bool MediaPipelineServerInternal::allSourcesAttachedInternal() return true; } -bool MediaPipelineServerInternal::play() +bool MediaPipelineServerInternal::play(bool &async) { RIALTO_SERVER_LOG_DEBUG("entry:"); bool result; - auto task = [&]() { result = playInternal(); }; + auto task = [&]() { result = playInternal(async); }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return result; } -bool MediaPipelineServerInternal::playInternal() +bool MediaPipelineServerInternal::playInternal(bool &async) { if (!m_gstPlayer) { @@ -354,7 +362,7 @@ bool MediaPipelineServerInternal::playInternal() return false; } - m_gstPlayer->play(); + m_gstPlayer->play(async); return true; } @@ -460,6 +468,8 @@ bool MediaPipelineServerInternal::setPositionInternal(int64_t position) isMediaTypeEos.second = false; } + m_needDataDelayCalculator.resetMediaDataDelay(); + return true; } @@ -477,6 +487,20 @@ bool MediaPipelineServerInternal::getPosition(int64_t &position) return m_gstPlayer->getPosition(position); } +bool MediaPipelineServerInternal::getDuration(int64_t &duration) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + std::shared_lock lock{m_getPropertyMutex}; + + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed to get duration - Gstreamer player has not been loaded"); + return false; + } + return m_gstPlayer->getDuration(duration); +} + bool MediaPipelineServerInternal::getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -535,6 +559,63 @@ bool MediaPipelineServerInternal::setImmediateOutputInternal(int32_t sourceId, b return m_gstPlayer->setImmediateOutput(sourceIter->first, immediateOutput); } +bool MediaPipelineServerInternal::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + bool result; + auto task = [&]() { result = setReportDecodeErrorsInternal(sourceId, reportDecodeErrors); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; +} + +bool MediaPipelineServerInternal::setReportDecodeErrorsInternal(int32_t sourceId, bool reportDecodeErrors) +{ + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed - Gstreamer player has not been loaded"); + return false; + } + auto sourceIter = std::find_if(m_attachedSources.begin(), m_attachedSources.end(), + [sourceId](const auto &src) { return src.second == sourceId; }); + if (sourceIter == m_attachedSources.end()) + { + RIALTO_SERVER_LOG_ERROR("Failed - Source not found"); + return false; + } + + return m_gstPlayer->setReportDecodeErrors(sourceIter->first, reportDecodeErrors); +} + +bool MediaPipelineServerInternal::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + bool result; + auto task = [&]() { result = getQueuedFramesInternal(sourceId, queuedFrames); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; +} + +bool MediaPipelineServerInternal::getQueuedFramesInternal(int32_t sourceId, uint32_t &queuedFrames) +{ + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed - Gstreamer player has not been loaded"); + return false; + } + auto sourceIter = std::find_if(m_attachedSources.begin(), m_attachedSources.end(), + [sourceId](const auto &src) { return src.second == sourceId; }); + if (sourceIter == m_attachedSources.end()) + { + RIALTO_SERVER_LOG_ERROR("Failed - Source not found"); + return false; + } + return m_gstPlayer->getQueuedFrames(queuedFrames); +} + bool MediaPipelineServerInternal::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -689,10 +770,12 @@ bool MediaPipelineServerInternal::haveDataInternal(MediaSourceStatus status, uin RIALTO_SERVER_LOG_WARN("NeedData RequestID is not valid: %u", needDataRequestId); return true; } + const std::uint32_t kMaxNumFrames = m_activeRequests->getMaxFrames(needDataRequestId); m_activeRequests->erase(needDataRequestId); unsigned int &counter = m_noAvailableSamplesCounter[mediaSourceType]; - if (status != MediaSourceStatus::OK && status != MediaSourceStatus::EOS) + if (status != MediaSourceStatus::OK && status != MediaSourceStatus::EOS && + status != MediaSourceStatus::NO_SPACE_FOR_SAMPLES) { // Incrementing the counter allows us to track the occurrences where the status is other than OK or EOS. @@ -742,8 +825,10 @@ bool MediaPipelineServerInternal::haveDataInternal(MediaSourceStatus status, uin if (0 != numFrames) { + const bool kIsBufferFull = kMaxNumFrames == numFrames || status == MediaSourceStatus::NO_SPACE_FOR_SAMPLES || + status == MediaSourceStatus::EOS; std::shared_ptr dataReader = - m_dataReaderFactory->createDataReader(mediaSourceType, buffer, regionOffset, numFrames); + m_dataReaderFactory->createDataReader(mediaSourceType, buffer, regionOffset, numFrames, kIsBufferFull); if (!dataReader) { RIALTO_SERVER_LOG_ERROR("Metadata version not supported for %s request id: %u", @@ -809,7 +894,6 @@ bool MediaPipelineServerInternal::setVolume(double targetVolume, uint32_t volume { RIALTO_SERVER_LOG_DEBUG("entry:"); - m_isSetVolumeInProgress = true; bool result; auto task = [&]() { result = setVolumeInternal(targetVolume, volumeDuration, easeType); }; @@ -827,7 +911,6 @@ bool MediaPipelineServerInternal::setVolumeInternal(double targetVolume, uint32_ return false; } m_gstPlayer->setVolume(targetVolume, volumeDuration, easeType); - m_isSetVolumeInProgress = false; return true; } @@ -835,16 +918,11 @@ bool MediaPipelineServerInternal::getVolume(double ¤tVolume) { RIALTO_SERVER_LOG_DEBUG("entry:"); - if (m_isSetVolumeInProgress) - { - bool result; - auto task = [&]() { result = getVolumeInternal(currentVolume); }; + bool result; + auto task = [&]() { result = getVolumeInternal(currentVolume); }; - m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); - return result; - } - std::shared_lock lock{m_getPropertyMutex}; - return getVolumeInternal(currentVolume); + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; } bool MediaPipelineServerInternal::getVolumeInternal(double ¤tVolume) @@ -1152,6 +1230,8 @@ bool MediaPipelineServerInternal::flushInternal(int32_t sourceId, bool resetTime m_gstPlayer->flush(sourceIter->first, resetTime, async); + m_needMediaDataTimers.erase(sourceIter->first); + // Reset Eos on flush auto it = m_isMediaTypeEosMap.find(sourceIter->first); if (it != m_isMediaTypeEosMap.end() && it->second) @@ -1408,7 +1488,14 @@ bool MediaPipelineServerInternal::notifyNeedMediaData(MediaSourceType mediaSourc // action being taken bool result{true}; - auto task = [&]() { result = notifyNeedMediaDataInternal(mediaSourceType); }; + auto task = [&]() + { + result = notifyNeedMediaDataInternal(mediaSourceType); + if (result) + { + m_needDataDelayCalculator.decreaseNeedMediaDataDelay(mediaSourceType); + } + }; m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); @@ -1447,6 +1534,48 @@ bool MediaPipelineServerInternal::notifyNeedMediaDataInternal(MediaSourceType me return true; } +bool MediaPipelineServerInternal::notifyNeedMediaDataWithDelay(MediaSourceType mediaSourceType) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + // the task won't execute for a disconnected client therefore + // set a default value of true which will help to stop any further + // action being taken + bool result{true}; + + auto task = [&]() { result = notifyNeedMediaDataWithDelayInternal(mediaSourceType); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + + return result; +} + +bool MediaPipelineServerInternal::notifyNeedMediaDataWithDelayInternal(MediaSourceType mediaSourceType) +{ + m_needMediaDataTimers.erase(mediaSourceType); + m_shmBuffer->clearData(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_sessionId, mediaSourceType); + const auto kSourceIter = m_attachedSources.find(mediaSourceType); + + if (m_attachedSources.cend() == kSourceIter) + { + RIALTO_SERVER_LOG_WARN("NeedMediaData event sending failed for %s - sourceId not found", + common::convertMediaSourceType(mediaSourceType)); + return false; + } + auto it = m_isMediaTypeEosMap.find(mediaSourceType); + if (it != m_isMediaTypeEosMap.end() && it->second) + { + RIALTO_SERVER_LOG_INFO("EOS, NeedMediaData not needed for %s", common::convertMediaSourceType(mediaSourceType)); + return false; + } + + scheduleNotifyNeedMediaData(mediaSourceType); + + RIALTO_SERVER_LOG_DEBUG("%s NeedMediaData scheduled.", common::convertMediaSourceType(mediaSourceType)); + + return true; +} + void MediaPipelineServerInternal::notifyPosition(std::int64_t position) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -1523,6 +1652,7 @@ void MediaPipelineServerInternal::notifyBufferUnderflow(MediaSourceType mediaSou auto task = [&, mediaSourceType]() { + m_needDataDelayCalculator.resetMediaDataDelay(mediaSourceType); if (m_mediaPipelineClient) { const auto kSourceIter = m_attachedSources.find(mediaSourceType); @@ -1539,6 +1669,28 @@ void MediaPipelineServerInternal::notifyBufferUnderflow(MediaSourceType mediaSou m_mainThread->enqueueTask(m_mainThreadClientId, task); } +void MediaPipelineServerInternal::notifyFirstFrameReceived(MediaSourceType mediaSourceType) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + auto task = [&, mediaSourceType]() + { + if (m_mediaPipelineClient) + { + const auto kSourceIter = m_attachedSources.find(mediaSourceType); + if (m_attachedSources.cend() == kSourceIter) + { + RIALTO_SERVER_LOG_WARN("First frame notification failed - sourceId not found for %s", + common::convertMediaSourceType(mediaSourceType)); + return; + } + m_mediaPipelineClient->notifyFirstFrameReceived(kSourceIter->second); + } + }; + + m_mainThread->enqueueTask(m_mainThreadClientId, task); +} + void MediaPipelineServerInternal::notifyPlaybackError(MediaSourceType mediaSourceType, PlaybackError error) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -1579,6 +1731,7 @@ void MediaPipelineServerInternal::notifySourceFlushed(MediaSourceType mediaSourc m_mediaPipelineClient->notifySourceFlushed(kSourceIter->second); RIALTO_SERVER_LOG_DEBUG("%s source flushed", common::convertMediaSourceType(mediaSourceType)); } + m_needDataDelayCalculator.resetMediaDataDelay(mediaSourceType); }; m_mainThread->enqueueTask(m_mainThreadClientId, task); @@ -1621,19 +1774,23 @@ void MediaPipelineServerInternal::scheduleNotifyNeedMediaData(MediaSourceType me mediaSourceType)); scheduleNotifyNeedMediaData(mediaSourceType); } + else + { + m_needDataDelayCalculator.increaseNeedMediaDataDelay( + mediaSourceType); + } }); }); } -std::chrono::milliseconds MediaPipelineServerInternal::getNeedMediaDataTimeout(MediaSourceType mediaSourceType) const +std::chrono::milliseconds MediaPipelineServerInternal::getNeedMediaDataTimeout(MediaSourceType mediaSourceType) { - constexpr std::chrono::milliseconds kDefaultNeedMediaDataResendTimeMs{15}; constexpr std::chrono::milliseconds kNeedMediaDataResendTimeMsForLowLatency{5}; if ((mediaSourceType == MediaSourceType::VIDEO && m_IsLowLatencyVideoPlayer) || (mediaSourceType == MediaSourceType::AUDIO && m_IsLowLatencyAudioPlayer)) { return kNeedMediaDataResendTimeMsForLowLatency; } - return kDefaultNeedMediaDataResendTimeMs; + return m_needDataDelayCalculator.getNeedMediaDataDelay(mediaSourceType); } }; // namespace firebolt::rialto::server diff --git a/media/server/main/source/NeedDataDelayCalculator.cpp b/media/server/main/source/NeedDataDelayCalculator.cpp new file mode 100644 index 000000000..2ec0affcb --- /dev/null +++ b/media/server/main/source/NeedDataDelayCalculator.cpp @@ -0,0 +1,76 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "NeedDataDelayCalculator.h" + +namespace +{ +constexpr std::chrono::milliseconds kDefaultNeedMediaDataResendTimeMs{15}; +constexpr std::chrono::milliseconds kMaxDelayMs{500}; +} // namespace + +namespace firebolt::rialto::server +{ +std::chrono::milliseconds NeedDataDelayCalculator::getNeedMediaDataDelay(MediaSourceType mediaSourceType) +{ + auto it = m_delays.find(mediaSourceType); + if (it == m_delays.end()) + { + m_delays[mediaSourceType] = kDefaultNeedMediaDataResendTimeMs; + return kDefaultNeedMediaDataResendTimeMs; + } + return it->second; +} + +void NeedDataDelayCalculator::increaseNeedMediaDataDelay(MediaSourceType mediaSourceType) +{ + auto it = m_delays.find(mediaSourceType); + if (it == m_delays.end()) + { + m_delays[mediaSourceType] = kDefaultNeedMediaDataResendTimeMs; + return; + } + if (it->second * 2 < kMaxDelayMs) + { + it->second *= 2; + } +} + +void NeedDataDelayCalculator::decreaseNeedMediaDataDelay(MediaSourceType mediaSourceType) +{ + auto it = m_delays.find(mediaSourceType); + if (it != m_delays.end() && it->second / 2 >= kDefaultNeedMediaDataResendTimeMs) + { + it->second /= 2; + } +} + +void NeedDataDelayCalculator::resetMediaDataDelay(MediaSourceType mediaSourceType) +{ + m_delays[mediaSourceType] = kDefaultNeedMediaDataResendTimeMs; +} + +void NeedDataDelayCalculator::resetMediaDataDelay() +{ + for (auto &delay : m_delays) + { + delay.second = kDefaultNeedMediaDataResendTimeMs; + } +} +} // namespace firebolt::rialto::server diff --git a/media/server/main/source/NeedMediaData.cpp b/media/server/main/source/NeedMediaData.cpp index 3d24cdc4e..8f66542cf 100644 --- a/media/server/main/source/NeedMediaData.cpp +++ b/media/server/main/source/NeedMediaData.cpp @@ -31,8 +31,14 @@ NeedMediaData::NeedMediaData(std::weak_ptr client, IActive const ISharedMemoryBuffer &shmBuffer, int sessionId, MediaSourceType mediaSourceType, std::int32_t sourceId, PlaybackState currentPlaybackState) : m_client{client}, m_activeRequests{activeRequests}, m_mediaSourceType{mediaSourceType}, m_frameCount{kMaxFrames}, - m_sourceId{sourceId} + m_sourceId{sourceId}, m_maxMediaBytes{0} { + if (PlaybackState::PLAYING != currentPlaybackState) + { + RIALTO_SERVER_LOG_DEBUG("Pipeline in prerolling state. Sending smaller frame count for %s", + common::convertMediaSourceType(m_mediaSourceType)); + m_frameCount = kPrerollNumFrames; + } if (MediaSourceType::AUDIO != mediaSourceType && MediaSourceType::VIDEO != mediaSourceType && MediaSourceType::SUBTITLE != mediaSourceType) { @@ -67,7 +73,7 @@ bool NeedMediaData::send() const if (client && m_isValid) { client->notifyNeedMediaData(m_sourceId, m_frameCount, - m_activeRequests.insert(m_mediaSourceType, m_maxMediaBytes), m_shmInfo); + m_activeRequests.insert(m_mediaSourceType, m_maxMediaBytes, m_frameCount), m_shmInfo); return true; } return false; diff --git a/media/server/service/include/ICdmService.h b/media/server/service/include/ICdmService.h index fdcbc95e5..d93b7ffbd 100644 --- a/media/server/service/include/ICdmService.h +++ b/media/server/service/include/ICdmService.h @@ -48,10 +48,11 @@ class ICdmService virtual bool createMediaKeys(int mediaKeysHandle, std::string keySystem) = 0; virtual bool destroyMediaKeys(int mediaKeysHandle) = 0; virtual MediaKeyErrorStatus createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, + const std::shared_ptr &client, int32_t &keySessionId) = 0; virtual MediaKeyErrorStatus generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) = 0; + const std::vector &initData, + const LimitedDurationLicense &ldlState) = 0; virtual MediaKeyErrorStatus loadSession(int mediaKeysHandle, int32_t keySessionId) = 0; virtual MediaKeyErrorStatus updateSession(int mediaKeysHandle, int32_t keySessionId, const std::vector &responseData) = 0; @@ -76,6 +77,8 @@ class ICdmService virtual bool supportsKeySystem(const std::string &keySystem) = 0; virtual bool getSupportedKeySystemVersion(const std::string &keySystem, std::string &version) = 0; virtual bool isServerCertificateSupported(const std::string &keySystem) = 0; + virtual bool getSupportedRobustnessLevels(const std::string &keySystem, + std::vector &robustnessLevels) = 0; virtual void ping(const std::shared_ptr &heartbeatProcedure) = 0; }; diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index 7ba9e8a1c..a70476b19 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -44,17 +44,19 @@ class IMediaPipelineService virtual bool createSession(int sessionId, const std::shared_ptr &mediaPipelineClient, std::uint32_t maxWidth, std::uint32_t maxHeight) = 0; virtual bool destroySession(int sessionId) = 0; - virtual bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) = 0; + virtual bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, bool isLive) = 0; virtual bool attachSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool removeSource(int sessionId, std::int32_t sourceId) = 0; virtual bool allSourcesAttached(int sessionId) = 0; - virtual bool play(int sessionId) = 0; + virtual bool play(int sessionId, bool &async) = 0; virtual bool pause(int sessionId) = 0; virtual bool stop(int sessionId) = 0; virtual bool setPlaybackRate(int sessionId, double rate) = 0; virtual bool setPosition(int sessionId, std::int64_t position) = 0; virtual bool getPosition(int sessionId, std::int64_t &position) = 0; virtual bool setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) = 0; + virtual bool setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) = 0; + virtual bool getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) = 0; virtual bool getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) = 0; virtual bool getStats(int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) = 0; virtual bool setVideoWindow(int sessionId, std::uint32_t x, std::uint32_t y, std::uint32_t width, @@ -91,6 +93,7 @@ class IMediaPipelineService virtual void ping(const std::shared_ptr &heartbeatProcedure) = 0; virtual bool switchSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool isVideoMaster(bool &isVideoMaster) = 0; + virtual bool getDuration(int sessionId, std::int64_t &duration) = 0; }; } // namespace firebolt::rialto::server::service diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index 8ea716f61..7dc62c8eb 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -31,8 +31,8 @@ namespace firebolt::rialto::server::service { CdmService::CdmService(std::shared_ptr &&mediaKeysFactory, std::shared_ptr &&mediaKeysCapabilitiesFactory) - : m_mediaKeysFactory{mediaKeysFactory}, m_mediaKeysCapabilitiesFactory{mediaKeysCapabilitiesFactory}, - m_isActive{false} + : m_mediaKeysFactory{std::move(mediaKeysFactory)}, + m_mediaKeysCapabilitiesFactory{std::move(mediaKeysCapabilitiesFactory)}, m_isActive{false} { RIALTO_SERVER_LOG_DEBUG("CdmService is constructed"); } @@ -57,6 +57,8 @@ void CdmService::switchToInactive() { std::lock_guard lock{m_mediaKeysMutex}; m_mediaKeys.clear(); + m_mediaKeysClients.clear(); + m_sessionInfo.clear(); } } @@ -121,8 +123,7 @@ bool CdmService::destroyMediaKeys(int mediaKeysHandle) } MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, - int32_t &keySessionId) + const std::shared_ptr &client, int32_t &keySessionId) { RIALTO_SERVER_LOG_DEBUG("CdmService requested to create key session: %d", mediaKeysHandle); @@ -134,7 +135,7 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession return MediaKeyErrorStatus::FAIL; } - MediaKeyErrorStatus status = mediaKeysIter->second->createKeySession(sessionType, client, isLDL, keySessionId); + MediaKeyErrorStatus status = mediaKeysIter->second->createKeySession(sessionType, client, keySessionId); if (MediaKeyErrorStatus::OK == status) { if (m_mediaKeysClients.find(keySessionId) != m_mediaKeysClients.end()) @@ -143,9 +144,7 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession static_cast(removeKeySessionInternal(mediaKeysHandle, keySessionId)); return MediaKeyErrorStatus::FAIL; } - m_sessionInfo.emplace( - std::make_pair(keySessionId, - MediaKeySessionInfo{mediaKeysHandle, mediaKeysIter->second->isNetflixPlayreadyKeySystem()})); + m_sessionInfo.emplace(std::make_pair(keySessionId, MediaKeySessionInfo{mediaKeysHandle})); m_mediaKeysClients.emplace(std::make_pair(keySessionId, client)); } @@ -153,7 +152,8 @@ MediaKeyErrorStatus CdmService::createKeySession(int mediaKeysHandle, KeySession } MediaKeyErrorStatus CdmService::generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) + const std::vector &initData, + const LimitedDurationLicense &ldlState) { RIALTO_SERVER_LOG_DEBUG("CdmService requested to generate request: %d", mediaKeysHandle); @@ -164,7 +164,11 @@ MediaKeyErrorStatus CdmService::generateRequest(int mediaKeysHandle, int32_t key RIALTO_SERVER_LOG_ERROR("Media keys handle: %d does not exists", mediaKeysHandle); return MediaKeyErrorStatus::FAIL; } - return mediaKeysIter->second->generateRequest(keySessionId, initDataType, initData); + if (LimitedDurationLicense::NOT_SPECIFIED != ldlState && m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } + return mediaKeysIter->second->generateRequest(keySessionId, initDataType, initData, ldlState); } MediaKeyErrorStatus CdmService::loadSession(int mediaKeysHandle, int32_t keySessionId) @@ -289,7 +293,10 @@ MediaKeyErrorStatus CdmService::setDrmHeader(int mediaKeysHandle, int32_t keySes RIALTO_SERVER_LOG_ERROR("Media keys handle: %d does not exists", mediaKeysHandle); return MediaKeyErrorStatus::FAIL; } - + if (m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } return mediaKeysIter->second->setDrmHeader(keySessionId, requestData); } @@ -490,6 +497,26 @@ bool CdmService::isServerCertificateSupported(const std::string &keySystem) return mediaKeysCapabilities->isServerCertificateSupported(keySystem); } +bool CdmService::getSupportedRobustnessLevels(const std::string &keySystem, std::vector &robustnessLevels) +{ + RIALTO_SERVER_LOG_DEBUG("CdmService requested to getSupportedRobustnessLevels"); + + if (!m_isActive) + { + RIALTO_SERVER_LOG_ERROR("Skip getSupportedRobustnessLevels: Session Server in Inactive state"); + return false; + } + + auto mediaKeysCapabilities = m_mediaKeysCapabilitiesFactory->getMediaKeysCapabilities(); + if (!mediaKeysCapabilities) + { + RIALTO_SERVER_LOG_ERROR("MediaKeysCapabilities is null"); + return false; + } + + return mediaKeysCapabilities->getSupportedRobustnessLevels(keySystem, robustnessLevels); +} + MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) { RIALTO_SERVER_LOG_DEBUG("CdmService requested to decrypt, key session id: %d", keySessionId); @@ -504,9 +531,9 @@ MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypt return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->decrypt(keySessionId, encrypted, caps); } -bool CdmService::isNetflixPlayreadyKeySystem(int32_t keySessionId) +bool CdmService::isExtendedInterfaceUsed(int32_t keySessionId) { - RIALTO_SERVER_LOG_DEBUG("CdmService requested to check if key system is Netflix Playready, key session id: %d", + RIALTO_SERVER_LOG_DEBUG("CdmService requested to check if extended interface is used, key session id: %d", keySessionId); std::lock_guard lock{m_mediaKeysMutex}; @@ -516,7 +543,7 @@ bool CdmService::isNetflixPlayreadyKeySystem(int32_t keySessionId) RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exists", keySessionId); return false; } - return mediaKeysHandleIter->second.isNetflixPlayready; + return mediaKeysHandleIter->second.isExtendedInterfaceUsed; } MediaKeyErrorStatus CdmService::selectKeyId(int32_t keySessionId, const std::vector &keyId) @@ -530,6 +557,10 @@ MediaKeyErrorStatus CdmService::selectKeyId(int32_t keySessionId, const std::vec RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exists", keySessionId); return MediaKeyErrorStatus::FAIL; } + if (m_sessionInfo.find(keySessionId) != m_sessionInfo.end()) + { + m_sessionInfo[keySessionId].isExtendedInterfaceUsed = true; + } return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->selectKeyId(keySessionId, keyId); } diff --git a/media/server/service/source/CdmService.h b/media/server/service/source/CdmService.h index 468f6a706..f03cb5c46 100644 --- a/media/server/service/source/CdmService.h +++ b/media/server/service/source/CdmService.h @@ -37,7 +37,7 @@ class CdmService : public ICdmService, public IDecryptionService struct MediaKeySessionInfo { int mediaKeysHandle; - bool isNetflixPlayready{false}; + bool isExtendedInterfaceUsed{false}; uint32_t refCounter{0}; bool shouldBeClosed{false}; bool shouldBeReleased{false}; @@ -54,10 +54,10 @@ class CdmService : public ICdmService, public IDecryptionService bool createMediaKeys(int mediaKeysHandle, std::string keySystem) override; bool destroyMediaKeys(int mediaKeysHandle) override; MediaKeyErrorStatus createKeySession(int mediaKeysHandle, KeySessionType sessionType, - const std::shared_ptr &client, bool isLDL, - int32_t &keySessionId) override; + const std::shared_ptr &client, int32_t &keySessionId) override; MediaKeyErrorStatus generateRequest(int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData) override; + const std::vector &initData, + const LimitedDurationLicense &ldlState) override; MediaKeyErrorStatus loadSession(int mediaKeysHandle, int32_t keySessionId) override; MediaKeyErrorStatus updateSession(int mediaKeysHandle, int32_t keySessionId, const std::vector &responseData) override; @@ -82,8 +82,9 @@ class CdmService : public ICdmService, public IDecryptionService bool supportsKeySystem(const std::string &keySystem) override; bool getSupportedKeySystemVersion(const std::string &keySystem, std::string &version) override; bool isServerCertificateSupported(const std::string &keySystem) override; + bool getSupportedRobustnessLevels(const std::string &keySystem, std::vector &robustnessLevels) override; MediaKeyErrorStatus decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) override; - bool isNetflixPlayreadyKeySystem(int32_t keySessionId) override; + bool isExtendedInterfaceUsed(int32_t keySessionId) override; MediaKeyErrorStatus selectKeyId(int32_t keySessionId, const std::vector &keyId) override; void incrementSessionIdUsageCounter(int32_t keySessionId) override; void decrementSessionIdUsageCounter(int32_t keySessionId) override; diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index 5e7c40063..717063564 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -32,7 +32,7 @@ MediaPipelineService::MediaPipelineService( IPlaybackService &playbackService, std::shared_ptr &&mediaPipelineFactory, std::shared_ptr &&mediaPipelineCapabilitiesFactory, IDecryptionService &decryptionService) - : m_playbackService{playbackService}, m_mediaPipelineFactory{mediaPipelineFactory}, + : m_playbackService{playbackService}, m_mediaPipelineFactory{std::move(mediaPipelineFactory)}, m_mediaPipelineCapabilities{mediaPipelineCapabilitiesFactory->createMediaPipelineCapabilities()}, m_decryptionService{decryptionService} { @@ -113,7 +113,8 @@ bool MediaPipelineService::destroySession(int sessionId) return true; } -bool MediaPipelineService::load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) +bool MediaPipelineService::load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, + bool isLive) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to load session with id: %d", sessionId); @@ -124,7 +125,7 @@ bool MediaPipelineService::load(int sessionId, MediaType type, const std::string RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); return false; } - return mediaPipelineIter->second->load(type, mimeType, url); + return mediaPipelineIter->second->load(type, mimeType, url, isLive); } bool MediaPipelineService::attachSource(int sessionId, const std::unique_ptr &source) @@ -169,7 +170,7 @@ bool MediaPipelineService::allSourcesAttached(int sessionId) return mediaPipelineIter->second->allSourcesAttached(); } -bool MediaPipelineService::play(int sessionId) +bool MediaPipelineService::play(int sessionId, bool &async) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to play, session id: %d", sessionId); @@ -180,7 +181,7 @@ bool MediaPipelineService::play(int sessionId) RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); return false; } - return mediaPipelineIter->second->play(); + return mediaPipelineIter->second->play(async); } bool MediaPipelineService::pause(int sessionId) @@ -253,6 +254,20 @@ bool MediaPipelineService::getPosition(int sessionId, std::int64_t &position) return mediaPipelineIter->second->getPosition(position); } +bool MediaPipelineService::getDuration(int sessionId, std::int64_t &duration) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to get duration, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exist", sessionId); + return false; + } + return mediaPipelineIter->second->getDuration(duration); +} + bool MediaPipelineService::setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to setImmediateOutput, session id: %d", sessionId); @@ -267,6 +282,34 @@ bool MediaPipelineService::setImmediateOutput(int sessionId, int32_t sourceId, b return mediaPipelineIter->second->setImmediateOutput(sourceId, immediateOutput); } +bool MediaPipelineService::setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to setReportDecodeErrors, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); + return false; + } + return mediaPipelineIter->second->setReportDecodeErrors(sourceId, reportDecodeErrors); +} + +bool MediaPipelineService::getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to getQueuedFrames, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); + return false; + } + return mediaPipelineIter->second->getQueuedFrames(sourceId, queuedFrames); +} + bool MediaPipelineService::getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to getImmediateOutput, session id: %d", sessionId); diff --git a/media/server/service/source/MediaPipelineService.h b/media/server/service/source/MediaPipelineService.h index d68edadff..7e777c777 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -55,17 +55,20 @@ class MediaPipelineService : public IMediaPipelineService bool createSession(int sessionId, const std::shared_ptr &mediaPipelineClient, std::uint32_t maxWidth, std::uint32_t maxHeight) override; bool destroySession(int sessionId) override; - bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url) override; + bool load(int sessionId, MediaType type, const std::string &mimeType, const std::string &url, bool isLive) override; bool attachSource(int sessionId, const std::unique_ptr &source) override; bool removeSource(int sessionId, std::int32_t sourceId) override; bool allSourcesAttached(int sessionId) override; - bool play(int sessionId) override; + bool play(int sessionId, bool &async) override; bool pause(int sessionId) override; bool stop(int sessionId) override; bool setPlaybackRate(int sessionId, double rate) override; bool setPosition(int sessionId, std::int64_t position) override; bool getPosition(int sessionId, std::int64_t &position) override; + bool getDuration(int sessionId, std::int64_t &duration) override; bool setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) override; + bool getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) override; bool getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) override; bool getStats(int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; bool setVideoWindow(int sessionId, std::uint32_t x, std::uint32_t y, std::uint32_t width, diff --git a/media/server/service/source/WebAudioPlayerService.cpp b/media/server/service/source/WebAudioPlayerService.cpp index e2182c201..b3831e63e 100644 --- a/media/server/service/source/WebAudioPlayerService.cpp +++ b/media/server/service/source/WebAudioPlayerService.cpp @@ -34,7 +34,7 @@ namespace firebolt::rialto::server::service { WebAudioPlayerService::WebAudioPlayerService(IPlaybackService &playbackService, std::shared_ptr &&webAudioPlayerFactory) - : m_playbackService{playbackService}, m_webAudioPlayerFactory{webAudioPlayerFactory} + : m_playbackService{playbackService}, m_webAudioPlayerFactory{std::move(webAudioPlayerFactory)} { RIALTO_SERVER_LOG_DEBUG("WebAudioPlayerService is constructed"); } diff --git a/openspec/changes/audio-first-frame/.openspec.yaml b/openspec/changes/audio-first-frame/.openspec.yaml new file mode 100644 index 000000000..a2168c37b --- /dev/null +++ b/openspec/changes/audio-first-frame/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-01 diff --git a/openspec/changes/audio-first-frame/design.md b/openspec/changes/audio-first-frame/design.md new file mode 100644 index 000000000..253af7f33 --- /dev/null +++ b/openspec/changes/audio-first-frame/design.md @@ -0,0 +1,86 @@ +## Context + +Rialto already supports first-frame notification for video and already has the downstream plumbing needed to propagate a first-frame event from gstplayer through the worker thread, server, IPC transport, and client callback. Audio playback currently lacks equivalent detection, which creates an observability gap even though the existing notification path is capable of carrying an audio first-frame event once one is detected. + +This change touches multiple layers: gstplayer element setup, first-frame scheduling, server-side forwarding, IPC transport, and client delivery. The design therefore focuses on how audio first-frame detection is introduced without changing the public callback or protobuf contract and without regressing the existing video path. + +## Goals / Non-Goals + +**Goals:** +- Add first-audio-frame detection in gstplayer using capability-based discovery instead of platform branching. +- Reuse the existing first-frame task scheduling and propagation path by routing audio notifications with `MediaSourceType::AUDIO`. +- Support audio sink implementations that do not expose callback-based first-frame detection by using a bounded fallback probe. +- Preserve existing public client callbacks and existing `FirstFrameReceivedEvent` IPC transport. +- Keep video first-frame behavior unchanged. + +**Non-Goals:** +- Redesign the current first-frame architecture. +- Introduce platform- or vendor-specific branches for audio first-frame support. +- Add a new public callback or a new protobuf event type for audio first-frame notification. +- Change behavior outside the first-frame detection and propagation path. + +## Decisions + +### Use role-specific capability detection during element setup + +Audio first-frame support will be discovered when gstplayer configures pipeline elements. Audio decoder elements will be checked for `first-audio-frame`, while audio sink elements will be checked for `first-audio-frame-callback`. + +This keeps detection aligned with the existing setup flow and avoids hard-coding platform names or element allowlists. It also lets decoder and sink behavior diverge cleanly where platform implementations expose different signaling mechanisms. + +Alternatives considered: +- Use platform-specific branching: rejected because it scales poorly across vendors and hard-codes knowledge that should remain capability based. +- Use a probe for all audio elements: rejected because native callback/signal support is cheaper and more precise when available. + +### Use sink-pad probing only as a fallback for audio sinks + +If an audio sink does not expose `first-audio-frame-callback`, Rialto will install a sink pad probe and treat the first valid audio buffer as the first-frame trigger. The probe will be removed immediately after the first valid trigger and also removed during teardown, flush, reset, or pipeline stop. + +This provides coverage for sink implementations that cannot emit a callback while keeping probe usage narrow and bounded. Restricting the fallback to audio sinks avoids adding speculative probe behavior to decoder elements where the expected mechanism is explicit capability detection. + +Alternatives considered: +- No fallback path: rejected because platforms without sink callback capability would remain unsupported. +- Probe both decoders and sinks: rejected because it broadens runtime overhead and increases ambiguity in which stage constitutes first-frame detection. + +### Reuse the existing first-frame scheduling and propagation path + +Once audio first-frame is detected, the implementation will schedule the existing first-frame handling flow and identify the source using `MediaSourceType::AUDIO`. Server-side forwarding, IPC serialization, and client dispatch will continue to use the existing `notifyFirstFrameReceived(...)` and `FirstFrameReceivedEvent` path. + +This minimizes surface area, keeps the client contract stable, and avoids introducing a parallel audio-specific notification stack. + +Alternatives considered: +- Add a dedicated audio-first-frame event type: rejected because it duplicates the current pipeline without adding functional value. +- Dispatch directly from gstplayer to clients: rejected because it bypasses existing threading and session/source mapping behavior. + +### Guard emission so exactly one first-frame event is sent per audio source lifecycle + +Both callback-based and probe-based detection may observe the same source lifecycle. The implementation will keep a one-shot guard for the relevant audio source/session so only one first-frame event is scheduled and propagated. Cleanup paths will remove probe state and clear any temporary tracking during teardown and reset operations. + +This preserves deterministic client behavior and prevents duplicate telemetry or callback delivery. + +Alternatives considered: +- Allow duplicate low-level detections and deduplicate later: rejected because duplicates would still traverse more of the pipeline and complicate server/client behavior. + +## Risks / Trade-offs + +- Duplicate detection from callback and probe paths -> Mitigation: keep a one-shot guard at the first scheduling point and remove fallback probes immediately after the first valid trigger. +- Incorrect trigger on non-audio or non-buffer sink activity -> Mitigation: only treat the first valid audio buffer as a trigger and ignore unrelated pad activity. +- Regression in existing video behavior -> Mitigation: leave the video detection path unchanged and run existing first-frame non-regression coverage alongside new audio tests. +- Threading issues if detection performs too much work on the GStreamer thread -> Mitigation: keep callbacks and probes minimal and schedule work onto the existing worker-thread path. +- Platform variability in element support -> Mitigation: prefer capability-based detection and limit fallback logic to the specific sink case where callback support is absent. + +## Migration Plan + +1. Implement audio capability detection during gstplayer setup for decoders and sinks. +2. Add sink fallback probe handling, including install, one-shot trigger, and cleanup paths. +3. Route detected audio first-frame events through the existing scheduling and propagation flow using `MediaSourceType::AUDIO`. +4. Add or update unit tests for setup-element detection behavior, fallback probing, and single-emission guarantees. +5. Add or update component coverage for end-to-end server and client delivery of audio first-frame notifications. +6. Validate that existing video first-frame tests remain unchanged. + +Rollback is low risk because the change is additive. If regressions are found, audio detection and fallback wiring can be removed while leaving the existing video path and notification contract intact. + +## Open Questions + +- Which exact gstplayer setup abstraction should own the audio sink probe lifecycle so cleanup is guaranteed across all teardown paths? +- Does any supported platform expose both decoder and sink audio first-frame signals simultaneously, and if so, where should the one-shot guard live to keep behavior deterministic? +- Are there existing test fixtures for sink pad probe behavior that can be extended, or does this change require new unit helpers for probe installation and cleanup validation? \ No newline at end of file diff --git a/openspec/changes/audio-first-frame/proposal.md b/openspec/changes/audio-first-frame/proposal.md new file mode 100644 index 000000000..c49fa4f40 --- /dev/null +++ b/openspec/changes/audio-first-frame/proposal.md @@ -0,0 +1,26 @@ +## Why + +Rialto already reports first-frame receipt for video, but it does not provide equivalent signaling for audio. Adding audio first-frame support closes that observability gap now that the existing first-frame pipeline already covers the worker-thread, server, IPC, and client callback path needed to carry the event end to end. + +## What Changes + +- Extend gstplayer setup to detect first-audio-frame support using element capabilities instead of platform-specific branching. +- Use `first-audio-frame` for audio decoder elements and `first-audio-frame-callback` for audio sink elements when those capabilities are available. +- Add a sink-only fallback probe when an audio sink does not expose callback support, and remove it immediately after the first valid audio buffer is observed. +- Reuse the existing first-frame scheduling, server forwarding, IPC event, and client callback path for audio by routing the event with `MediaSourceType::AUDIO`. +- Preserve the existing public callback and protobuf contract so the change remains additive and non-breaking. + +## Capabilities + +### New Capabilities +- `audio-first-frame`: Detect and propagate the first rendered audio frame through the existing first-frame notification pipeline. + +### Modified Capabilities + +None. + +## Impact + +Affected areas include gstplayer element setup and first-frame detection, worker-thread task scheduling for first-frame handling, server-side forwarding via `notifyFirstFrameReceived(...)`, IPC transport using `FirstFrameReceivedEvent`, and client-side forwarding to `notifyFirstFrameReceived(sourceId)`. + +This change does not introduce a new public callback or protobuf message, but it does require unit and component coverage for audio capability detection, sink probe fallback, one-shot event emission, and video non-regression. diff --git a/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md b/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md new file mode 100644 index 000000000..5585e9102 --- /dev/null +++ b/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md @@ -0,0 +1,47 @@ +## ADDED Requirements + +### Requirement: Audio decoder first-frame detection +The system SHALL detect audio first-frame support on audio decoder elements by checking for the `first-audio-frame` capability during element setup. + +#### Scenario: Audio decoder exposes first-frame capability +- **WHEN** an audio decoder element exposes `first-audio-frame` during setup +- **THEN** the system connects that capability for the audio source +- **THEN** the first detected audio frame is scheduled through the existing first-frame handling path + +### Requirement: Audio sink first-frame fallback +The system SHALL detect audio first-frame support on audio sink elements by using `first-audio-frame-callback` when available and SHALL install a sink pad probe only when that callback capability is unavailable. + +#### Scenario: Audio sink exposes callback capability +- **WHEN** an audio sink element exposes `first-audio-frame-callback` during setup +- **THEN** the system uses that callback capability for first-frame detection +- **THEN** no fallback sink pad probe is installed for that sink + +#### Scenario: Audio sink does not expose callback capability +- **WHEN** an audio sink element does not expose `first-audio-frame-callback` during setup +- **THEN** the system installs a sink pad probe for that sink +- **THEN** the first valid audio buffer observed by the probe triggers first-frame handling +- **THEN** the probe is removed immediately after the first valid audio buffer is observed + +### Requirement: Single audio first-frame notification +The system MUST emit exactly one first-frame notification for each relevant audio source lifecycle even if both capability-based and probe-based detection paths become active. + +#### Scenario: Multiple detection paths observe the same first frame +- **WHEN** capability-based detection and fallback probe detection both observe the same audio source lifecycle +- **THEN** the system emits only one first-frame notification for that audio source + +#### Scenario: Audio source ends before first frame +- **WHEN** an audio source is flushed, reset, torn down, or the pipeline stops before the first audio frame is emitted +- **THEN** the system removes any installed fallback probe without emitting a duplicate or stale first-frame notification + +### Requirement: Existing first-frame contract reuse +The system SHALL propagate audio first-frame notifications through the existing first-frame pipeline using `MediaSourceType::AUDIO` without introducing a new public callback or protobuf event. + +#### Scenario: Audio first frame reaches the client +- **WHEN** the system detects the first frame for an audio source +- **THEN** it forwards the event through the existing worker-thread, server, IPC, and client callback path +- **THEN** the client receives the existing `notifyFirstFrameReceived(sourceId)` callback for the audio source + +#### Scenario: Existing protocol remains unchanged +- **WHEN** audio first-frame support is added +- **THEN** the system continues to use the existing `FirstFrameReceivedEvent` transport shape +- **THEN** no new public callback name or protobuf message is required \ No newline at end of file diff --git a/openspec/changes/audio-first-frame/tasks.md b/openspec/changes/audio-first-frame/tasks.md new file mode 100644 index 000000000..081a555c8 --- /dev/null +++ b/openspec/changes/audio-first-frame/tasks.md @@ -0,0 +1,33 @@ +## 1. Gstplayer Detection Wiring + +- [x] 1.1 Update signal lookup to include audio first-frame signal names in media/server/gstplayer/source/Utils.cpp. +- [x] 1.2 Add audio first-frame callback connection in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 1.3 Add audio first-frame scheduler API in media/server/gstplayer/include/IGstGenericPlayerPrivate.h and media/server/gstplayer/include/GstGenericPlayer.h. +- [x] 1.4 Implement audio first-frame scheduling with MediaSourceType::AUDIO in media/server/gstplayer/source/GstGenericPlayer.cpp. + +## 2. Audio Sink Fallback Probe + +- [x] 2.1 Implement sink pad probe installation for audio sinks without callback capability in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 2.2 Add probe state storage and lifecycle hooks in media/server/gstplayer/source/GstGenericPlayer.cpp and media/server/gstplayer/include/GstGenericPlayer.h. +- [x] 2.3 Remove probe on first trigger and terminal paths (flush/reset/stop/teardown) in media/server/gstplayer/source/GstGenericPlayer.cpp. + +## 3. One-Shot Emission Guard + +- [x] 3.1 Add a per-audio-source one-shot first-frame guard in media/server/gstplayer/source/GstGenericPlayer.cpp. +- [x] 3.2 Wire guard checks in both callback and probe paths in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 3.3 Extend private-interface mocks for new audio scheduler hooks in tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h. + +## 4. End-to-End Notification Reuse + +- [ ] 4.1 Validate audio source-id mapping path in media/server/main/source/MediaPipelineServerInternal.cpp (no API changes expected). +- [ ] 4.2 Validate server IPC event emission remains unchanged in media/server/ipc/source/MediaPipelineClient.cpp and proto/mediapipelinemodule.proto. +- [ ] 4.3 Validate client IPC and callback path remains unchanged in media/client/ipc/source/MediaPipelineIpc.cpp and media/client/main/source/MediaPipeline.cpp. + +## 5. Test Coverage and Validation + +- [x] 5.1 Add setup-task unit coverage for audio first-frame signal hookup in tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp and tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp. +- [x] 5.2 Add private-player unit coverage for audio first-frame scheduling in tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp. +- [x] 5.3 Add/update first-frame task behavior assertions in tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp. +- [x] 5.4 Add/update server component first-frame flow for audio source in tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp. +- [x] 5.5 Add/update client component first-frame flow for audio source in tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp and tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp. +- [ ] 5.6 Run openspec validate audio-first-frame --type change --json --no-interactive and execute affected unit/component test targets for non-regression. \ No newline at end of file diff --git a/proto/mediakeyscapabilitiesmodule.proto b/proto/mediakeyscapabilitiesmodule.proto index 347b67bb2..eae3ac123 100644 --- a/proto/mediakeyscapabilitiesmodule.proto +++ b/proto/mediakeyscapabilitiesmodule.proto @@ -83,6 +83,21 @@ message IsServerCertificateSupportedResponse { optional bool is_supported = 1; } +/** + * @fn bool, vector getSupportedRobustnessLevels(const std::string &keySystem) + * @brief Returns supported robustness levels for a key system. + * + * @param[in] key_system The key system to query. + * + * @returns the supported robustness levels. + */ +message GetSupportedRobustnessLevelsRequest { + optional string key_system = 1; +} +message GetSupportedRobustnessLevelsResponse { + repeated string robustness_levels = 1; +} + service MediaKeysCapabilitiesModule { /** * @brief Returns the EME key systems supported by Rialto. @@ -111,4 +126,11 @@ service MediaKeysCapabilitiesModule { */ rpc isServerCertificateSupported(IsServerCertificateSupportedRequest) returns (IsServerCertificateSupportedResponse) { } + + /** + * @brief Gets the robustness levels supported by the specified key system. + * @see GetSupportedRobustnessLevelsRequest + */ + rpc getSupportedRobustnessLevels(GetSupportedRobustnessLevelsRequest) returns (GetSupportedRobustnessLevelsResponse) { + } } diff --git a/proto/mediakeysmodule.proto b/proto/mediakeysmodule.proto index 0fa4905ad..79ff9e9d5 100644 --- a/proto/mediakeysmodule.proto +++ b/proto/mediakeysmodule.proto @@ -37,6 +37,7 @@ enum ProtoMediaKeyErrorStatus { INVALID_STATE = 4; ///< The object is in an invalid state for the operation. */ INTERFACE_NOT_IMPLEMENTED = 5; ///< The interface is not implemented. */ BUFFER_TOO_SMALL = 6; ///< The buffer size is too small. */ + OUTPUT_RESTRICTED = 7; ///< Output protection prevents the operation from succeeding. */ }; /** @@ -203,6 +204,8 @@ message CreateKeySessionResponse { * @param[in] key_session_id The key session id for the session. * @param[in] init_data_type The init data type. * @param[in] init_data The init data. + * @param[in] ldlState The Limited Duration License state. Most of key systems do not need this parameter, + * so the default value is NOT_SPECIFIED. * * @retval an error status. */ @@ -215,11 +218,17 @@ message GenerateRequestRequest { DRMHEADER = 4; ///< The init data is in DrmHeader format. }; + enum LimitedDurationLicense { + NOT_SPECIFIED = 0; ///< The license duration is not specified + ENABLED = 1; ///< The license has a limited duration + DISABLED = 2; ///< The license does not have a limited duration + }; + optional int32 media_keys_handle = 1 [default = -1]; optional int32 key_session_id = 2 [default = -1]; optional InitDataType init_data_type = 3; repeated uint32 init_data = 4; - + optional LimitedDurationLicense ldl_state = 5 [default = NOT_SPECIFIED]; } message GenerateRequestResponse { optional ProtoMediaKeyErrorStatus error_status = 1 [default = FAIL]; diff --git a/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index f56a19970..518e6ddc0 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -67,13 +67,14 @@ message DestroySessionResponse { } /** - * @fn void load(MediaType type, string mime_type, string url) + * @fn void load(MediaType type, string mime_type, string url, bool isLive) * @brief Loads the media pipeline. * * @param[in] session_id The id of the A/V session. * @param[in] type The type of media. * @param[in] mime_type The mime type. * @param[in] url The url. + * @param[in] isLive Indicates if the media is live. * */ message LoadRequest { @@ -86,6 +87,7 @@ message LoadRequest { optional MediaType type = 2; optional string mime_type = 3; optional string url = 4; + optional bool is_live = 5; } message LoadResponse { } @@ -261,7 +263,8 @@ message SetVideoWindowResponse { * @fn void play(int session_id) * @brief Starts playback on a session. * - * @param[in] session_id The id of the A/V session. + * @param[in] session_id The id of the A/V session. + * @param[out] async True if play method call is asynchronous * * This method is asynchronous. Once the backend is successfully playing it will notify the media player client of * playback state. @@ -271,6 +274,7 @@ message PlayRequest { optional int32 session_id = 1 [default = -1]; } message PlayResponse { + optional bool async = 1 [default = true]; } /** @@ -343,6 +347,22 @@ message GetPositionResponse { optional int64 position = 1 [default = -1]; } +/** + * @fn void getDuration(int session_id) + * @brief Get the playback Duration in nanoseconds. + * + * @param[in] session_id The id of the A/V session. + * + * This method is considered to be synchronous, it returns current playback duration + * + */ +message GetDurationRequest { + optional int32 session_id = 1 [default = -1]; +} +message GetDurationResponse { + optional int64 duration = 1 [default = -1]; +} + /** * @fn void setPlaybackRate(int session_id, double rate) * @brief Set the playback position in nanoseconds. @@ -375,8 +395,9 @@ message HaveDataRequest { OK = 1; ///< Source data provided without error. EOS = 2; ///< Source reached the end of stream. ERROR = 3; ///< There was an error providing source data. - CODEC_CHANGED = 4; ///< strThe codec has changed and the decoder must be reconfigured. + CODEC_CHANGED = 4; ///< The codec has changed and the decoder must be reconfigured. NO_AVAILABLE_SAMPLES = 5; ///< Could not retrieve media samples. + NO_SPACE_FOR_SAMPLES = 6; ///< Could not copy data to shared buffer space. }; optional int32 session_id = 1 [default = -1]; @@ -676,6 +697,24 @@ message SetImmediateOutputRequest { message SetImmediateOutputResponse { } +/** + * @fn void setReportDecodeErrors(int session_id, int source_id, bool report_decode_errors) + * @brief Enables or disables decode error reporting for this source. + * + * @param[in] session_id The id of the A/V session. + * @param[in] source_id The id of the media source. + * @param[in] report_decode_errors Enable decode error reporting. + */ +message SetReportDecodeErrorsRequest { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; + optional bool report_decode_errors = 3 [default = false]; +} + +message SetReportDecodeErrorsResponse { +} + + /** * @fn void getImmediateOutput(int session_id, int source_id, bool &immediate_output) * @brief Gets the "Immediate Output" property for this source. @@ -693,6 +732,23 @@ message GetImmediateOutputResponse { optional bool immediate_output = 1 [default = false]; } +/** + * @fn void getQueuedFrames(int session_id, int source_id, uint32_t &queued_frames) + * @brief Gets the "Queued Frames" property for this source. + * + * @param[in] session_id The id of the A/V session. + * @param[in] source_id The id of the media source. + * @param[out] queued_frames The number of queued frames on the decoder + * + */ +message GetQueuedFramesRequest { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; +} +message GetQueuedFramesResponse { + optional uint32 queued_frames = 1 [default = 0]; +} + /** * @fn void getStats(int session_id, int source_id, uint64 &rendered_frames, uint64 &dropped_frames) * @brief Get various stats from the source. @@ -933,6 +989,19 @@ message BufferUnderflowEvent { optional int32 source_id = 2 [default = -1]; } +/** + * @brief Event sent by the server when the first frame has been received. + * + * @param session_id The id of the A/V session the request is for. + * @param source_id The id of the media source the request is for. + * + * This is sent by the server whenever a video/audio first frame is received + */ +message FirstFrameReceivedEvent { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; +} + /** * @brief Event sent by the server when a non-fatal error has occurred in the player. * @@ -944,6 +1013,7 @@ message PlaybackErrorEvent { enum PlaybackError { UNKNOWN = 0; ///< An unknown or undefined network state. DECRYPTION = 1; ///< Player failed to decrypt a buffer and the frame has been dropped. + OUTPUT_PROTECTION = 2; ///< HDCP output protection failure - triggers retune. } optional int32 session_id = 1 [default = -1]; @@ -1076,6 +1146,14 @@ service MediaPipelineModule { rpc setImmediateOutput(SetImmediateOutputRequest) returns (SetImmediateOutputResponse) { } + /** + * @brief Sets the "Report Decode Errors" property for this source. + * @see SetReportDecodeErrorsRequest + */ + rpc setReportDecodeErrors(SetReportDecodeErrorsRequest) + returns (SetReportDecodeErrorsResponse) { + } + /** * @brief Gets the "Immediate Output" property for this source. * @see GetImmediateOutputRequest @@ -1083,6 +1161,13 @@ service MediaPipelineModule { rpc getImmediateOutput(GetImmediateOutputRequest) returns (GetImmediateOutputResponse) { } + /** + * @brief Gets the "Queued Frames" property for this source. + * @see GetQueuedFramesRequest + */ + rpc getQueuedFrames(GetQueuedFramesRequest) returns (GetQueuedFramesResponse) { + } + /** * @brief Gets the current stats property * @see GetStatsRequest @@ -1251,4 +1336,12 @@ service MediaPipelineModule { */ rpc getUseBuffering(GetUseBufferingRequest) returns (GetUseBufferingResponse) { } + + /** + * @brief Gets the current duration of the playback + * @see GetDurationRequest + */ + rpc getDuration(GetDurationRequest) returns (GetDurationResponse) { + } + } diff --git a/scripts/gtest/build_and_run_tests.py b/scripts/gtest/build_and_run_tests.py index 231f538a9..3ad119f44 100644 --- a/scripts/gtest/build_and_run_tests.py +++ b/scripts/gtest/build_and_run_tests.py @@ -73,6 +73,8 @@ def buildAndRunGTests(args, f, buildDefines, suitesToRun): os.environ["RIALTO_CONSOLE_LOG"] = "1" # Set env variable to enable debug prints os.environ["RIALTO_DEBUG"] = "5" + # Enable profiler for all test suites. + os.environ["PROFILER_ENABLED"] = "true" # Clean if required if args['clean'] == True: @@ -147,7 +149,8 @@ def runTests (suites, doListTests, gtestFilter, outputDir, resultsFile, xmlFile, # Run the command if resultsFile != None: - status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stdout=resultsFile, stderr=subprocess.STDOUT) + status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stdout=resultsFile, + stderr=subprocess.STDOUT) else: status = runcmd(executeCmd, cwd=os.getcwd() + '/' + outputDir, stderr=subprocess.STDOUT) diff --git a/serverManager/common/source/HealthcheckService.cpp b/serverManager/common/source/HealthcheckService.cpp index 6a478a64d..7abc8e205 100644 --- a/serverManager/common/source/HealthcheckService.cpp +++ b/serverManager/common/source/HealthcheckService.cpp @@ -65,7 +65,7 @@ void HealthcheckService::onPingSent(int serverId, int pingId) return; } m_remainingPings.insert(serverId); - m_failedPings.try_emplace(serverId, 0); + m_failedPings.try_emplace(serverId, std::set{}); } void HealthcheckService::onPingFailed(int serverId, int pingId) @@ -86,7 +86,7 @@ void HealthcheckService::onPingFailed(int serverId, int pingId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - m_failedPings.emplace(serverId, 1); + m_failedPings.emplace(serverId, std::set{pingId}); } } @@ -95,15 +95,26 @@ void HealthcheckService::onAckReceived(int serverId, int pingId, bool success) std::unique_lock lock{m_mutex}; if (pingId != m_currentPingId) { - RIALTO_SERVER_MANAGER_LOG_WARN("Unexpected ack received from server id: %d. Current ping id: %d, received ping " - "id: %d", - serverId, m_currentPingId, pingId); + if (success && m_failedPings[serverId].find(pingId) != m_failedPings[serverId].end()) + { + RIALTO_SERVER_MANAGER_LOG_WARN("Late ack received for server id: %d, Current ping id: %d, received ping " + "id: %d. Removing from failed pings list", + serverId, m_currentPingId, pingId); + m_failedPings[serverId].erase(pingId); + } + else + { + RIALTO_SERVER_MANAGER_LOG_ERROR("Unexpected ack received from server id: %d. Current ping id: %d, received " + "ping " + "id: %d", + serverId, m_currentPingId, pingId); + } return; } m_remainingPings.erase(serverId); if (success) { - m_failedPings[serverId] = 0; + m_failedPings[serverId].clear(); } else { @@ -136,12 +147,13 @@ void HealthcheckService::sendPing() void HealthcheckService::handleError(int serverId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - unsigned &failedPingsNum{m_failedPings[serverId]}; - if (++failedPingsNum >= m_kNumOfFailedPingsBeforeRecovery) + auto &failedPings{m_failedPings[serverId]}; + failedPings.insert(m_currentPingId); + if (failedPings.size() >= m_kNumOfFailedPingsBeforeRecovery) { RIALTO_SERVER_MANAGER_LOG_WARN( "Max num of failed pings reached for server with id: %d. Starting recovery action", serverId); - failedPingsNum = 0; + failedPings.clear(); m_sessionServerAppManager.restartServer(serverId); } } diff --git a/serverManager/common/source/HealthcheckService.h b/serverManager/common/source/HealthcheckService.h index d0517bdb4..b3649ac0b 100644 --- a/serverManager/common/source/HealthcheckService.h +++ b/serverManager/common/source/HealthcheckService.h @@ -53,7 +53,7 @@ class HealthcheckService : public IHealthcheckService std::mutex m_mutex; int m_currentPingId; std::set m_remainingPings; - std::map m_failedPings; + std::map> m_failedPings; }; } // namespace rialto::servermanager::common diff --git a/serverManager/common/source/ISessionServerAppFactory.h b/serverManager/common/source/ISessionServerAppFactory.h index 18c5323ce..7b2d9d1ab 100644 --- a/serverManager/common/source/ISessionServerAppFactory.h +++ b/serverManager/common/source/ISessionServerAppFactory.h @@ -36,11 +36,11 @@ class ISessionServerAppFactory ISessionServerAppFactory() = default; virtual ~ISessionServerAppFactory() = default; - virtual std::unique_ptr + virtual std::shared_ptr create(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const = 0; - virtual std::unique_ptr + virtual std::shared_ptr create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const = 0; }; diff --git a/serverManager/common/source/SessionServerApp.cpp b/serverManager/common/source/SessionServerApp.cpp index 75b6388d7..ac5cac45a 100644 --- a/serverManager/common/source/SessionServerApp.cpp +++ b/serverManager/common/source/SessionServerApp.cpp @@ -298,7 +298,6 @@ void SessionServerApp::kill() if (m_pid > 0) { m_linuxWrapper->kill(m_pid, SIGKILL); - m_pid = -1; } } diff --git a/serverManager/common/source/SessionServerAppFactory.cpp b/serverManager/common/source/SessionServerAppFactory.cpp index 24e940c52..63e01c305 100644 --- a/serverManager/common/source/SessionServerAppFactory.cpp +++ b/serverManager/common/source/SessionServerAppFactory.cpp @@ -38,12 +38,12 @@ SessionServerAppFactory::SessionServerAppFactory(const std::list &e { } -std::unique_ptr SessionServerAppFactory::create( +std::shared_ptr SessionServerAppFactory::create( const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const { - return std::make_unique(appName, initialState, appConfig, + return std::make_shared(appName, initialState, appConfig, m_linuxWrapperFactory->createLinuxWrapper(), firebolt::rialto::common::ITimerFactory::getFactory(), sessionServerAppManager, m_kEnvironmentVariables, m_kSessionServerPath, @@ -51,11 +51,11 @@ std::unique_ptr SessionServerAppFactory::create( m_kSocketGroup, std::move(namedSocket)); } -std::unique_ptr +std::shared_ptr SessionServerAppFactory::create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const { - return std::make_unique(m_linuxWrapperFactory->createLinuxWrapper(), + return std::make_shared(m_linuxWrapperFactory->createLinuxWrapper(), firebolt::rialto::common::ITimerFactory::getFactory(), sessionServerAppManager, m_kEnvironmentVariables, m_kSessionServerPath, m_kSessionServerStartupTimeout, m_kSocketPermissions, m_kSocketOwner, diff --git a/serverManager/common/source/SessionServerAppFactory.h b/serverManager/common/source/SessionServerAppFactory.h index 5802b7841..c8a49b82e 100644 --- a/serverManager/common/source/SessionServerAppFactory.h +++ b/serverManager/common/source/SessionServerAppFactory.h @@ -39,11 +39,11 @@ class SessionServerAppFactory : public ISessionServerAppFactory const std::string &socketGroup); ~SessionServerAppFactory() override = default; - std::unique_ptr + std::shared_ptr create(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const override; - std::unique_ptr + std::shared_ptr create(SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket) const override; diff --git a/serverManager/common/source/SessionServerAppManager.cpp b/serverManager/common/source/SessionServerAppManager.cpp index 7ce152a51..38bb178e9 100644 --- a/serverManager/common/source/SessionServerAppManager.cpp +++ b/serverManager/common/source/SessionServerAppManager.cpp @@ -24,11 +24,6 @@ #include #include -namespace -{ -const std::unique_ptr kInvalidSessionServer; -} // namespace - namespace rialto::servermanager::common { SessionServerAppManager::SessionServerAppManager( @@ -82,7 +77,7 @@ bool SessionServerAppManager::handleInitiateApplication(const std::string &appNa toString(state)); if (state != firebolt::rialto::common::SessionServerState::NOT_RUNNING && !getServerByAppName(appName)) { - const auto &preloadedServer{getPreloadedServer()}; + auto preloadedServer{getPreloadedServer()}; if (preloadedServer) { return configurePreloadedSessionServer(preloadedServer, appName, state, appConfig); @@ -153,10 +148,10 @@ std::string SessionServerAppManager::getAppConnectionInfo(const std::string &app m_eventThread->add( [&]() { - const auto &kSessionServer{getServerByAppName(appName)}; - if (kSessionServer) + auto sessionServer{getServerByAppName(appName)}; + if (sessionServer) { - return p.set_value(kSessionServer->getSessionManagementSocketName()); + return p.set_value(sessionServer->getSessionManagementSocketName()); } RIALTO_SERVER_MANAGER_LOG_ERROR("App: %s could not be found", appName.c_str()); return p.set_value(""); @@ -196,18 +191,22 @@ void SessionServerAppManager::handleRestartServer(int serverId) RIALTO_SERVER_MANAGER_LOG_DEBUG("Not restarting serverId: %d as server manager is shutting down", serverId); return; } - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("Unable to restart server, serverId: %d", serverId); return; } + if (m_healthcheckService) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } // First, get all needed information from current app - const std::string kAppName{kSessionServer->getAppName()}; - const firebolt::rialto::common::SessionServerState kState{kSessionServer->getExpectedState()}; - const firebolt::rialto::common::AppConfig kAppConfig{kSessionServer->getSessionManagementSocketName(), - kSessionServer->getClientDisplayName()}; - std::unique_ptr namedSocket{std::move(kSessionServer->releaseNamedSocket())}; + const std::string kAppName{sessionServer->getAppName()}; + const firebolt::rialto::common::SessionServerState kState{sessionServer->getExpectedState()}; + const firebolt::rialto::common::AppConfig kAppConfig{sessionServer->getSessionManagementSocketName(), + sessionServer->getClientDisplayName()}; + std::unique_ptr namedSocket{std::move(sessionServer->releaseNamedSocket())}; if (firebolt::rialto::common::SessionServerState::INACTIVE != kState && firebolt::rialto::common::SessionServerState::ACTIVE != kState) { @@ -216,8 +215,9 @@ void SessionServerAppManager::handleRestartServer(int serverId) } RIALTO_SERVER_MANAGER_LOG_DEBUG("Restarting server with id: %d", serverId); // Then kill the app - kSessionServer->kill(); + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); + sessionServer.reset(); // Finally, spawn the new app with old settings and set named socket if present auto app = m_sessionServerAppFactory->create(kAppName, kState, kAppConfig, *this, std::move(namedSocket)); @@ -235,7 +235,7 @@ void SessionServerAppManager::handleRestartServer(int serverId) } } -bool SessionServerAppManager::connectSessionServer(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::connectSessionServer(const std::shared_ptr &kSessionServer) { if (!kSessionServer) { @@ -260,7 +260,7 @@ bool SessionServerAppManager::connectSessionServer(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServer(const std::shared_ptr &kSessionServer) { if (!kSessionServer) { @@ -274,7 +274,7 @@ bool SessionServerAppManager::configureSessionServer(const std::unique_ptr &kSessionServer, +bool SessionServerAppManager::configurePreloadedSessionServer(const std::shared_ptr &kSessionServer, const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig) @@ -302,18 +302,22 @@ bool SessionServerAppManager::changeSessionServerState(const std::string &appNam { RIALTO_SERVER_MANAGER_LOG_INFO("RialtoServerManager requests to change state of %s to %s", appName.c_str(), toString(newState)); - const auto &kSessionServer{getServerByAppName(appName)}; - if (!kSessionServer) + auto sessionServer{getServerByAppName(appName)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_ERROR("Change state of %s to %s failed - session server not found.", appName.c_str(), toString(newState)); return false; } - kSessionServer->setExpectedState(newState); - if (!m_ipcController->performSetState(kSessionServer->getServerId(), newState)) + sessionServer->setExpectedState(newState); + if (m_healthcheckService && firebolt::rialto::common::SessionServerState::NOT_RUNNING == newState) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + if (!m_ipcController->performSetState(sessionServer->getServerId(), newState)) { RIALTO_SERVER_MANAGER_LOG_ERROR("Change state of %s to %s failed.", appName.c_str(), toString(newState)); - handleStateChangeFailure(kSessionServer, newState); + handleStateChangeFailure(sessionServer, newState); return false; } RIALTO_SERVER_MANAGER_LOG_INFO("Change state of %s to %s succeeded.", appName.c_str(), toString(newState)); @@ -324,46 +328,49 @@ void SessionServerAppManager::handleSessionServerStateChange(int serverId, firebolt::rialto::common::SessionServerState newState) { RIALTO_SERVER_MANAGER_LOG_INFO("SessionServer with id: %d changed state to %s", serverId, toString(newState)); - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("SessionServer with id: %d not found", serverId); return; } - std::string appName{kSessionServer->getAppName()}; + std::string appName{sessionServer->getAppName()}; if (!appName.empty() && m_stateObserver) // empty app name is when SessionServer is preloaded { m_stateObserver->stateChanged(appName, newState); } if (firebolt::rialto::common::SessionServerState::UNINITIALIZED == newState) { - kSessionServer->cancelStartupTimer(); - if (!kSessionServer->isPreloaded() && !configureSessionServer(kSessionServer)) + sessionServer->cancelStartupTimer(); + if (!sessionServer->isPreloaded() && !configureSessionServer(sessionServer)) { handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::ERROR); - kSessionServer->kill(); + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); } } - else if (newState == firebolt::rialto::common::SessionServerState::ERROR && kSessionServer->isPreloaded()) + else if (newState == firebolt::rialto::common::SessionServerState::ERROR && sessionServer->isPreloaded()) { m_ipcController->removeClient(serverId); - kSessionServer->kill(); + sessionServer->kill(); if (m_healthcheckService) { - m_healthcheckService->onServerRemoved(kSessionServer->getServerId()); + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + m_sessionServerApps.erase(sessionServer); + if (!m_isShuttingDown) + { + connectSessionServer(preloadSessionServer()); } - m_sessionServerApps.erase(kSessionServer); - connectSessionServer(preloadSessionServer()); } else if (newState == firebolt::rialto::common::SessionServerState::NOT_RUNNING) { m_ipcController->removeClient(serverId); if (m_healthcheckService) { - m_healthcheckService->onServerRemoved(kSessionServer->getServerId()); + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); } - m_sessionServerApps.erase(kSessionServer); + m_sessionServerApps.erase(sessionServer); } } @@ -394,7 +401,7 @@ void SessionServerAppManager::shutdownAllSessionServers() m_sessionServerApps.clear(); } -const std::unique_ptr & +std::shared_ptr SessionServerAppManager::launchSessionServer(const std::string &appName, const firebolt::rialto::common::SessionServerState &kInitialState, const firebolt::rialto::common::AppConfig &appConfig) @@ -404,31 +411,29 @@ SessionServerAppManager::launchSessionServer(const std::string &appName, m_namedSocketFactory.createNamedSocket()); if (app->launch()) { - auto result = m_sessionServerApps.emplace(std::move(app)); - if (result.second) + if (m_sessionServerApps.emplace(app).second) { - return *result.first; + return app; } } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::preloadSessionServer() +std::shared_ptr SessionServerAppManager::preloadSessionServer() { RIALTO_SERVER_MANAGER_LOG_INFO("Preloading new Rialto Session Server"); auto app = m_sessionServerAppFactory->create(*this, m_namedSocketFactory.createNamedSocket()); if (app->launch()) { - auto result = m_sessionServerApps.emplace(std::move(app)); - if (result.second) + if (m_sessionServerApps.emplace(app).second) { - return *result.first; + return app; } } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getPreloadedServer() const +std::shared_ptr SessionServerAppManager::getPreloadedServer() const { auto iter = std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [](const auto &srv) { return srv->isPreloaded() && srv->isConnected(); }); @@ -436,10 +441,10 @@ const std::unique_ptr &SessionServerAppManager::getPreloadedS { return *iter; } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getServerByAppName(const std::string &appName) const +std::shared_ptr SessionServerAppManager::getServerByAppName(const std::string &appName) const { auto iter{std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [&](const auto &srv) { return srv->getAppName() == appName; })}; @@ -447,10 +452,10 @@ const std::unique_ptr &SessionServerAppManager::getServerByAp { return *iter; } - return kInvalidSessionServer; + return nullptr; } -const std::unique_ptr &SessionServerAppManager::getServerById(int serverId) const +std::shared_ptr SessionServerAppManager::getServerById(int serverId) const { auto iter{std::find_if(m_sessionServerApps.begin(), m_sessionServerApps.end(), [&](const auto &srv) { return srv->getServerId() == serverId; })}; @@ -458,10 +463,10 @@ const std::unique_ptr &SessionServerAppManager::getServerById { return *iter; } - return kInvalidSessionServer; + return nullptr; } -void SessionServerAppManager::handleStateChangeFailure(const std::unique_ptr &kSessionServer, +void SessionServerAppManager::handleStateChangeFailure(const std::shared_ptr &kSessionServer, const firebolt::rialto::common::SessionServerState &state) { if (state == firebolt::rialto::common::SessionServerState::NOT_RUNNING) @@ -477,7 +482,7 @@ void SessionServerAppManager::handleStateChangeFailure(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServerWithSocketName(const std::shared_ptr &kSessionServer) { RIALTO_SERVER_MANAGER_LOG_DEBUG("Configuring Session Server using socket name"); const auto kInitialState{kSessionServer->getInitialState()}; @@ -486,7 +491,7 @@ bool SessionServerAppManager::configureSessionServerWithSocketName(const std::un const auto kSocketPermissions{kSessionServer->getSessionManagementSocketPermissions()}; const auto kSocketOwner{kSessionServer->getSessionManagementSocketOwner()}; const auto kSocketGroup{kSessionServer->getSessionManagementSocketGroup()}; - const auto kAppName{kSessionServer->getAppName()}; + const auto &kAppName{kSessionServer->getAppName()}; const firebolt::rialto::common::MaxResourceCapabilitites kMaxResource{kSessionServer->getMaxPlaybackSessions(), kSessionServer->getMaxWebAudioPlayers()}; @@ -502,13 +507,13 @@ bool SessionServerAppManager::configureSessionServerWithSocketName(const std::un return true; } -bool SessionServerAppManager::configureSessionServerWithSocketFd(const std::unique_ptr &kSessionServer) +bool SessionServerAppManager::configureSessionServerWithSocketFd(const std::shared_ptr &kSessionServer) { RIALTO_SERVER_MANAGER_LOG_DEBUG("Configuring Session Server using socket fd"); const auto kInitialState{kSessionServer->getInitialState()}; const auto kSocketFd{kSessionServer->getSessionManagementSocketFd()}; const auto kClientDisplayName{kSessionServer->getClientDisplayName()}; - const auto kAppName{kSessionServer->getAppName()}; + const auto &kAppName{kSessionServer->getAppName()}; const firebolt::rialto::common::MaxResourceCapabilitites kMaxResource{kSessionServer->getMaxPlaybackSessions(), kSessionServer->getMaxWebAudioPlayers()}; @@ -530,19 +535,23 @@ void SessionServerAppManager::onServerStartupTimeout(int serverId) void SessionServerAppManager::handleServerStartupTimeout(int serverId) { - const auto &kSessionServer{getServerById(serverId)}; - if (!kSessionServer) + auto sessionServer{getServerById(serverId)}; + if (!sessionServer) { RIALTO_SERVER_MANAGER_LOG_WARN("Unable to handle startup timeout for serverId: %d", serverId); return; } - const bool isPreloaded{kSessionServer->isPreloaded()}; + const bool isPreloaded{sessionServer->isPreloaded()}; RIALTO_SERVER_MANAGER_LOG_WARN("Killing: %d", serverId); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::ERROR); if (!isPreloaded) { - kSessionServer->kill(); + if (m_healthcheckService) + { + m_healthcheckService->onServerRemoved(sessionServer->getServerId()); + } + sessionServer->kill(); handleSessionServerStateChange(serverId, firebolt::rialto::common::SessionServerState::NOT_RUNNING); } } diff --git a/serverManager/common/source/SessionServerAppManager.h b/serverManager/common/source/SessionServerAppManager.h index b1d684348..e16f26f70 100644 --- a/serverManager/common/source/SessionServerAppManager.h +++ b/serverManager/common/source/SessionServerAppManager.h @@ -64,9 +64,9 @@ class SessionServerAppManager : public ISessionServerAppManager void onServerStartupTimeout(int serverId) override; private: - bool connectSessionServer(const std::unique_ptr &sessionServer); - bool configureSessionServer(const std::unique_ptr &sessionServer); - bool configurePreloadedSessionServer(const std::unique_ptr &sessionServer, + bool connectSessionServer(const std::shared_ptr &sessionServer); + bool configureSessionServer(const std::shared_ptr &sessionServer); + bool configurePreloadedSessionServer(const std::shared_ptr &sessionServer, const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig); @@ -75,26 +75,26 @@ class SessionServerAppManager : public ISessionServerAppManager void handleSessionServerStateChange(int serverId, firebolt::rialto::common::SessionServerState newState); void handleAck(int serverId, int pingId, bool success); void shutdownAllSessionServers(); - const std::unique_ptr & + std::shared_ptr launchSessionServer(const std::string &appName, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig); - void handleStateChangeFailure(const std::unique_ptr &sessionServer, + void handleStateChangeFailure(const std::shared_ptr &sessionServer, const firebolt::rialto::common::SessionServerState &state); - const std::unique_ptr &preloadSessionServer(); - const std::unique_ptr &getPreloadedServer() const; - const std::unique_ptr &getServerByAppName(const std::string &appName) const; - const std::unique_ptr &getServerById(int serverId) const; + std::shared_ptr preloadSessionServer(); + std::shared_ptr getPreloadedServer() const; + std::shared_ptr getServerByAppName(const std::string &appName) const; + std::shared_ptr getServerById(int serverId) const; bool handleInitiateApplication(const std::string &appName, const firebolt::rialto::common::SessionServerState &state, const firebolt::rialto::common::AppConfig &appConfig); void handleRestartServer(int serverId); - bool configureSessionServerWithSocketName(const std::unique_ptr &kSessionServer); - bool configureSessionServerWithSocketFd(const std::unique_ptr &kSessionServer); + bool configureSessionServerWithSocketName(const std::shared_ptr &kSessionServer); + bool configureSessionServerWithSocketFd(const std::shared_ptr &kSessionServer); void handleServerStartupTimeout(int serverId); private: std::unique_ptr &m_ipcController; std::unique_ptr m_eventThread; - std::set> m_sessionServerApps; + std::set> m_sessionServerApps; std::unique_ptr m_sessionServerAppFactory; std::shared_ptr m_stateObserver; std::unique_ptr m_healthcheckService; diff --git a/serverManager/ipc/source/Client.cpp b/serverManager/ipc/source/Client.cpp index 2690093fc..c11c0693c 100644 --- a/serverManager/ipc/source/Client.cpp +++ b/serverManager/ipc/source/Client.cpp @@ -138,6 +138,15 @@ Client::Client(std::unique_ptr &sessionServerA Client::~Client() { RIALTO_SERVER_MANAGER_LOG_INFO("Client for serverId: %d is destructed", m_serverId); + m_isShuttingDown = true; + if (m_ipcLoop && m_ipcLoop->channel()) + { + for (const auto &tag : m_eventTags) + { + m_ipcLoop->channel()->unsubscribe(tag); + } + m_eventTags.clear(); + } m_serviceStub.reset(); m_ipcLoop.reset(); } @@ -151,9 +160,18 @@ bool Client::connect() return false; } m_serviceStub = std::make_unique<::rialto::ServerManagerModule_Stub>(m_ipcLoop->channel()); - m_ipcLoop->channel()->subscribe( - std::bind(&Client::onStateChangedEvent, this, std::placeholders::_1)); - m_ipcLoop->channel()->subscribe(std::bind(&Client::onAckEvent, this, std::placeholders::_1)); + int eventTag{m_ipcLoop->channel()->subscribe( + std::bind(&Client::onStateChangedEvent, this, std::placeholders::_1))}; + if (eventTag >= 0) + { + m_eventTags.push_back(eventTag); + } + eventTag = + m_ipcLoop->channel()->subscribe(std::bind(&Client::onAckEvent, this, std::placeholders::_1)); + if (eventTag >= 0) + { + m_eventTags.push_back(eventTag); + } return true; } @@ -308,6 +326,12 @@ bool Client::setLogLevels(const service::LoggingLevels &logLevels) const void Client::onDisconnected() const { + if (!m_sessionServerAppManager || m_isShuttingDown) + { + RIALTO_SERVER_MANAGER_LOG_DEBUG("Connection to serverId: %d broken, but server manager is shutting down", + m_serverId); + return; + } RIALTO_SERVER_MANAGER_LOG_WARN("Connection to serverId: %d broken, server probably crashed. Starting recovery", m_serverId); m_sessionServerAppManager->restartServer(m_serverId); @@ -316,7 +340,7 @@ void Client::onDisconnected() const void Client::onStateChangedEvent(const std::shared_ptr &event) const { RIALTO_SERVER_MANAGER_LOG_DEBUG("StateChangedEvent received for serverId: %d", m_serverId); - if (!m_sessionServerAppManager || !event) + if (!m_sessionServerAppManager || !event || m_isShuttingDown) { RIALTO_SERVER_MANAGER_LOG_WARN("Problem during StateChangedEvent processing"); return; @@ -327,7 +351,7 @@ void Client::onStateChangedEvent(const std::shared_ptr &event) const { RIALTO_SERVER_MANAGER_LOG_DEBUG("AckEvent received for serverId: %d", m_serverId); - if (!m_sessionServerAppManager || !event) + if (!m_sessionServerAppManager || !event || m_isShuttingDown) { RIALTO_SERVER_MANAGER_LOG_WARN("Problem during AckEvent processing"); return; diff --git a/serverManager/ipc/source/Client.h b/serverManager/ipc/source/Client.h index f97c87f02..0eb84aa6f 100644 --- a/serverManager/ipc/source/Client.h +++ b/serverManager/ipc/source/Client.h @@ -26,6 +26,7 @@ #include "LoggingLevels.h" #include #include +#include namespace rialto { @@ -69,9 +70,11 @@ class Client int m_serverId; std::unique_ptr &m_sessionServerAppManager; int m_socket; + bool m_isShuttingDown{false}; std::shared_ptr<::firebolt::rialto::ipc::IChannel> m_channel; std::shared_ptr m_ipcLoop; std::unique_ptr<::rialto::ServerManagerModule_Stub> m_serviceStub; + std::vector m_eventTags; }; } // namespace rialto::servermanager::ipc diff --git a/serverManager/serverManagerSim/HttpRequest.cpp b/serverManager/serverManagerSim/HttpRequest.cpp index ba2bfb50c..154cff654 100644 --- a/serverManager/serverManagerSim/HttpRequest.cpp +++ b/serverManager/serverManagerSim/HttpRequest.cpp @@ -19,6 +19,7 @@ #include "HttpRequest.h" #include +#include namespace { @@ -28,16 +29,16 @@ std::vector splitUri(std::string uri) size_t pos = 0; while ((pos = uri.find("/")) != std::string::npos) { - const std::string token = uri.substr(0, pos); + std::string token = uri.substr(0, pos); if (!token.empty()) { - result.push_back(token); + result.push_back(std::move(token)); } uri.erase(0, pos + 1); } if (!uri.empty()) { - result.push_back(uri); + result.push_back(std::move(uri)); } return result; } diff --git a/serverManager/serverManagerSim/RialtoServerManagerSim.cpp b/serverManager/serverManagerSim/RialtoServerManagerSim.cpp index bb57f8822..f2b0722df 100644 --- a/serverManager/serverManagerSim/RialtoServerManagerSim.cpp +++ b/serverManager/serverManagerSim/RialtoServerManagerSim.cpp @@ -91,43 +91,51 @@ catch (const std::exception &e) int main(int argc, char *argv[]) { - fprintf(stderr, "===========================================================================\n"); - fprintf(stderr, "== RIALTO SERVER MANAGER SIM ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== Test application is a Http Server running on localhost:9008 ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== To set state, send POST HttpRequest /SetState/AppName/NewState ==\n"); - fprintf(stderr, "== Available states: Inactive, Active, NotRunning, Error ==\n"); - fprintf(stderr, "== For example: ==\n"); - fprintf(stderr, "== curl -X POST -d \"\" :9008/SetState/YouTube/NotRunning ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== Custom socket name can be set in POST data. Available values are: ==\n"); - fprintf(stderr, "== - Empty string (socket name will be automatically generated) ==\n"); - fprintf(stderr, "== - Full socket path, e.g. POST -d \"/var/customsocket\" ==\n"); - fprintf(stderr, "== - Socket name, e.g. POST -d \"sock\" will create /tmp/sock socket ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== To get current state, send GET HttpRequest: /GetState/AppName ==\n"); - fprintf(stderr, "== For example: ==\n"); - fprintf(stderr, "== curl -X GET :9008/GetState/YouTube ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== To get application info, send GET HttpRequest: /GetAppInfo/AppName ==\n"); - fprintf(stderr, "== For example: ==\n"); - fprintf(stderr, "== curl -X GET :9008/GetAppInfo/YouTube ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== To set log levels, send POST HttpRequest: /SetLog// ==\n"); - fprintf(stderr, "== For example: ==\n"); - fprintf(stderr, "== curl -X POST -d \"\" :9008/SetLog/client/error ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "== To shutdown Test Service, send POST HttpRequest: /Quit ==\n"); - fprintf(stderr, "== For example: ==\n"); - fprintf(stderr, "== curl -X POST -d \"\" :9008/Quit ==\n"); - fprintf(stderr, "== ==\n"); - fprintf(stderr, "===========================================================================\n"); + try + { + fprintf(stderr, "===========================================================================\n"); + fprintf(stderr, "== RIALTO SERVER MANAGER SIM ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== Test application is a Http Server running on localhost:9008 ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== To set state, send POST HttpRequest /SetState/AppName/NewState ==\n"); + fprintf(stderr, "== Available states: Inactive, Active, NotRunning, Error ==\n"); + fprintf(stderr, "== For example: ==\n"); + fprintf(stderr, "== curl -X POST -d \"\" :9008/SetState/YouTube/NotRunning ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== Custom socket name can be set in POST data. Available values are: ==\n"); + fprintf(stderr, "== - Empty string (socket name will be automatically generated) ==\n"); + fprintf(stderr, "== - Full socket path, e.g. POST -d \"/var/customsocket\" ==\n"); + fprintf(stderr, "== - Socket name, e.g. POST -d \"sock\" will create /tmp/sock socket ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== To get current state, send GET HttpRequest: /GetState/AppName ==\n"); + fprintf(stderr, "== For example: ==\n"); + fprintf(stderr, "== curl -X GET :9008/GetState/YouTube ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== To get application info, send GET HttpRequest: /GetAppInfo/AppName ==\n"); + fprintf(stderr, "== For example: ==\n"); + fprintf(stderr, "== curl -X GET :9008/GetAppInfo/YouTube ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== To set log levels, send POST HttpRequest: /SetLog// ==\n"); + fprintf(stderr, "== For example: ==\n"); + fprintf(stderr, "== curl -X POST -d \"\" :9008/SetLog/client/error ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "== To shutdown Test Service, send POST HttpRequest: /Quit ==\n"); + fprintf(stderr, "== For example: ==\n"); + fprintf(stderr, "== curl -X POST -d \"\" :9008/Quit ==\n"); + fprintf(stderr, "== ==\n"); + fprintf(stderr, "===========================================================================\n"); - firebolt::rialto::common::ServerManagerConfig config{getEnvironmentVariables(), getNumberOfPreloadedServers(), - getSessionServerPath(), getStartupTimeout()}; - rialto::servermanager::TestService service{config}; - service.run(); + firebolt::rialto::common::ServerManagerConfig config{getEnvironmentVariables(), getNumberOfPreloadedServers(), + getSessionServerPath(), getStartupTimeout()}; + rialto::servermanager::TestService service{config}; + service.run(); - return 0; + return 0; + } + catch (const std::exception &e) + { + fprintf(stderr, "Fatal error: %s\n", e.what()); + return 1; + } } diff --git a/serverManager/service/include/ConfigReader.h b/serverManager/service/include/ConfigReader.h index 6c2ba8735..f2810c3f8 100644 --- a/serverManager/service/include/ConfigReader.h +++ b/serverManager/service/include/ConfigReader.h @@ -34,8 +34,8 @@ namespace rialto::servermanager::service class ConfigReader : public IConfigReader { public: - ConfigReader(std::shared_ptr jsonWrapper, - std::shared_ptr fileReader); + ConfigReader(const std::shared_ptr &jsonWrapper, + const std::shared_ptr &fileReader); bool read() override; std::list getEnvironmentVariables() override; @@ -51,23 +51,23 @@ class ConfigReader : public IConfigReader std::optional getNumOfPingsBeforeRecovery() override; private: - void parseEnvironmentVariables(std::shared_ptr root); - void parseExtraEnvVariables(std::shared_ptr root); - void parseSessionServerPath(std::shared_ptr root); - void parseSessionServerStartupTimeout(std::shared_ptr root); - void parseHealthcheckInterval(std::shared_ptr root); - void parseSocketPermissions(std::shared_ptr root); - void parseSocketOwner(std::shared_ptr root); - void parseSocketGroup(std::shared_ptr root); - void parseNumOfPreloadedServers(std::shared_ptr root); - void parseLogLevel(std::shared_ptr root); - void parseNumOfPingsBeforeRecovery(std::shared_ptr root); + void parseEnvironmentVariables(const std::shared_ptr &root); + void parseExtraEnvVariables(const std::shared_ptr &root); + void parseSessionServerPath(const std::shared_ptr &root); + void parseSessionServerStartupTimeout(const std::shared_ptr &root); + void parseHealthcheckInterval(const std::shared_ptr &root); + void parseSocketPermissions(const std::shared_ptr &root); + void parseSocketOwner(const std::shared_ptr &root); + void parseSocketGroup(const std::shared_ptr &root); + void parseNumOfPreloadedServers(const std::shared_ptr &root); + void parseLogLevel(const std::shared_ptr &root); + void parseNumOfPingsBeforeRecovery(const std::shared_ptr &root); - std::list getListOfStrings(std::shared_ptr root, + std::list getListOfStrings(const std::shared_ptr &root, const std::string &valueName) const; - std::optional getString(std::shared_ptr root, + std::optional getString(const std::shared_ptr &root, const std::string &valueName) const; - std::optional getUInt(std::shared_ptr root, + std::optional getUInt(const std::shared_ptr &root, const std::string &valueName) const; std::shared_ptr m_jsonWrapper; diff --git a/serverManager/service/source/ConfigReader.cpp b/serverManager/service/source/ConfigReader.cpp index 551935997..200f94945 100644 --- a/serverManager/service/source/ConfigReader.cpp +++ b/serverManager/service/source/ConfigReader.cpp @@ -24,8 +24,8 @@ namespace rialto::servermanager::service { -ConfigReader::ConfigReader(std::shared_ptr jsonWrapper, - std::shared_ptr fileReader) +ConfigReader::ConfigReader(const std::shared_ptr &jsonWrapper, + const std::shared_ptr &fileReader) : m_jsonWrapper(jsonWrapper), m_fileReader(fileReader) { } @@ -60,22 +60,22 @@ bool ConfigReader::read() return true; } -void ConfigReader::parseEnvironmentVariables(std::shared_ptr root) +void ConfigReader::parseEnvironmentVariables(const std::shared_ptr &root) { m_envVars = getListOfStrings(root, "environmentVariables"); } -void ConfigReader::parseExtraEnvVariables(std::shared_ptr root) +void ConfigReader::parseExtraEnvVariables(const std::shared_ptr &root) { m_extraEnvVars = getListOfStrings(root, "extraEnvVariables"); } -void ConfigReader::parseSessionServerPath(std::shared_ptr root) +void ConfigReader::parseSessionServerPath(const std::shared_ptr &root) { m_sessionServerPath = getString(root, "sessionServerPath"); } -void ConfigReader::parseSessionServerStartupTimeout(std::shared_ptr root) +void ConfigReader::parseSessionServerStartupTimeout(const std::shared_ptr &root) { auto timeout{getUInt(root, "startupTimeoutMs")}; if (timeout.has_value()) @@ -84,7 +84,7 @@ void ConfigReader::parseSessionServerStartupTimeout(std::shared_ptr root) +void ConfigReader::parseHealthcheckInterval(const std::shared_ptr &root) { auto interval{getUInt(root, "healthcheckIntervalInSeconds")}; if (interval.has_value()) @@ -93,7 +93,7 @@ void ConfigReader::parseHealthcheckInterval(std::shared_ptr root) +void ConfigReader::parseSocketPermissions(const std::shared_ptr &root) { auto permissions{getUInt(root, "socketPermissions")}; if (permissions.has_value()) @@ -106,22 +106,22 @@ void ConfigReader::parseSocketPermissions(std::shared_ptr root) +void ConfigReader::parseSocketOwner(const std::shared_ptr &root) { m_socketOwner = getString(root, "socketOwner"); } -void ConfigReader::parseSocketGroup(std::shared_ptr root) +void ConfigReader::parseSocketGroup(const std::shared_ptr &root) { m_socketGroup = getString(root, "socketGroup"); } -void ConfigReader::parseNumOfPreloadedServers(std::shared_ptr root) +void ConfigReader::parseNumOfPreloadedServers(const std::shared_ptr &root) { m_numOfPreloadedServers = getUInt(root, "numOfPreloadedServers"); } -void ConfigReader::parseLogLevel(std::shared_ptr root) +void ConfigReader::parseLogLevel(const std::shared_ptr &root) { std::optional loggingLevel{getUInt(root, "logLevel")}; @@ -157,7 +157,7 @@ void ConfigReader::parseLogLevel(std::shared_ptr root) +void ConfigReader::parseNumOfPingsBeforeRecovery(const std::shared_ptr &root) { m_numOfPingsBeforeRecovery = getUInt(root, "numOfPingsBeforeRecovery"); } @@ -217,8 +217,9 @@ std::optional ConfigReader::getNumOfPingsBeforeRecovery() return m_numOfPingsBeforeRecovery; } -std::list ConfigReader::getListOfStrings(std::shared_ptr root, - const std::string &valueName) const +std::list +ConfigReader::getListOfStrings(const std::shared_ptr &root, + const std::string &valueName) const { std::list result; if (root->isMember(valueName) && root->at(valueName)->isArray()) @@ -236,8 +237,9 @@ std::list ConfigReader::getListOfStrings(std::shared_ptr ConfigReader::getString(std::shared_ptr root, - const std::string &valueName) const +std::optional +ConfigReader::getString(const std::shared_ptr &root, + const std::string &valueName) const { if (root->isMember(valueName) && root->at(valueName)->isString()) { @@ -246,7 +248,7 @@ std::optional ConfigReader::getString(std::shared_ptr ConfigReader::getUInt(std::shared_ptr root, +std::optional ConfigReader::getUInt(const std::shared_ptr &root, const std::string &valueName) const { if (root->isMember(valueName) && root->at(valueName)->isUInt()) diff --git a/serverManager/service/source/ServerManagerService.cpp b/serverManager/service/source/ServerManagerService.cpp index 09fa6126e..9edd1576f 100644 --- a/serverManager/service/source/ServerManagerService.cpp +++ b/serverManager/service/source/ServerManagerService.cpp @@ -59,7 +59,7 @@ ServerManagerService::ServerManagerService(std::unique_ptr &&co ServerManagerService::~ServerManagerService() { - RIALTO_SERVER_MANAGER_LOG_INFO("RialtoServerManager is closing..."); + RIALTO_SERVER_MANAGER_LOG_MIL("RialtoServerManager is closing..."); if (m_logHandler) { firebolt::rialto::logging::setLogHandler(RIALTO_COMPONENT_SERVER_MANAGER, 0, false); diff --git a/stubs/opencdm/open_cdm.cpp b/stubs/opencdm/open_cdm.cpp index e279f6a8e..910dd04de 100644 --- a/stubs/opencdm/open_cdm.cpp +++ b/stubs/opencdm/open_cdm.cpp @@ -115,4 +115,13 @@ extern "C" { return ERROR_NONE; } + + OpenCDMError opencdm_system_supported_robustness(struct OpenCDMSystem *system, char ***robustness, uint16_t *count) + { + if (robustness) + *robustness = nullptr; + if (count) + *count = 0; + return ERROR_NONE; + } } diff --git a/tests/common/externalLibraryMocks/GlibWrapperMock.h b/tests/common/externalLibraryMocks/GlibWrapperMock.h index 965c8da5c..9650a3b74 100644 --- a/tests/common/externalLibraryMocks/GlibWrapperMock.h +++ b/tests/common/externalLibraryMocks/GlibWrapperMock.h @@ -32,6 +32,7 @@ class GlibWrapperMock : public IGlibWrapper virtual ~GlibWrapperMock() = default; MOCK_METHOD(gpointer, gTypeClassRef, (GType type), (override)); + MOCK_METHOD(void, gTypeClassUnref, (gpointer g_class), (override)); MOCK_METHOD(GType, gTypeFromName, (const gchar *name), (override)); MOCK_METHOD(GFlagsValue *, gFlagsGetValueByNick, (GFlagsClass * flags_class, const gchar *nick), (override)); MOCK_METHOD(void, gObjectUnref, (gpointer object), (override)); @@ -138,6 +139,9 @@ class GlibWrapperMock : public IGlibWrapper MOCK_METHOD(void, gValueUnset, (GValue * value), (const, override)); MOCK_METHOD(GError *, gErrorNewLiteral, (GQuark domain, gint code, const gchar *message), (const, override)); MOCK_METHOD(GValue *, gValueInit, (GValue * value, GType type), (const, override)); + MOCK_METHOD(void, gThreadPoolStopUnusedThreads, (), (const, override)); + MOCK_METHOD(void, gThreadPoolSetMaxUnusedThreads, (gint maxThreads), (const, override)); + MOCK_METHOD(void, gThreadPoolSetMaxIdleTime, (guint interval), (const, override)); }; } // namespace firebolt::rialto::wrappers diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index 401b8e2f7..718b9ecf2 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -42,6 +42,7 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(gboolean, gstElementRegister, (GstPlugin * plugin, const gchar *name, guint rank, GType type), (override)); MOCK_METHOD(GstElement *, gstElementFactoryMake, (const gchar *factoryname, const gchar *name), (override)); + MOCK_METHOD(GType, gstElementFactoryGetElementType, (GstElementFactory * factory), (override)); MOCK_METHOD(gpointer, gstObjectRef, (gpointer object), (override)); MOCK_METHOD(GstElement *, gstBinGetByName, (GstBin * bin, const gchar *name), (override)); MOCK_METHOD(GstBus *, gstPipelineGetBus, (GstPipeline * pipeline), (override)); @@ -87,6 +88,7 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(void, gstBusSetSyncHandler, (GstBus *, GstBusSyncHandler, gpointer, GDestroyNotify), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcEndOfStream, (GstAppSrc *), (override)); MOCK_METHOD(gboolean, gstElementQueryPosition, (GstElement *, GstFormat, gint64 *), (override)); + MOCK_METHOD(gboolean, gstElementQueryDuration, (GstElement *, GstFormat, gint64 *), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcPushBuffer, (GstAppSrc *, GstBuffer *), (override)); MOCK_METHOD(GstBuffer *, gstBufferNew, (), (override)); MOCK_METHOD(GstBuffer *, gstBufferNewAllocate, (GstAllocator *, gsize, GstAllocationParams *), (override)); @@ -193,6 +195,7 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(gboolean, gstElementPostMessage, (GstElement * element, GstMessage *message), (const, override)); MOCK_METHOD(GstMessage *, gstMessageNewWarning, (GstObject * src, GError *error, const gchar *debug), (const, override)); + MOCK_METHOD(GstMessage *, gstMessageNewApplication, (GstObject * src, GstStructure *structure), (const, override)); MOCK_METHOD(void, gstMessageParseWarning, (GstMessage * message, GError **gerror, gchar **debug), (const, override)); MOCK_METHOD(GstStructure *, gstCapsGetStructure, (const GstCaps *caps, guint index), (const, override)); MOCK_METHOD(gboolean, gstObjectSetName, (GstObject * object, const gchar *name), (const, override)); @@ -220,6 +223,18 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(gboolean, gstIsBaseParse, (GstElement * element), (const, override)); MOCK_METHOD(void, gstBaseParseSetPtsInterpolation, (GstBaseParse * parse, gboolean ptsInterpolate), (const, override)); + MOCK_METHOD(GstStateChangeReturn, gstElementGetState, + (GstElement * element, GstState *state, GstState *pending, GstClockTime timeout), (override)); + MOCK_METHOD(GstPad *, gstPadGetPeer, (GstPad * pad), (override)); + MOCK_METHOD(gboolean, gstPadUnlink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(GstPadLinkReturn, gstPadLink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(gboolean, gstBinRemove, (GstBin * bin, GstElement *element), (override)); + MOCK_METHOD(GstObject *, gstPadGetParent, (GstPad * pad), (override)); + MOCK_METHOD(gulong, gstPadAddProbe, + (GstPad * pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData), + (override)); + MOCK_METHOD(void, gstPadRemoveProbe, (GstPad * pad, gulong id), (override)); GstCaps *gstCapsNewSimple(const char *media_type, const char *fieldname, ...) const override { diff --git a/tests/common/externalLibraryMocks/OcdmSystemMock.h b/tests/common/externalLibraryMocks/OcdmSystemMock.h index b71d8add4..e01c0e3da 100644 --- a/tests/common/externalLibraryMocks/OcdmSystemMock.h +++ b/tests/common/externalLibraryMocks/OcdmSystemMock.h @@ -41,6 +41,7 @@ class OcdmSystemMock : public IOcdmSystem MOCK_METHOD(MediaKeyErrorStatus, getDrmTime, (uint64_t * time), (override)); MOCK_METHOD(std::unique_ptr, createSession, (IOcdmSessionClient * client), (override, const)); MOCK_METHOD(bool, supportsServerCertificate, (), (const, override)); + MOCK_METHOD(bool, getSupportedRobustnessLevels, (std::vector & robustnessLevels), (override)); MOCK_METHOD(MediaKeyErrorStatus, getMetricSystemData, (uint32_t & bufferLength, std::vector &buffer), (override)); }; diff --git a/tests/common/matchers/MediaKeysProtoRequestMatchers.h b/tests/common/matchers/MediaKeysProtoRequestMatchers.h index 4784bc0a3..dd43d7401 100644 --- a/tests/common/matchers/MediaKeysProtoRequestMatchers.h +++ b/tests/common/matchers/MediaKeysProtoRequestMatchers.h @@ -42,12 +42,11 @@ MATCHER_P(destroyMediaKeysRequestMatcher, mediaKeysHandle, "") return (kRequest->media_keys_handle() == mediaKeysHandle); } -MATCHER_P3(createKeySessionRequestMatcher, mediaKeysHandle, sessionType, isLdl, "") +MATCHER_P2(createKeySessionRequestMatcher, mediaKeysHandle, sessionType, "") { const ::firebolt::rialto::CreateKeySessionRequest *kRequest = dynamic_cast(arg); - return ((kRequest->media_keys_handle() == mediaKeysHandle) && (kRequest->session_type() == sessionType) && - (kRequest->is_ldl() == isLdl)); + return ((kRequest->media_keys_handle() == mediaKeysHandle) && (kRequest->session_type() == sessionType)); } MATCHER_P4(generateRequestRequestMatcher, mediaKeysHandle, keySessionId, initDataType, initData, "") @@ -198,6 +197,13 @@ MATCHER_P(isServerCertificateSupportedRequestMatcher, keySystem, "") return (kRequest->key_system() == keySystem); } +MATCHER_P(getSupportedRobustnessLevelsRequestMatcher, keySystem, "") +{ + const ::firebolt::rialto::GetSupportedRobustnessLevelsRequest *kRequest = + dynamic_cast(arg); + return (kRequest->key_system() == keySystem); +} + MATCHER_P2(releaseKeySessionRequestMatcher, mediaKeysHandle, keySessionId, "") { const ::firebolt::rialto::ReleaseKeySessionRequest *kRequest = diff --git a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h index 04c48ad78..f8186d4c0 100644 --- a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h +++ b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h @@ -37,11 +37,11 @@ MATCHER_P2(createSessionRequestMatcher, maxWidth, maxHeight, "") return ((kRequest->max_width() == maxWidth) && (kRequest->max_height() == maxHeight)); } -MATCHER_P4(loadRequestMatcher, sessionId, type, mimeType, url, "") +MATCHER_P5(loadRequestMatcher, sessionId, type, mimeType, url, isLive, "") { const ::firebolt::rialto::LoadRequest *kRequest = dynamic_cast(arg); return ((kRequest->session_id() == sessionId) && (kRequest->type() == type) && - (kRequest->mime_type() == mimeType) && (kRequest->url() == url)); + (kRequest->mime_type() == mimeType) && (kRequest->url() == url) && (kRequest->is_live() == isLive)); } MATCHER_P(playRequestMatcher, sessionId, "") @@ -352,6 +352,13 @@ MATCHER_P(getPositionRequestMatcher, sessionId, "") return ((kRequest->session_id() == sessionId)); } +MATCHER_P(getDurationRequestMatcher, sessionId, "") +{ + const ::firebolt::rialto::GetDurationRequest *kRequest = + dynamic_cast(arg); + return ((kRequest->session_id() == sessionId)); +} + MATCHER_P2(setImmediateOutputRequestMatcher, sessionId, sourceId, "") { const ::firebolt::rialto::SetImmediateOutputRequest *kRequest = @@ -366,6 +373,20 @@ MATCHER_P2(getImmediateOutputRequestMatcher, sessionId, sourceId, "") return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); } +MATCHER_P2(setReportDecodeErrorsRequestMatcher, sessionId, sourceId, "") +{ + const ::firebolt::rialto::SetReportDecodeErrorsRequest *kRequest = + dynamic_cast(arg); + return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); +} + +MATCHER_P2(getQueuedFramesRequestMatcher, sessionId, sourceId, "") +{ + const ::firebolt::rialto::GetQueuedFramesRequest *kRequest = + dynamic_cast(arg); + return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); +} + MATCHER_P2(getStatsRequestMatcher, sessionId, sourceId, "") { const ::firebolt::rialto::GetStatsRequest *kRequest = dynamic_cast(arg); diff --git a/tests/common/protoUtils/MediaKeysProtoUtils.h b/tests/common/protoUtils/MediaKeysProtoUtils.h index 56ca3e58d..7a576a6ad 100644 --- a/tests/common/protoUtils/MediaKeysProtoUtils.h +++ b/tests/common/protoUtils/MediaKeysProtoUtils.h @@ -84,6 +84,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::ProtoMediaKeyErrorStatus &err { return firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE; } + case firebolt::rialto::ProtoMediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } case firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL: { return firebolt::rialto::MediaKeyErrorStatus::FAIL; @@ -121,6 +125,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::MediaKeyErrorStatus &errorSta { return firebolt::rialto::ProtoMediaKeyErrorStatus::INVALID_STATE; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return firebolt::rialto::ProtoMediaKeyErrorStatus::OUTPUT_RESTRICTED; + } case firebolt::rialto::MediaKeyErrorStatus::FAIL: { return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; @@ -163,6 +171,21 @@ convertInitDataType(const firebolt::rialto::InitDataType &initDataType) } } +inline firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense +convertLimitedDurationLicense(const firebolt::rialto::LimitedDurationLicense &ldlState) +{ + switch (ldlState) + { + case firebolt::rialto::LimitedDurationLicense::ENABLED: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_ENABLED; + case firebolt::rialto::LimitedDurationLicense::DISABLED: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_DISABLED; + case firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED: + default: + return firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_NOT_SPECIFIED; + } +} + inline firebolt::rialto::KeyStatus convertKeyStatus(const firebolt::rialto::KeyStatusesChangedEvent_KeyStatus &keyStatus) { switch (keyStatus) diff --git a/tests/common/protoUtils/MediaPipelineProtoUtils.h b/tests/common/protoUtils/MediaPipelineProtoUtils.h index ecafe4472..3e6a89597 100644 --- a/tests/common/protoUtils/MediaPipelineProtoUtils.h +++ b/tests/common/protoUtils/MediaPipelineProtoUtils.h @@ -224,6 +224,10 @@ convertPlaybackError(const firebolt::rialto::PlaybackError &playbackError) { return firebolt::rialto::PlaybackErrorEvent_PlaybackError_DECRYPTION; } + case firebolt::rialto::PlaybackError::OUTPUT_PROTECTION: + { + return firebolt::rialto::PlaybackErrorEvent_PlaybackError_OUTPUT_PROTECTION; + } } return firebolt::rialto::PlaybackErrorEvent_PlaybackError_UNKNOWN; } diff --git a/tests/common/publicClientMocks/MediaPipelineClientMock.h b/tests/common/publicClientMocks/MediaPipelineClientMock.h index d390fb72b..d5c8b5c2c 100644 --- a/tests/common/publicClientMocks/MediaPipelineClientMock.h +++ b/tests/common/publicClientMocks/MediaPipelineClientMock.h @@ -46,6 +46,7 @@ class MediaPipelineClientMock : public IMediaPipelineClient MOCK_METHOD(void, notifyCancelNeedMediaData, (int32_t sourceId), (override)); MOCK_METHOD(void, notifyQos, (int32_t sourceId, const QosInfo &qosInfo), (override)); MOCK_METHOD(void, notifyBufferUnderflow, (int32_t sourceId), (override)); + MOCK_METHOD(void, notifyFirstFrameReceived, (int32_t sourceId), (override)); MOCK_METHOD(void, notifyPlaybackError, (int32_t sourceId, PlaybackError error), (override)); MOCK_METHOD(void, notifySourceFlushed, (int32_t sourceId), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (const PlaybackInfo &playbackInfo), (override)); diff --git a/tests/componenttests/client/CMakeLists.txt b/tests/componenttests/client/CMakeLists.txt index 35ac239f3..bfc933f06 100644 --- a/tests/componenttests/client/CMakeLists.txt +++ b/tests/componenttests/client/CMakeLists.txt @@ -44,6 +44,7 @@ add_gtests ( tests/mse/CreateMediaPipelineFailuresTest.cpp tests/mse/DualVideoPlaybackTest.cpp tests/mse/FlushTest.cpp + tests/mse/FirstFrameNotificationTest.cpp tests/mse/MediaPipelineCapabilitiesTest.cpp tests/mse/MuteTest.cpp tests/mse/PipelinePropertyTest.cpp diff --git a/tests/componenttests/client/mocks/MediaKeysCapabilitiesModuleMock.h b/tests/componenttests/client/mocks/MediaKeysCapabilitiesModuleMock.h index 78f3fdcd6..56a864c25 100644 --- a/tests/componenttests/client/mocks/MediaKeysCapabilitiesModuleMock.h +++ b/tests/componenttests/client/mocks/MediaKeysCapabilitiesModuleMock.h @@ -45,6 +45,10 @@ class MediaKeysCapabilitiesModuleMock : public ::firebolt::rialto::MediaKeysCapa (::google::protobuf::RpcController * controller, const ::firebolt::rialto::IsServerCertificateSupportedRequest *request, ::firebolt::rialto::IsServerCertificateSupportedResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getSupportedRobustnessLevels, + (::google::protobuf::RpcController * controller, + const ::firebolt::rialto::GetSupportedRobustnessLevelsRequest *request, + ::firebolt::rialto::GetSupportedRobustnessLevelsResponse *response, ::google::protobuf::Closure *done)); void defaultReturn(::google::protobuf::RpcController *controller, ::google::protobuf::Closure *done) { @@ -90,6 +94,17 @@ class MediaKeysCapabilitiesModuleMock : public ::firebolt::rialto::MediaKeysCapa return response; } + ::firebolt::rialto::GetSupportedRobustnessLevelsResponse + getSupportedRobustnessLevelsResponse(const std::vector &values) + { + firebolt::rialto::GetSupportedRobustnessLevelsResponse response; + for (const auto &value : values) + { + response.add_robustness_levels(value); + } + return response; + } + MediaKeysCapabilitiesModuleMock() {} virtual ~MediaKeysCapabilitiesModuleMock() = default; }; diff --git a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h index a6fc772c0..6988528f4 100644 --- a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h +++ b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h @@ -64,6 +64,9 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule MOCK_METHOD(void, getPosition, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getDuration, + (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done)); MOCK_METHOD(void, setImmediateOutput, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, @@ -72,6 +75,13 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, setReportDecodeErrors, + (::google::protobuf::RpcController * controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getQueuedFrames, + (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, ::google::protobuf::Closure *done)); MOCK_METHOD(void, getStats, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetStatsRequest *request, ::firebolt::rialto::GetStatsResponse *response, ::google::protobuf::Closure *done)); @@ -218,6 +228,13 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule return response; } + ::firebolt::rialto::GetDurationResponse getDurationResponse(const int64_t duration) + { + firebolt::rialto::GetDurationResponse response; + response.set_duration(duration); + return response; + } + ::firebolt::rialto::SetImmediateOutputResponse setImmediateOutputResponse() { firebolt::rialto::SetImmediateOutputResponse response; @@ -231,6 +248,19 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule return response; } + ::firebolt::rialto::SetReportDecodeErrorsResponse SetReportDecodeErrorsResponse() + { + firebolt::rialto::SetReportDecodeErrorsResponse response; + return response; + } + + ::firebolt::rialto::GetQueuedFramesResponse getQueuedFramesResponse(uint32_t queuedFramesResponse) + { + firebolt::rialto::GetQueuedFramesResponse response; + response.set_queued_frames(queuedFramesResponse); + return response; + } + ::firebolt::rialto::GetStatsResponse getStatsResponse(const uint64_t renderedFrames, const uint64_t droppedFrames) { firebolt::rialto::GetStatsResponse response; diff --git a/tests/componenttests/client/stubs/MediaPipelineModuleStub.cpp b/tests/componenttests/client/stubs/MediaPipelineModuleStub.cpp index c6b7115d0..435992758 100644 --- a/tests/componenttests/client/stubs/MediaPipelineModuleStub.cpp +++ b/tests/componenttests/client/stubs/MediaPipelineModuleStub.cpp @@ -105,6 +105,16 @@ void MediaPipelineModuleStub::notifyBufferUnderflowEvent(int sessionId, int32_t getClient()->sendEvent(event); } +void MediaPipelineModuleStub::notifyFirstFrameReceivedEvent(int sessionId, int32_t sourceId) +{ + waitForClientConnect(); + + auto event = std::make_shared(); + event->set_session_id(sessionId); + event->set_source_id(sourceId); + getClient()->sendEvent(event); +} + void MediaPipelineModuleStub::notifyPlaybackErrorEvent(int sessionId, int32_t sourceId, PlaybackError error) { waitForClientConnect(); diff --git a/tests/componenttests/client/stubs/MediaPipelineModuleStub.h b/tests/componenttests/client/stubs/MediaPipelineModuleStub.h index a939070e8..3cd343254 100644 --- a/tests/componenttests/client/stubs/MediaPipelineModuleStub.h +++ b/tests/componenttests/client/stubs/MediaPipelineModuleStub.h @@ -40,6 +40,7 @@ class MediaPipelineModuleStub void notifyPositionChangeEvent(int sessionId, int64_t position); void notifyQosEvent(int sessionId, int32_t sourceId, const ::firebolt::rialto::QosInfo &qosInfo); void notifyBufferUnderflowEvent(int sessionId, int32_t sourceId); + void notifyFirstFrameReceivedEvent(int sessionId, int32_t sourceId); void notifyPlaybackErrorEvent(int sessionId, int32_t sourceId, PlaybackError error); void notifySourceFlushed(int sessionId, int32_t sourceId); void notifyPlaybackInfo(int sessionId, const firebolt::rialto::PlaybackInfo &playbackInfo); diff --git a/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp b/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp index 59341e47a..3b4dd00ff 100644 --- a/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaKeysTestMethods.cpp @@ -32,7 +32,6 @@ const std::string kKeySystemWidevine{"com.widevine.alpha"}; const std::string kKeySystemPlayready{"com.netflix.playready"}; constexpr int32_t kMediaKeysHandle{1}; constexpr firebolt::rialto::KeySessionType kSessionTypeTemp{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsNotLdl{false}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusOk{firebolt::rialto::MediaKeyErrorStatus::OK}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusFailed{firebolt::rialto::MediaKeyErrorStatus::FAIL}; constexpr firebolt::rialto::MediaKeyErrorStatus kStatusInterfaceNotImplemented{ @@ -63,6 +62,7 @@ const std::vector kKeySystems(firebolt::rialto::server::kSupportedK firebolt::rialto::server::kSupportedKeySystems.end()); const std::string kVersion{"123"}; const std::vector kBuffer{0x1, 0x2, 0x3, 0x4}; +const std::vector kRobustnessLevels{"SW_SECURE_CRYPTO", "SW_SECURE_DECODE", "HW_SECURE_CRYPTO"}; } // namespace namespace firebolt::rialto::client::ct @@ -108,8 +108,7 @@ void MediaKeysTestMethods::shouldCreateKeySession() { EXPECT_CALL(*m_mediaKeysModuleMock, createKeySession(_, - createKeySessionRequestMatcher(kMediaKeysHandle, - convertKeySessionType(kSessionTypeTemp), kIsNotLdl), + createKeySessionRequestMatcher(kMediaKeysHandle, convertKeySessionType(kSessionTypeTemp)), _, _)) .WillOnce(DoAll(SetArgPointee<2>(m_mediaKeysModuleMock->createKeySessionResponse(kStatusOk, kKeySessionId)), WithArgs<0, 3>(Invoke(&(*m_mediaKeysModuleMock), &MediaKeysModuleMock::defaultReturn)))); @@ -119,8 +118,7 @@ void MediaKeysTestMethods::shouldCreateKeySessionFailure() { EXPECT_CALL(*m_mediaKeysModuleMock, createKeySession(_, - createKeySessionRequestMatcher(kMediaKeysHandle, - convertKeySessionType(kSessionTypeTemp), kIsNotLdl), + createKeySessionRequestMatcher(kMediaKeysHandle, convertKeySessionType(kSessionTypeTemp)), _, _)) .WillOnce(DoAll(SetArgPointee<2>(m_mediaKeysModuleMock->createKeySessionResponse(kStatusFailed, kKeySessionId)), WithArgs<0, 3>(Invoke(&(*m_mediaKeysModuleMock), &MediaKeysModuleMock::defaultReturn)))); @@ -129,15 +127,14 @@ void MediaKeysTestMethods::shouldCreateKeySessionFailure() void MediaKeysTestMethods::createKeySession() { int32_t keySessionId; - EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, kIsNotLdl, keySessionId), kStatusOk); + EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, keySessionId), kStatusOk); EXPECT_EQ(keySessionId, kKeySessionId); } void MediaKeysTestMethods::createKeySessionFailure() { int32_t keySessionId; - EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, kIsNotLdl, keySessionId), - kStatusFailed); + EXPECT_EQ(m_mediaKeys->createKeySession(kSessionTypeTemp, m_mediaKeysClientMock, keySessionId), kStatusFailed); } void MediaKeysTestMethods::shouldGenerateRequest() @@ -704,6 +701,39 @@ void MediaKeysTestMethods::doesNotSupportServerCertificate() EXPECT_FALSE(m_mediaKeysCapabilities->isServerCertificateSupported(kKeySystems[0])); } +void MediaKeysTestMethods::shouldGetSupportedRobustnessLevels() +{ + EXPECT_CALL(*m_mediaKeysCapabilitiesModuleMock, + getSupportedRobustnessLevels(_, getSupportedRobustnessLevelsRequestMatcher(kKeySystems[0]), _, _)) + .WillOnce(DoAll(SetArgPointee<2>( + m_mediaKeysCapabilitiesModuleMock->getSupportedRobustnessLevelsResponse(kRobustnessLevels)), + WithArgs<0, 3>(Invoke(&(*m_mediaKeysCapabilitiesModuleMock), + &MediaKeysCapabilitiesModuleMock::defaultReturn)))); +} + +void MediaKeysTestMethods::getSupportedRobustnessLevels() +{ + std::vector robustnessLevels; + EXPECT_TRUE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystems[0], robustnessLevels)); + EXPECT_EQ(robustnessLevels, kRobustnessLevels); +} + +void MediaKeysTestMethods::shouldNotGetSupportedRobustnessLevels() +{ + EXPECT_CALL(*m_mediaKeysCapabilitiesModuleMock, + getSupportedRobustnessLevels(_, getSupportedRobustnessLevelsRequestMatcher(kKeySystems[0]), _, _)) + .WillOnce(DoAll(SetArgPointee<2>( + m_mediaKeysCapabilitiesModuleMock->getSupportedRobustnessLevelsResponse(kRobustnessLevels)), + WithArgs<0, 3>(Invoke(&(*m_mediaKeysCapabilitiesModuleMock), + &MediaKeysCapabilitiesModuleMock::failureReturn)))); +} + +void MediaKeysTestMethods::doesNotGetSupportedRobustnessLevels() +{ + std::vector robustnessLevels; + EXPECT_FALSE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystems[0], robustnessLevels)); +} + void MediaKeysTestMethods::shouldGetMetricSystemData() { EXPECT_CALL(*m_mediaKeysModuleMock, getMetricSystemData(_, getMetricSystemDataRequestMatcher(kMediaKeysHandle), _, _)) diff --git a/tests/componenttests/client/tests/base/MediaKeysTestMethods.h b/tests/componenttests/client/tests/base/MediaKeysTestMethods.h index 8b00b07fb..58c49ee1f 100644 --- a/tests/componenttests/client/tests/base/MediaKeysTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaKeysTestMethods.h @@ -105,6 +105,8 @@ class MediaKeysTestMethods void shouldNotGetSupportedKeySystemVersion(); void shouldSupportServerCertificate(); void shouldNotSupportServerCertificate(); + void shouldGetSupportedRobustnessLevels(); + void shouldNotGetSupportedRobustnessLevels(); // Api methods void createMediaKeysWidevine(); @@ -145,6 +147,8 @@ class MediaKeysTestMethods void destroyMediaKeysCapabilitiesObject(); void supportsServerCertificate(); void doesNotSupportServerCertificate(); + void getSupportedRobustnessLevels(); + void doesNotGetSupportedRobustnessLevels(); void releaseKeySession(); void getMetricSystemData(); void getMetricSystemDataFailure(); diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index 51ef2b5d4..d05699e84 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -91,6 +91,7 @@ constexpr int64_t kDiscontinuityGap{1}; constexpr bool kIsAudioAac{false}; const std::vector kSupportedProperties{"immediate-output", "testProp2"}; constexpr uint64_t kStopPosition{452345}; +constexpr bool kIsLive{false}; } // namespace namespace firebolt::rialto::client::ct @@ -892,7 +893,8 @@ void MediaPipelineTestMethods::shouldNotifyPlaybackStateFailure() void MediaPipelineTestMethods::playFailure() { - EXPECT_EQ(m_mediaPipeline->play(), false); + bool async{false}; + EXPECT_EQ(m_mediaPipeline->play(async), false); } void MediaPipelineTestMethods::pauseFailure() @@ -1373,6 +1375,30 @@ void MediaPipelineTestMethods::sendNotifyBufferUnderflowVideo() waitEvent(); } +void MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedVideo() +{ + EXPECT_CALL(*m_mediaPipelineClientMock, notifyFirstFrameReceived(kVideoSourceId)) + .WillOnce(Invoke(this, &MediaPipelineTestMethods::notifyEvent)); +} + +void MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedAudio() +{ + EXPECT_CALL(*m_mediaPipelineClientMock, notifyFirstFrameReceived(kAudioSourceId)) + .WillOnce(Invoke(this, &MediaPipelineTestMethods::notifyEvent)); +} + +void MediaPipelineTestMethods::sendNotifyFirstFrameReceivedAudio() +{ + getServerStub()->notifyFirstFrameReceivedEvent(kSessionId, kAudioSourceId); + waitEvent(); +} + +void MediaPipelineTestMethods::sendNotifyFirstFrameReceivedVideo() +{ + getServerStub()->notifyFirstFrameReceivedEvent(kSessionId, kVideoSourceId); + waitEvent(); +} + void MediaPipelineTestMethods::shouldNotifyPlaybackErrorAudio() { EXPECT_CALL(*m_mediaPipelineClientMock, notifyPlaybackError(kAudioSourceId, PlaybackError::DECRYPTION)) @@ -1423,6 +1449,20 @@ void MediaPipelineTestMethods::getPosition(const int64_t expectedPosition) EXPECT_EQ(returnPosition, expectedPosition); } +void MediaPipelineTestMethods::shouldGetDuration(const int64_t duration) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, getDuration(_, getDurationRequestMatcher(kSessionId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->getDurationResponse(duration)), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::getDuration(const int64_t expectedDuration) +{ + int64_t returnDuration; + EXPECT_EQ(m_mediaPipeline->getDuration(returnDuration), true); + EXPECT_EQ(returnDuration, expectedDuration); +} + void MediaPipelineTestMethods::shouldSetImmediateOutput(bool immediateOutput) { EXPECT_CALL(*m_mediaPipelineModuleMock, @@ -1449,6 +1489,34 @@ void MediaPipelineTestMethods::getImmediateOutput(bool immediateOutput) EXPECT_TRUE(m_mediaPipeline->getImmediateOutput(kVideoSourceId, immediateOutput)); } +void MediaPipelineTestMethods::shouldSetReportDecodeErrors(bool reportDecodeErrors) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + setReportDecodeErrors(_, setReportDecodeErrorsRequestMatcher(kSessionId, kVideoSourceId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->SetReportDecodeErrorsResponse()), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::setReportDecodeErrors(bool reportDecodeErrors) +{ + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(kVideoSourceId, reportDecodeErrors)); +} + +void MediaPipelineTestMethods::shouldGetQueuedFrames(uint32_t queuedFrames) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + getQueuedFrames(_, getQueuedFramesRequestMatcher(kSessionId, kVideoSourceId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->getQueuedFramesResponse(queuedFrames)), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::getQueuedFrames(uint32_t queuedFrames) +{ + uint32_t returnQueuedFrames; + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(kVideoSourceId, returnQueuedFrames)); + EXPECT_EQ(returnQueuedFrames, queuedFrames); +} + void MediaPipelineTestMethods::shouldGetStats(uint64_t renderedFrames, uint64_t droppedFrames) { EXPECT_CALL(*m_mediaPipelineModuleMock, getStats(_, getStatsRequestMatcher(kSessionId, kVideoSourceId), _, _)) @@ -1830,7 +1898,7 @@ void MediaPipelineTestMethods::shouldLoadInternal(const int32_t sessionId, const const std::string &mimeType, const std::string &url) { EXPECT_CALL(*m_mediaPipelineModuleMock, - load(_, loadRequestMatcher(sessionId, convertMediaType(mediaType), mimeType, url), _, _)) + load(_, loadRequestMatcher(sessionId, convertMediaType(mediaType), mimeType, url, kIsLive), _, _)) .WillOnce(WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn))); } @@ -1949,7 +2017,7 @@ void MediaPipelineTestMethods::loadInternal(const std::unique_ptrload(mediaType, mimeType, url), status); + EXPECT_EQ(mediaPipeline->load(mediaType, mimeType, url, kIsLive), status); } void MediaPipelineTestMethods::removeSourceInternal(const std::unique_ptr &mediaPipeline, @@ -2033,7 +2101,8 @@ void MediaPipelineTestMethods::haveDataInternal(const std::unique_ptr &mediaPipeline, const bool status) { - EXPECT_EQ(mediaPipeline->play(), status); + bool async{false}; + EXPECT_EQ(mediaPipeline->play(async), status); } void MediaPipelineTestMethods::sendNotifyPlaybackStateInternal(const int32_t sessionId, const PlaybackState &state) diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index c6b9d7f74..6aa529ee1 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -128,8 +128,11 @@ class MediaPipelineTestMethods void shouldRenderFrame(); void shouldRenderFrameFailure(); void shouldGetPosition(const int64_t position); + void shouldGetDuration(const int64_t duration); void shouldSetImmediateOutput(bool immediateOutput); void shouldGetImmediateOutput(bool immediateOutput); + void shouldSetReportDecodeErrors(bool reportDecodeErrors); + void shouldGetQueuedFrames(uint32_t queuedFrames); void shouldGetStats(uint64_t renderedFrames, uint64_t droppedFrames); void shouldFlush(); void shouldFailToFlush(); @@ -172,6 +175,8 @@ class MediaPipelineTestMethods void shouldNotifyQosVideo(); void shouldNotifyBufferUnderflowAudio(); void shouldNotifyBufferUnderflowVideo(); + void shouldNotifyFirstFrameReceivedAudio(); + void shouldNotifyFirstFrameReceivedVideo(); void shouldNotifyPlaybackErrorAudio(); void shouldNotifyPlaybackErrorVideo(); void shouldNotifySourceFlushed(); @@ -246,8 +251,11 @@ class MediaPipelineTestMethods void renderFrame(); void renderFrameFailure(); void getPosition(const int64_t expectedPosition); + void getDuration(const int64_t expectedDuration); void setImmediateOutput(bool immediateOutput); void getImmediateOutput(bool immediateOutput); + void setReportDecodeErrors(bool reportDecodeErrors); + void getQueuedFrames(uint32_t queuedFrames); void getStats(uint64_t expectedFrames, uint64_t expectedDropped); void createMediaPipelineCapabilitiesObject(); void destroyMediaPipelineCapabilitiesObject(); @@ -299,6 +307,8 @@ class MediaPipelineTestMethods void sendNotifyQosVideo(); void sendNotifyBufferUnderflowAudio(); void sendNotifyBufferUnderflowVideo(); + void sendNotifyFirstFrameReceivedAudio(); + void sendNotifyFirstFrameReceivedVideo(); void sendNotifyPlaybackErrorAudio(); void sendNotifyPlaybackErrorVideo(); void sendNotifySourceFlushed(); diff --git a/tests/componenttests/client/tests/eme/MediaKeysCapabilitiesTest.cpp b/tests/componenttests/client/tests/eme/MediaKeysCapabilitiesTest.cpp index c6f39958d..1f68e0366 100644 --- a/tests/componenttests/client/tests/eme/MediaKeysCapabilitiesTest.cpp +++ b/tests/componenttests/client/tests/eme/MediaKeysCapabilitiesTest.cpp @@ -134,7 +134,15 @@ TEST_F(MediaKeysCapabilitiesTest, checkSupportedKeySystems) MediaKeysTestMethods::shouldNotSupportServerCertificate(); MediaKeysTestMethods::doesNotSupportServerCertificate(); - // Step 9: Destroy MediaKeysCabilities. + // Step 9: Get supported robustness levels - success + MediaKeysTestMethods::shouldGetSupportedRobustnessLevels(); + MediaKeysTestMethods::getSupportedRobustnessLevels(); + + // Step 10: Get supported robustness levels - failure + MediaKeysTestMethods::shouldNotGetSupportedRobustnessLevels(); + MediaKeysTestMethods::doesNotGetSupportedRobustnessLevels(); + + // Step 11: Destroy MediaKeysCabilities. MediaKeysTestMethods::destroyMediaKeysCapabilitiesObject(); } } // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp b/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp new file mode 100644 index 000000000..15d6bd1d1 --- /dev/null +++ b/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp @@ -0,0 +1,94 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "ClientComponentTest.h" +#include + +namespace firebolt::rialto::client::ct +{ +class FirstFrameNotificationTest : public ClientComponentTest +{ +public: + FirstFrameNotificationTest() : ClientComponentTest() + { + ClientComponentTest::startApplicationRunning(); + MediaPipelineTestMethods::startAudioVideoMediaSessionPrerollPaused(); + + MediaPipelineTestMethods::shouldPlay(); + MediaPipelineTestMethods::play(); + MediaPipelineTestMethods::shouldNotifyPlaybackStatePlaying(); + MediaPipelineTestMethods::sendNotifyPlaybackStatePlaying(); + } + + ~FirstFrameNotificationTest() + { + MediaPipelineTestMethods::endAudioVideoMediaSession(); + ClientComponentTest::stopApplication(); + } +}; + +/* + * Component Test: First frame notification + * Test Objective: + * Test the first frame notification for video and audio sources. + * + * Sequence Diagrams: + * First frame notification + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Create memory region for the shared buffer. + * Create a server that handles Control IPC requests. + * Initalise the control state to running for this test application. + * Initalise a audio video media session playing. + * + * Test Steps: + * Step 1: Notify first frame received for video + * Server notifies the client first frame received with source id video. + * Expect that the first frame notification is propagated to the client. + * + * Step 2: Notify first frame received for audio + * Server notifies the client first frame received with source id audio. + * Expect that the first frame notification is propagated to the client. + * + * Test Teardown: + * Terminate the media session. + * Memory region created for the shared buffer is closed. + * Server is terminated. + * + * Expected Results: + * First frame received notification is propagated to the application. + * + * Code: + */ +TEST_F(FirstFrameNotificationTest, notification) +{ + // Step 1: Notify first frame received for video + MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedVideo(); + MediaPipelineTestMethods::sendNotifyFirstFrameReceivedVideo(); + + // Step 2: Notify first frame received for audio + MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedAudio(); + MediaPipelineTestMethods::sendNotifyFirstFrameReceivedAudio(); +} +} // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp index a64aeadd7..6a019c9c4 100644 --- a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp +++ b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp @@ -108,6 +108,18 @@ class PipelinePropertyTest : public ClientComponentTest * GetUseBuffering * Expect that GetUseBuffering propagated to the server and gets the property * + * Step 13: Set Report Decode Errors + * Set report decode errors + * Expect that Set report decode errors propagated to the server and sets the property + * + * Step 14: Get Queued Frames + * GetQueuedFrames + * Expect that GetQueuedFrames propagated to the server and gets the number of queued frames + * + * Step 15: Get Duration + * GetDuration + * Expect that GetDuration propagated to the server and gets the duration + * * Test Teardown: * Terminate the media session. * Memory region created for the shared buffer is closed. @@ -174,5 +186,20 @@ TEST_F(PipelinePropertyTest, setAndGetPipelineProperties) // Step 12: Get UseBuffering MediaPipelineTestMethods::shouldGetUseBuffering(useBuffering); MediaPipelineTestMethods::getUseBuffering(useBuffering); + + // Step 13: Get Duration + constexpr int64_t duration{123456789}; + MediaPipelineTestMethods::shouldGetDuration(duration); + MediaPipelineTestMethods::getDuration(duration); + + // Step 14: Set Report Decode Errors + bool reportDecodeErrors{true}; + MediaPipelineTestMethods::shouldSetReportDecodeErrors(reportDecodeErrors); + MediaPipelineTestMethods::setReportDecodeErrors(reportDecodeErrors); + + // Step 15: Get Queued Frames + uint32_t queuedFrames{123}; + MediaPipelineTestMethods::shouldGetQueuedFrames(queuedFrames); + MediaPipelineTestMethods::getQueuedFrames(queuedFrames); } } // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/server/common/ActionTraits.h b/tests/componenttests/server/common/ActionTraits.h index d601e8d9f..e0326bb82 100644 --- a/tests/componenttests/server/common/ActionTraits.h +++ b/tests/componenttests/server/common/ActionTraits.h @@ -344,6 +344,14 @@ struct ProcessAudioGap static constexpr auto m_kFunction{&Stub::processAudioGap}; }; +struct GetDuration +{ + using RequestType = ::firebolt::rialto::GetDurationRequest; + using ResponseType = ::firebolt::rialto::GetDurationResponse; + using Stub = ::firebolt::rialto::MediaPipelineModule_Stub; + static constexpr auto m_kFunction{&Stub::getDuration}; +}; + // mediakeys module struct CreateMediaKeys { diff --git a/tests/componenttests/server/common/Constants.h b/tests/componenttests/server/common/Constants.h index 0072a59a9..422e9c33c 100644 --- a/tests/componenttests/server/common/Constants.h +++ b/tests/componenttests/server/common/Constants.h @@ -54,6 +54,8 @@ constexpr double kRate{1.0}; constexpr uint64_t kRenderedFrames{54321}; constexpr uint64_t kDroppedFrames{76}; constexpr uint64_t kStopPosition{234234}; +constexpr int kPrerollNumFrames{3}; +constexpr int kFrameCountInPlayingState{24}; } // namespace firebolt::rialto::server::ct #endif // FIREBOLT_RIALTO_SERVER_CT_CONSTANTS_H_ diff --git a/tests/componenttests/server/common/ExpectMessage.h b/tests/componenttests/server/common/ExpectMessage.h index 839319e37..bd140f92a 100644 --- a/tests/componenttests/server/common/ExpectMessage.h +++ b/tests/componenttests/server/common/ExpectMessage.h @@ -70,7 +70,7 @@ template class ExpectMessage EventRanger &m_eventRanger; std::shared_ptr m_message{nullptr}; std::function m_filter{[](const MessageType &) { return true; }}; - std::chrono::milliseconds m_timeout{400}; + std::chrono::milliseconds m_timeout{600}; }; } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/common/MessageBuilders.cpp b/tests/componenttests/server/common/MessageBuilders.cpp index 848b925f8..bdfb44a01 100644 --- a/tests/componenttests/server/common/MessageBuilders.cpp +++ b/tests/componenttests/server/common/MessageBuilders.cpp @@ -407,6 +407,13 @@ ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sess return request; } +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId) +{ + ::firebolt::rialto::GetDurationRequest request; + request.set_session_id(sessionId); + return request; +} + ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine() { ::firebolt::rialto::CreateMediaKeysRequest request; @@ -430,7 +437,8 @@ ::firebolt::rialto::CreateKeySessionRequest createCreateKeySessionRequest(int me } ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int mediaKeysHandle, int keySessionId, - const std::vector &initData) + const std::vector &initData, + bool extendedInterface) { ::firebolt::rialto::GenerateRequestRequest request; request.set_media_keys_handle(mediaKeysHandle); @@ -440,6 +448,10 @@ ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int medi { request.add_init_data(i); } + if (extendedInterface) + { + request.set_ldl_state(::firebolt::rialto::GenerateRequestRequest_LimitedDurationLicense_DISABLED); + } return request; } diff --git a/tests/componenttests/server/common/MessageBuilders.h b/tests/componenttests/server/common/MessageBuilders.h index dae4679ae..0c255bb5f 100644 --- a/tests/componenttests/server/common/MessageBuilders.h +++ b/tests/componenttests/server/common/MessageBuilders.h @@ -83,13 +83,15 @@ ::firebolt::rialto::SetSourcePositionRequest createSetSourcePositionRequest(int ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sessionId, std::int64_t position, unsigned duration, std::int64_t discontinuityGap, bool audioAac); +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId); // media keys module ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine(); ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestNetflix(); ::firebolt::rialto::CreateKeySessionRequest createCreateKeySessionRequest(int mediaKeysHandle); ::firebolt::rialto::GenerateRequestRequest createGenerateRequestRequest(int mediaKeysHandle, int keySessionId, - const std::vector &initData); + const std::vector &initData, + bool extendedInterface = false); ::firebolt::rialto::UpdateSessionRequest createUpdateSessionRequest(int mediaKeysHandle, int keySessionId, const std::vector &response); ::firebolt::rialto::ContainsKeyRequest createContainsKeyRequest(int mediaKeysHandle, int keySessionId, diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp index 03eadf4bc..94dc56ab3 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp @@ -35,6 +35,7 @@ using testing::_; using testing::AtLeast; +using testing::AtMost; using testing::DoAll; using testing::Invoke; using testing::Return; @@ -52,7 +53,6 @@ constexpr GType kGstPlayFlagsType{static_cast(123)}; constexpr unsigned kNeededDataLength{1}; constexpr std::chrono::milliseconds kWorkerTimeout{200}; GstAudioClippingMeta kClippingMeta{}; -constexpr int kNeedDataFrameCount{24}; } // namespace namespace firebolt::rialto::server::ct @@ -67,6 +67,7 @@ MediaPipelineTest::MediaPipelineTest() MediaPipelineTest::~MediaPipelineTest() { positionUpdatesShouldNotBeReceivedFromNow(); + playbackInfoUpdatesShouldNotBeReceivedFromNow(); } void MediaPipelineTest::gstPlayerWillBeCreated() @@ -77,6 +78,7 @@ void MediaPipelineTest::gstPlayerWillBeCreated() EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("playbin"), _)).WillOnce(Return(&m_pipeline)); EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(4).WillRepeatedly(Return(kGstPlayFlagsType)); EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(4).WillRepeatedly(Return(&m_flagsClass)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_flagsClass)).Times(4); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); @@ -91,6 +93,7 @@ void MediaPipelineTest::gstPlayerWillBeCreated() EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_playsink)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_pipeline)).Times(AtMost(1)); // In case of longer testruns, GstPlayer may request to query position and volume EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).Times(AtLeast(0)); @@ -108,7 +111,14 @@ void MediaPipelineTest::gstPlayerWillBeCreated() return true; })); - EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)).Times(AtLeast(0)); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillRepeatedly(Invoke( + [&](gpointer object, const gchar *first_property_name, void *element) + { + GstElement **elementPtr = reinterpret_cast(element); + *elementPtr = m_audioSink; + })); + EXPECT_CALL(*m_gstWrapperMock, gstStreamVolumeGetVolume(_, GST_STREAM_VOLUME_FORMAT_LINEAR)) .Times(AtLeast(0)) .WillRepeatedly(Return(kVolume)); @@ -121,7 +131,8 @@ void MediaPipelineTest::gstPlayerWillBeDestructed() EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(&m_bus, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pipeline)).Times(testing::Between(1, 2)); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolStopUnusedThreads()).Times(testing::Between(1, 2)); } void MediaPipelineTest::audioSourceWillBeAttached() @@ -397,25 +408,6 @@ void MediaPipelineTest::willEos(GstAppSrc *appSrc) EXPECT_CALL(*m_gstWrapperMock, gstAppSrcEndOfStream(appSrc)).WillOnce(Return(GST_FLOW_OK)); } -void MediaPipelineTest::willRemoveAudioSource() -{ - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) - .WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(0)).WillOnce(Return(&m_flushStopEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(3).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(3).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))) - .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); -} - void MediaPipelineTest::willStop() { EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_NULL)) @@ -428,19 +420,6 @@ void MediaPipelineTest::willStop() EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); } -void MediaPipelineTest::willSetAudioAndVideoFlags() -{ - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(4).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(4).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryFind(StrEq("brcmaudiosink"))).WillOnce(Return(nullptr)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))); -} - void MediaPipelineTest::createSession() { // Use matchResponse to store session id @@ -501,28 +480,28 @@ void MediaPipelineTest::setupSource() void MediaPipelineTest::indicateAllSourcesAttached(const std::vector &appsrcs) { ExpectMessage expectedPlaybackStateChange(m_clientStub); - std::map>> expectedNeedDataMap; + std::vector>>> expectedNeedData; for (const GstAppSrc *appSrc : appsrcs) { const int kSourceId = ((appSrc == &m_audioAppSrc) ? m_audioSourceId : m_videoSourceId); auto expectation{std::make_unique>(m_clientStub)}; expectation->setFilter([kSourceId](const firebolt::rialto::NeedMediaDataEvent &msg) { return msg.source_id() == kSourceId; }); - expectedNeedDataMap.emplace(kSourceId, std::move(expectation)); + expectedNeedData.emplace_back(kSourceId, std::move(expectation)); } auto allSourcesAttachedReq{createAllSourcesAttachedRequest(m_sessionId)}; ConfigureAction(m_clientStub).send(allSourcesAttachedReq).expectSuccess(); - for (const auto &[sourceId, expectedNeedData] : expectedNeedDataMap) + for (const auto &[sourceId, expectedNeedDataEntry] : expectedNeedData) { auto &needDataPtr = ((sourceId == m_audioSourceId) ? m_lastAudioNeedData : m_lastVideoNeedData); - auto receivedNeedData{expectedNeedData->getMessage()}; + auto receivedNeedData{expectedNeedDataEntry->getMessage()}; ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), sourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), kPrerollNumFrames); needDataPtr = receivedNeedData; } @@ -534,6 +513,8 @@ void MediaPipelineTest::indicateAllSourcesAttached(const std::vector(m_clientStub).send(pauseReq).expectSuccess(); positionUpdatesShouldNotBeReceivedFromNow(); @@ -555,7 +536,7 @@ void MediaPipelineTest::notifyPaused() ASSERT_TRUE(receivedPlaybackInfo); } -void MediaPipelineTest::pushAudioData(unsigned dataCountToPush) +void MediaPipelineTest::pushAudioData(unsigned dataCountToPush, int needDataFrameCount) { // First, generate new data std::vector> segments(dataCountToPush); @@ -588,11 +569,11 @@ void MediaPipelineTest::pushAudioData(unsigned dataCountToPush) ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastAudioNeedData = receivedNeedData; } -void MediaPipelineTest::pushVideoData(unsigned dataCountToPush) +void MediaPipelineTest::pushVideoData(unsigned dataCountToPush, int needDataFrameCount) { // First, generate new data std::vector> segments(dataCountToPush); @@ -625,11 +606,11 @@ void MediaPipelineTest::pushVideoData(unsigned dataCountToPush) ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_videoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastVideoNeedData = receivedNeedData; } -void MediaPipelineTest::pushAudioSample() +void MediaPipelineTest::pushAudioSample(int needDataFrameCount) { // First, generate new data std::unique_ptr segment{SegmentBuilder().basicAudioSegment(m_audioSourceId)()}; @@ -657,11 +638,11 @@ void MediaPipelineTest::pushAudioSample() ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastAudioNeedData = receivedNeedData; } -void MediaPipelineTest::pushVideoSample() +void MediaPipelineTest::pushVideoSample(int needDataFrameCount) { // First, generate new data std::unique_ptr segment{SegmentBuilder().basicVideoSegment(m_videoSourceId)()}; @@ -689,7 +670,7 @@ void MediaPipelineTest::pushVideoSample() ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), m_videoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kNeedDataFrameCount); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); m_lastVideoNeedData = receivedNeedData; } @@ -809,10 +790,6 @@ void MediaPipelineTest::removeSource(int sourceId) { auto removeSourceReq{createRemoveSourceRequest(m_sessionId, sourceId)}; ConfigureAction(m_clientStub).send(removeSourceReq).expectSuccess(); - - // Sources other than audio do not do anything for RemoveSource - if (m_audioSourceId == sourceId) - waitWorker(); } void MediaPipelineTest::stop() @@ -830,6 +807,7 @@ void MediaPipelineTest::stop() EXPECT_EQ(receivedPlaybackStateChange->state(), ::firebolt::rialto::PlaybackStateChangeEvent_PlaybackState_STOPPED); positionUpdatesShouldNotBeReceivedFromNow(); + playbackInfoUpdatesShouldNotBeReceivedFromNow(); } void MediaPipelineTest::destroySession() @@ -853,11 +831,6 @@ void MediaPipelineTest::mayReceivePositionUpdates() { m_positionChangeEventSuppressionId = m_clientStub.addSuppression(); } - - if (-1 == m_playbackInfoEventSuppressionId) - { - m_playbackInfoEventSuppressionId = m_clientStub.addSuppression(); - } } void MediaPipelineTest::positionUpdatesShouldNotBeReceivedFromNow() @@ -867,7 +840,18 @@ void MediaPipelineTest::positionUpdatesShouldNotBeReceivedFromNow() m_clientStub.removeSuppression(m_positionChangeEventSuppressionId); m_positionChangeEventSuppressionId = -1; } +} + +void MediaPipelineTest::mayReceivePlaybackInfoUpdates() +{ + if (-1 == m_playbackInfoEventSuppressionId) + { + m_playbackInfoEventSuppressionId = m_clientStub.addSuppression(); + } +} +void MediaPipelineTest::playbackInfoUpdatesShouldNotBeReceivedFromNow() +{ if (-1 != m_playbackInfoEventSuppressionId) { m_clientStub.removeSuppression(m_playbackInfoEventSuppressionId); diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.h b/tests/componenttests/server/fixtures/MediaPipelineTest.h index efe006423..b44e5d298 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.h +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_CT_MEDIA_PIPELINE_TEST_H_ #define FIREBOLT_RIALTO_SERVER_CT_MEDIA_PIPELINE_TEST_H_ +#include "Constants.h" #include "GstSrc.h" #include "GstreamerStub.h" #include "IMediaPipeline.h" @@ -57,9 +58,7 @@ class MediaPipelineTest : public RialtoServerComponentTest void willNotifyPaused(); void willPlay(); void willEos(GstAppSrc *appSrc); - void willRemoveAudioSource(); void willStop(); - void willSetAudioAndVideoFlags(); void willSetStateInvalidForQueryPosition(); void createSession(); @@ -70,10 +69,10 @@ class MediaPipelineTest : public RialtoServerComponentTest void indicateAllSourcesAttached(const std::vector &appsrcs); void pause(); void notifyPaused(); - void pushAudioData(unsigned dataCountToPush); - void pushVideoData(unsigned dataCountToPush); - void pushAudioSample(); - void pushVideoSample(); + void pushAudioData(unsigned dataCountToPush, int needDataFrameCount = kPrerollNumFrames); + void pushVideoData(unsigned dataCountToPush, int needDataFrameCount = kPrerollNumFrames); + void pushAudioSample(int needDataFrameCount = kPrerollNumFrames); + void pushVideoSample(int needDataFrameCount = kPrerollNumFrames); void play(); void eosAudio(unsigned dataCountToPush); void eosVideo(unsigned dataCountToPush); @@ -88,6 +87,8 @@ class MediaPipelineTest : public RialtoServerComponentTest void initShm(); void mayReceivePositionUpdates(); void positionUpdatesShouldNotBeReceivedFromNow(); + void mayReceivePlaybackInfoUpdates(); + void playbackInfoUpdatesShouldNotBeReceivedFromNow(); protected: int m_sessionId{-1}; @@ -120,6 +121,7 @@ class MediaPipelineTest : public RialtoServerComponentTest GstSample *m_sample{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastAudioNeedData{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastVideoNeedData{nullptr}; + GstElement *m_audioSink{nullptr}; // Position Update events may be received in PLAYING state. We have to suppress them // to avoid occassional test failures diff --git a/tests/componenttests/server/fixtures/RialtoServerComponentTest.cpp b/tests/componenttests/server/fixtures/RialtoServerComponentTest.cpp index 20df2972b..3c34a8d17 100644 --- a/tests/componenttests/server/fixtures/RialtoServerComponentTest.cpp +++ b/tests/componenttests/server/fixtures/RialtoServerComponentTest.cpp @@ -202,6 +202,8 @@ void RialtoServerComponentTest::initialiseGstreamer() EXPECT_CALL(*m_gstWrapperMock, gstInit(nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstRegistryGet()).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstRegistryFindPlugin(nullptr, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolSetMaxUnusedThreads(2)); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolSetMaxIdleTime(5 * 1000)); firebolt::rialto::server::IGstInitialiser::instance().initialise(nullptr, nullptr); }); } diff --git a/tests/componenttests/server/stubs/ClientStub.cpp b/tests/componenttests/server/stubs/ClientStub.cpp index 60896dc6d..1da9e9147 100644 --- a/tests/componenttests/server/stubs/ClientStub.cpp +++ b/tests/componenttests/server/stubs/ClientStub.cpp @@ -43,14 +43,14 @@ bool ClientStub::connect() { return false; } - setupSubscriptions(m_ipcChannel); + setupSubscriptions< + firebolt::rialto::PlaybackStateChangeEvent, firebolt::rialto::NetworkStateChangeEvent, + firebolt::rialto::PositionChangeEvent, firebolt::rialto::NeedMediaDataEvent, firebolt::rialto::QosEvent, + firebolt::rialto::BufferUnderflowEvent, firebolt::rialto::FirstFrameReceivedEvent, + firebolt::rialto::PlaybackErrorEvent, firebolt::rialto::SetLogLevelsEvent, firebolt::rialto::SourceFlushedEvent, + firebolt::rialto::WebAudioPlayerStateEvent, firebolt::rialto::ApplicationStateChangeEvent, + firebolt::rialto::PingEvent, firebolt::rialto::LicenseRequestEvent, firebolt::rialto::LicenseRenewalEvent, + firebolt::rialto::KeyStatusesChangedEvent, firebolt::rialto::PlaybackInfoEvent>(m_ipcChannel); m_ipcThread = std::thread(&ClientStub::ipcThread, this); return true; } diff --git a/tests/componenttests/server/stubs/GstreamerStub.cpp b/tests/componenttests/server/stubs/GstreamerStub.cpp index b30f9bcaa..3677ed0a2 100644 --- a/tests/componenttests/server/stubs/GstreamerStub.cpp +++ b/tests/componenttests/server/stubs/GstreamerStub.cpp @@ -93,7 +93,7 @@ void GstreamerStub::setupMessages(bool repeatedCallsToGstPipelineGetBus) gstBusTimedPopFiltered(m_bus, 100 * GST_MSECOND, static_cast(GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_QOS | GST_MESSAGE_EOS | GST_MESSAGE_ERROR | - GST_MESSAGE_WARNING))) + GST_MESSAGE_WARNING | GST_MESSAGE_APPLICATION))) .WillRepeatedly(Invoke( [&](GstBus *bus, GstClockTime timeout, GstMessageType types) { diff --git a/tests/componenttests/server/tests/CMakeLists.txt b/tests/componenttests/server/tests/CMakeLists.txt index 9fe1d15bb..9898b805d 100644 --- a/tests/componenttests/server/tests/CMakeLists.txt +++ b/tests/componenttests/server/tests/CMakeLists.txt @@ -46,6 +46,7 @@ add_gtests ( mediaPipeline/DualVideoPlaybackTest.cpp mediaPipeline/EncryptedPlaybackTest.cpp mediaPipeline/FailureTests.cpp + mediaPipeline/FirstFrameNotificationTest.cpp mediaPipeline/FlushTest.cpp mediaPipeline/HaveDataFailureTest.cpp mediaPipeline/MuteTest.cpp @@ -56,13 +57,13 @@ add_gtests ( mediaPipeline/PositionUpdatesTest.cpp mediaPipeline/ProcessAudioGapTest.cpp mediaPipeline/QosUpdatesTest.cpp - mediaPipeline/RemoveAudioPlaybackTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/SetPositionTest.cpp mediaPipeline/SetSourcePositionTest.cpp mediaPipeline/SetVideoWindowTest.cpp mediaPipeline/SourceTest.cpp + mediaPipeline/SwitchAudioPlaybackTest.cpp mediaPipeline/UnderflowTest.cpp mediaPipeline/VolumeTest.cpp mediaPipeline/WriteSegmentsTest.cpp diff --git a/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp b/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp index 68a42b773..49a3249e9 100644 --- a/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/LicenseRenewalTest.cpp @@ -130,12 +130,7 @@ void LicenseRenewalTest::updateOneKey() * Server notifies the client that of license renewal. * Expect that the license renewal notification is processed by the client. * - * Step 2: Update session - * updateSession with the updated license. - * Expect that updateSession is processed by the server. - * Api call returns with success. - * - * Step 3: Notify key statuses changed + * Step 2: Notify key statuses changed * Server notifies the client of key statuses changed. * Expect that the key statuses changed notification is processed by the client. * @@ -149,20 +144,77 @@ void LicenseRenewalTest::updateOneKey() */ TEST_F(LicenseRenewalTest, licenseRenewal) { - createMediaKeysNetflix(); + createMediaKeysWidevine(); ocdmSessionWillBeCreated(); createKeySession(); // Step 1: Notify license renewal licenseRenew(); - // Step 2: Update session + // Step 2: Notify key statuses changed + updateOneKey(); + updateAllKeys(); +} + +/* + * Component Test: License renewal sequence for netflix playready. + * Test Objective: + * Test the notification of license renewal and updating of the new license. + * + * Sequence Diagrams: + * License Renewal - Cobalt/OCDM, Update MKS - Cobalt/OCDM, "Destroy" MKS - Cobalt/OCDM + * - https://wiki.rdkcentral.com/display/ASP/Rialto+Media+Key+Session+Management+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaKeys + * + * Test Initialize: + * RialtoServerComponentTest::RialtoServerComponentTest() will set up wrappers and + * starts rialtoServer running in its own thread + * send a CreateMediaKeys message to rialtoServer + * expect a "createSession" call (to OCDM mock) + * send a CreateKeySession message to rialtoServer + * generate request message for playready and send it to the client + * expect the client to process the generate request message + * + * + * Test Steps: + * Step 1: Update session + * updateSession with the updated license. + * Expect that updateSession is processed by the server. + * Api call returns with success. + * + * Step 2: Close session + * closeSession. + * Expect that closeSession is processed by the server. + * Api call returns with success. + * + * Test Tear-down: + * Server is terminated. + * + * Expected Results: + * Client can be notified of license renewal and update the key session successfully. + * + * Code: + */ +TEST_F(LicenseRenewalTest, licenseRenewalNetflix) +{ + createMediaKeysNetflix(); + ocdmSessionWillBeCreated(); + createKeySession(); + willGenerateRequestPlayready(); + generateRequestPlayready(); + + // Step 1: Update session willUpdateSessionNetflix(); updateSessionNetflix(); - // Step 3: Notify key statuses changed - updateOneKey(); - updateAllKeys(); + // Step 2: Close session + willCloseKeySessionPlayready(); + closeKeySessionPlayready(); + willRelease(); } } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp b/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp index c954b8af5..def0de62d 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTest.cpp @@ -42,8 +42,6 @@ class MediaKeysTest : public MediaKeysTestMethods void generateRequestFail(); void shouldFailToCreateKeySessionWhenMksIdIsWrong(); - - const std::vector m_kInitData{1, 2, 7}; }; void MediaKeysTest::willGenerateRequestFail() diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp index 9bfd4e182..b9e91dc4e 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.cpp @@ -85,6 +85,69 @@ void MediaKeysTestMethods::ocdmSessionWillBeCreated() })); } +void MediaKeysTestMethods::willGenerateRequestPlayready() +{ + EXPECT_CALL(m_ocdmSessionMock, constructSession(KeySessionType::TEMPORARY, InitDataType::CENC, _, m_kInitData.size())) + .WillOnce(testing::Invoke( + [&](KeySessionType sessionType, InitDataType initDataType, const uint8_t initData[], + uint32_t initDataSize) -> MediaKeyErrorStatus + { + for (uint32_t i = 0; i < initDataSize; ++i) + { + EXPECT_EQ(initData[i], m_kInitData[i]); + } + + return MediaKeyErrorStatus::OK; + })); + + EXPECT_CALL(m_ocdmSessionMock, getChallengeData(false, _, _)) + .WillOnce(testing::Invoke( + [&](bool isLDL, const uint8_t *challenge, uint32_t *challengeSize) -> MediaKeyErrorStatus + { + // This first call asks for the size of the data + EXPECT_EQ(challenge, nullptr); + *challengeSize = m_kLicenseRequestMessage.size(); + return MediaKeyErrorStatus::OK; + })) + .WillOnce(testing::Invoke( + [&](bool isLDL, uint8_t *challenge, const uint32_t *challengeSize) -> MediaKeyErrorStatus + { + // This second call asks for the data + EXPECT_EQ(*challengeSize, m_kLicenseRequestMessage.size()); + for (size_t i = 0; i < m_kLicenseRequestMessage.size(); ++i) + { + challenge[i] = m_kLicenseRequestMessage[i]; + } + return MediaKeyErrorStatus::OK; + })); +} + +void MediaKeysTestMethods::generateRequestPlayready() +{ + constexpr bool kUseExtendedInterface{true}; + auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData, kUseExtendedInterface)}; + + ExpectMessage<::firebolt::rialto::LicenseRequestEvent> expectedMessage(m_clientStub); + + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const firebolt::rialto::GenerateRequestResponse &resp) + { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); + + auto message = expectedMessage.getMessage(); + ASSERT_TRUE(message); + ASSERT_EQ(message->media_keys_handle(), m_mediaKeysHandle); + ASSERT_EQ(message->key_session_id(), m_mediaKeySessionId); + EXPECT_EQ(message->url(), ""); + const unsigned int kMax = message->license_request_message_size(); + ASSERT_EQ(kMax, m_kLicenseRequestMessage.size()); + for (unsigned int i = 0; i < kMax; ++i) + { + ASSERT_EQ(message->license_request_message(i), m_kLicenseRequestMessage[i]); + } +} + void MediaKeysTestMethods::willUpdateSessionNetflix() { EXPECT_CALL(m_ocdmSessionMock, storeLicenseData(_, m_kUpdateSessionNetflixResponse.size())) @@ -109,6 +172,23 @@ void MediaKeysTestMethods::updateSessionNetflix() { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); } +void MediaKeysTestMethods::willCloseKeySessionPlayready() +{ + EXPECT_CALL(m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); + EXPECT_CALL(m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); +} + +void MediaKeysTestMethods::closeKeySessionPlayready() +{ + auto request{createCloseKeySessionRequest(m_mediaKeysHandle, m_mediaKeySessionId)}; + + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const firebolt::rialto::CloseKeySessionResponse &resp) + { EXPECT_EQ(resp.error_status(), ProtoMediaKeyErrorStatus::OK); }); +} + void MediaKeysTestMethods::willTeardown() { // For teardown... diff --git a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h index 0cfdea24c..65f16b0fb 100644 --- a/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h +++ b/tests/componenttests/server/tests/mediaKeys/MediaKeysTestMethods.h @@ -41,9 +41,15 @@ class MediaKeysTestMethods : public RialtoServerComponentTest void createKeySession(); void ocdmSessionWillBeCreated(); + void willGenerateRequestPlayready(); + void generateRequestPlayready(); + void willUpdateSessionNetflix(); void updateSessionNetflix(); + void willCloseKeySessionPlayready(); + void closeKeySessionPlayready(); + void willTeardown(); void willRelease(); @@ -59,6 +65,8 @@ class MediaKeysTestMethods : public RialtoServerComponentTest firebolt::rialto::wrappers::IOcdmSessionClient *m_ocdmSessionClient{0}; const std::vector m_kUpdateSessionNetflixResponse{5, 6}; + const std::vector m_kInitData{1, 2, 7}; + const std::vector m_kLicenseRequestMessage{'d', 'z', 'f'}; }; } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp b/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp index bd2e0f97f..b98e4802d 100644 --- a/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/SessionReadyForDecryptionTest.cpp @@ -58,8 +58,6 @@ class SessionReadyForDecryptionTest : public virtual MediaKeysTestMethods void destroyMediaKeysRequest(); const std::vector kResponse{4, 1, 3}; - const std::vector m_kInitData{1, 2, 7}; - const std::vector m_kLicenseRequestMessage{'d', 'z', 'f'}; }; void SessionReadyForDecryptionTest::willGenerateRequestWidevine() @@ -149,7 +147,8 @@ void SessionReadyForDecryptionTest::willGenerateRequestNetflix() void SessionReadyForDecryptionTest::generateRequestNetflix() { - auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData)}; + constexpr bool kUseExtendedInterface{true}; + auto request{createGenerateRequestRequest(m_mediaKeysHandle, m_mediaKeySessionId, m_kInitData, kUseExtendedInterface)}; ExpectMessage<::firebolt::rialto::LicenseRequestEvent> expectedMessage(m_clientStub); diff --git a/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp b/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp index a8aaa4de1..e9e6ac27d 100644 --- a/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp +++ b/tests/componenttests/server/tests/mediaKeys/SetDrmHeaderTest.cpp @@ -87,16 +87,32 @@ void SetDrmHeaderTest::setDrmHeader(const std::vector &kKeyId) * * * Test Steps: - * Step 1: Set the drm header + * Step 1: generateRequest + * client sends generateRequest message to rialtoServer + * rialtoServer passes request to OCDM library + * ocdm lib returns success + * rialtoServer returns a success message to the client + * + * rialtoServer calls OCDM library get_challenge_data() + * rialtoServer should forward this request, via an onLicenceRequest + * message, to the client. The content of this message should match + * the details from the ocdm library + + * Step 2: Set the drm header * setDrmHeader first header. * Expect that setDrmHeader is processed by the server. * Api call returns with success. * - * Step 2: Set the drm header for a second time with different header + * Step 3: Set the drm header for a second time with different header * setDrmHeader second header. * Expect that setDrmHeader is processed by the server. * Api call returns with success. * + * Step 4: Close session + * closeSession. + * Expect that closeSession is processed by the server. + * Api call returns with success. + * * Test Tear-down: * Server is terminated. * @@ -111,13 +127,22 @@ TEST_F(SetDrmHeaderTest, multiple) ocdmSessionWillBeCreated(); createKeySession(); - // Step 1: Set the drm header + // Step 1: generateRequest + willGenerateRequestPlayready(); + generateRequestPlayready(); + + // Step 2: Set the drm header willSetDrmHeader(kKeyId1); setDrmHeader(kKeyId1); - // Step 2: Set the drm header for a second time with different header + // Step 3: Set the drm header for a second time with different header willSetDrmHeader(kKeyId2); setDrmHeader(kKeyId2); + + // Step 4: Close session + willCloseKeySessionPlayready(); + closeKeySessionPlayready(); + willRelease(); } } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp index a53f69159..abbe7431e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp @@ -23,7 +23,7 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{3}; +constexpr int kPrerollNumFrames{3}; constexpr int kFrameCountInPlayingState{24}; } // namespace @@ -175,7 +175,7 @@ TEST_F(MediaPipelineTest, AudioOnlyPlayback) play(); // Step 9: Write 1 audio frame - pushAudioData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); // Step 10: End of audio stream willEos(&m_audioAppSrc); @@ -185,7 +185,6 @@ TEST_F(MediaPipelineTest, AudioOnlyPlayback) gstNotifyEos(); // Step 12: Remove source - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 13: Stop diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp index b9710bbf1..31f3411d2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp @@ -26,6 +26,8 @@ #include "MessageBuilders.h" using testing::_; +using testing::AtLeast; +using testing::Invoke; using testing::Return; using testing::StrEq; @@ -39,8 +41,15 @@ namespace firebolt::rialto::server::ct class AudioSourceSwitchTest : public MediaPipelineTest { public: - AudioSourceSwitchTest() = default; - ~AudioSourceSwitchTest() = default; + AudioSourceSwitchTest() + { + GstElementFactory *elementFactory = gst_element_factory_find("fakesrc"); + m_audioSink = gst_element_factory_create(elementFactory, nullptr); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_audioSink))).WillRepeatedly(Return("audio_sink")); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)).Times(AtLeast(0)); + gst_object_unref(elementFactory); + } + ~AudioSourceSwitchTest() override { gst_object_unref(m_audioSink); } void willSwitchAudioSource() { @@ -61,6 +70,7 @@ class AudioSourceSwitchTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(FALSE)); EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&m_oldCaps)).WillOnce(Return(&m_oldCapsStr)); EXPECT_CALL(*m_glibWrapperMock, gFree(&m_oldCapsStr)); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(_, _, _, _, _, _, _, _, _, _, kSvpEnabled, GST_ELEMENT(&m_audioAppSrc), _)) @@ -179,7 +189,6 @@ TEST_F(AudioSourceSwitchTest, SwitchAudioSource) switchAudioSource(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp index 7daa9fad3..ffa32cada 100644 --- a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp @@ -42,6 +42,7 @@ const std::string kDummyStateName{"dummy"}; using testing::_; using testing::AtLeast; +using testing::AtMost; using testing::DoAll; using testing::Invoke; using testing::Return; @@ -75,6 +76,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kSecondaryGstPlayFlagsType)) .Times(4) .WillRepeatedly(Return(&m_flagsClass)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_flagsClass)).Times(4); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))) .WillOnce(Return(&m_audioFlag)) .RetiresOnSaturation(); @@ -98,6 +100,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPlaysink)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_secondaryPipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_secondaryPipeline)).Times(AtMost(1)); // In case of longer testruns, GstPlayer may request to query position EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(&m_secondaryPipeline, GST_FORMAT_TIME, _)) @@ -279,7 +282,8 @@ class DualVideoPlaybackTest : public MediaPipelineTest .WillOnce(Return(&m_secondaryBus)); EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(&m_secondaryBus, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryBus)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_secondaryPipeline)).Times(testing::Between(1, 2)); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolStopUnusedThreads()).Times(testing::Between(1, 2)); } void createSecondaryFullSession() @@ -342,7 +346,7 @@ class DualVideoPlaybackTest : public MediaPipelineTest ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_secondarySessionId); EXPECT_EQ(receivedNeedData->source_id(), m_secondaryVideoSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), kFrameCountInPlayingState); + EXPECT_EQ(receivedNeedData->frame_count(), kPrerollNumFrames); m_lastSecondaryNeedData = receivedNeedData; auto receivedPlaybackStateChange{expectedPlaybackStateChange.getMessage()}; @@ -687,8 +691,8 @@ TEST_F(DualVideoPlaybackTest, playbackFullDualVideo) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -731,7 +735,6 @@ TEST_F(DualVideoPlaybackTest, playbackFullDualVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); @@ -940,8 +943,8 @@ TEST_F(DualVideoPlaybackTest, playbackNoResouceManagerSecondaryVideo) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -984,7 +987,6 @@ TEST_F(DualVideoPlaybackTest, playbackNoResouceManagerSecondaryVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); diff --git a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp index 89d8f9fae..e6334c454 100644 --- a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp @@ -29,7 +29,6 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCount{24}; } // namespace using testing::_; @@ -300,8 +299,8 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushEncryptedAudioData(kFrameCount); - pushEncryptedVideoData(kFrameCount); + pushEncryptedAudioData(kPrerollNumFrames); + pushEncryptedVideoData(kPrerollNumFrames); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -319,8 +318,8 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) // Step 10: Write 1 encrypted audio frame // Step 11: Write 1 encrypted video frame - pushEncryptedAudioData(kFrameCount); - pushEncryptedVideoData(kFrameCount); + pushEncryptedAudioData(kFrameCountInPlayingState); + pushEncryptedVideoData(kFrameCountInPlayingState); // Step 12: End of audio stream // Step 13: End of video stream @@ -331,7 +330,6 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp new file mode 100644 index 000000000..62d2341c6 --- /dev/null +++ b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp @@ -0,0 +1,380 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "ExpectMessage.h" +#include "Matchers.h" +#include "MediaPipelineTest.h" +#include + +#include +#include + +using testing::_; +using testing::Invoke; +using testing::Return; +using testing::StrEq; + +namespace +{ +constexpr unsigned kFramesToPush{1}; +const std::string kElementName{"Decoder"}; +constexpr gulong kSignalId{123}; +} // namespace + +namespace firebolt::rialto::server::ct +{ +class FirstFrameNotificationTest : public MediaPipelineTest +{ +public: + FirstFrameNotificationTest() + { + m_elementFactory = gst_element_factory_find("fakesrc"); + m_videoDecoder = gst_element_factory_create(m_elementFactory, nullptr); + m_audioDecoder = gst_element_factory_create(m_elementFactory, nullptr); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(_)).WillRepeatedly(Return(m_elementFactory)); + } + + ~FirstFrameNotificationTest() override + { + gst_object_unref(m_audioDecoder); + gst_object_unref(m_videoDecoder); + gst_object_unref(m_elementFactory); + } + + void setupElementsCommon(const char *signalName) + { + EXPECT_CALL(*m_glibWrapperMock, gTypeName(_)).WillRepeatedly(Return(kElementName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstIsBaseParse(_)).WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_glibWrapperMock, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return m_signals; + })); + EXPECT_CALL(*m_glibWrapperMock, gSignalQuery(m_signals[0], _)) + .WillRepeatedly(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = signalName; })); + EXPECT_CALL(*m_glibWrapperMock, gFree(m_signals)).Times(2); + } + + void willSetupVideoDecoder() + { + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(m_videoDecoder)).WillOnce(Return(m_videoDecoder)); + + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_elementFactory, _)) + .WillRepeatedly(Invoke( + [](GstElementFactory *, GstElementFactoryListType type) + { + if (type == GST_ELEMENT_FACTORY_TYPE_DECODER) + return true; + if (type == GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) + return true; + return false; + })); + + EXPECT_CALL(*m_glibWrapperMock, gObjectType(m_videoDecoder)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*m_glibWrapperMock, gSignalConnect(_, _, _, _)) + .WillRepeatedly(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + if (std::strcmp(detailed_signal, "first-video-frame-callback") == 0) + { + m_firstVideoFrameCallback = c_handler; + m_firstVideoFrameData = data; + } + return kSignalId; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_videoDecoder)) + .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); + } + + void setupVideoDecoder() + { + m_gstreamerStub.setupElement(m_videoDecoder); + waitWorker(); + } + + void firstVideoFrameReceived() + { + if (!m_firstVideoFrameCallback || !m_firstVideoFrameData) + { + return; + } + + ExpectMessage expectedFirstFrameReceived{m_clientStub}; + expectedFirstFrameReceived.setFilter([&](const auto &msg) { return msg.source_id() == m_videoSourceId; }); + reinterpret_cast( + m_firstVideoFrameCallback)(m_videoDecoder, 0, nullptr, m_firstVideoFrameData); + + auto receivedFirstFrameReceived{expectedFirstFrameReceived.getMessage()}; + ASSERT_TRUE(receivedFirstFrameReceived); + EXPECT_EQ(receivedFirstFrameReceived->session_id(), m_sessionId); + EXPECT_EQ(receivedFirstFrameReceived->source_id(), m_videoSourceId); + } + + void willSetupAudioDecoder() + { + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(m_audioDecoder)).WillOnce(Return(m_audioDecoder)); + + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_elementFactory, _)) + .WillRepeatedly(Invoke( + [](GstElementFactory *, GstElementFactoryListType type) + { + if (type == GST_ELEMENT_FACTORY_TYPE_DECODER) + return true; + if (type == GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) + return true; + if (type == (GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + return true; + return false; + })); + + EXPECT_CALL(*m_glibWrapperMock, gObjectType(m_audioDecoder)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*m_glibWrapperMock, gSignalConnect(_, _, _, _)) + .WillRepeatedly(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + if (std::strcmp(detailed_signal, "first-audio-frame") == 0 || + std::strcmp(detailed_signal, "first-audio-frame-callback") == 0) + { + m_firstFrameCallback = c_handler; + m_firstFrameData = data; + } + return kSignalId; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioDecoder)) + .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); + } + + void setupAudioDecoder() + { + m_gstreamerStub.setupElement(m_audioDecoder); + waitWorker(); + } + + void firstAudioFrameReceived() + { + if (!m_firstFrameCallback || !m_firstFrameData) + { + return; + } + + ExpectMessage expectedFirstFrameReceived{m_clientStub}; + expectedFirstFrameReceived.setFilter([&](const auto &msg) { return msg.source_id() == m_audioSourceId; }); + reinterpret_cast( + m_firstFrameCallback)(m_audioDecoder, 0, nullptr, m_firstFrameData); + + auto receivedFirstFrameReceived{expectedFirstFrameReceived.getMessage()}; + ASSERT_TRUE(receivedFirstFrameReceived); + EXPECT_EQ(receivedFirstFrameReceived->session_id(), m_sessionId); + EXPECT_EQ(receivedFirstFrameReceived->source_id(), m_audioSourceId); + } + + void createAndLoadSession() + { + createSession(); + gstPlayerWillBeCreated(); + load(); + } + + void waitForBufferedState(const std::function &pushFrame) + { + ExpectMessage expectedNetworkStateChange{m_clientStub}; + + pushFrame(); + + auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; + ASSERT_TRUE(receivedNetworkStateChange); + EXPECT_EQ(receivedNetworkStateChange->session_id(), m_sessionId); + EXPECT_EQ(receivedNetworkStateChange->state(), ::firebolt::rialto::NetworkStateChangeEvent_NetworkState_BUFFERED); + + willNotifyPaused(); + notifyPaused(); + } + + void finishStreamAndDestroy(GstAppSrc *appSrc, std::int32_t sourceId, const std::function &sendEos) + { + willEos(appSrc); + sendEos(kFramesToPush); + gstNotifyEos(); + removeSource(sourceId); + willStop(); + stop(); + gstPlayerWillBeDestructed(); + destroySession(); + } + +private: + GstElementFactory *m_elementFactory{nullptr}; + GstElement *m_videoDecoder{nullptr}; + GstElement *m_audioDecoder{nullptr}; + guint m_signals[1]{123}; + GCallback m_firstVideoFrameCallback{nullptr}; + gpointer m_firstVideoFrameData{nullptr}; + GCallback m_firstFrameCallback{nullptr}; + gpointer m_firstFrameData{nullptr}; +}; + +/* + * Component Test: First frame notification test + * Test Objective: + * Test if Rialto Server handles gstreamer first frame signals correctly. The notification should be forwarded to + * Rialto Client with FirstFrameReceivedEvent message. + * + * Sequence Diagrams: + * First frame notification + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Set Rialto Server to Active + * Connect Rialto Client Stub + * Map Shared Memory + * + * Test Steps: + * Step 1: Create a new media session + * Send CreateSessionRequest to Rialto Server + * Expect that successful CreateSessionResponse is received + * Save returned session id + * + * Step 2: Load content + * Send LoadRequest to Rialto Server + * Expect that successful LoadResponse is received + * Expect that GstPlayer instance is created. + * Expect that client is notified that the NetworkState has changed to BUFFERING. + * + * Step 3: Setup Video Decoder + * Call SetupElement callback with Video Decoder + * First frame callback should be registered. + * + * Step 4: Attach video source + * Attach the video source. + * Expect that video source is attached. + * Expect that rialto source is setup. + * Expect that all sources are attached. + * Expect that the Playback state has changed to IDLE. + * + * Step 5: Pause + * Pause the content. + * Expect that gstreamer pipeline is paused. + * + * Step 6: Write 1 video frame + * Gstreamer Stub notifies, that it needs video data. + * Expect that server notifies the client that it needs 3 frames of video data. + * Write 1 frame of video data to the shared buffer. + * Send HaveData message. + * Expect that server notifies the client that it needs 3 frames of video data. + * + * Step 7: Notify buffered and Paused + * Expect that server notifies the client that the Network state has changed to BUFFERED. + * Gstreamer Stub notifies, that pipeline state is in PAUSED state. + * Expect that server notifies the client that the Network state has changed to PAUSED. + * + * Step 8: First video frame received + * Rialto Server will receive first video frame signal. + * Rialto Server should send FirstFrameReceivedEvent with video source. + * + * Step 9: End of video stream + * Send video haveData with one frame and EOS status. + * Expect that Gstreamer is notified about end of stream. + * + * Step 10: Notify end of stream + * Simulate, that gst_message_eos is received by Rialto Server. + * Expect that server notifies the client that the Network state has changed to END_OF_STREAM. + * + * Step 11: Remove source + * Remove the video source. + * Expect that video source is removed. + * + * Step 12: Stop + * Stop the playback. + * Expect that stop propagated to the gstreamer pipeline. + * Expect that server notifies the client that the Playback state has changed to STOPPED. + * + * Step 13: Destroy media session + * Send DestroySessionRequest. + * Expect that the session is destroyed on the server. + * + * Test Teardown: + * Memory region created for the shared buffer is unmapped. + * Server is terminated. + * + * Expected Results: + * First frame signal is handled by Rialto Server. + * + * Code: + */ +TEST_F(FirstFrameNotificationTest, firstFrameNotification) +{ + createAndLoadSession(); + + setupElementsCommon("first-video-frame-callback"); + willSetupVideoDecoder(); + setupVideoDecoder(); + + videoSourceWillBeAttached(); + attachVideoSource(); + sourceWillBeSetup(); + setupSource(); + willSetupAndAddSource(&m_videoAppSrc); + willFinishSetupAndAddSource(); + indicateAllSourcesAttached({&m_videoAppSrc}); + + willPause(); + pause(); + + waitForBufferedState([&]() { pushVideoData(kFramesToPush); }); + + firstVideoFrameReceived(); + + finishStreamAndDestroy(&m_videoAppSrc, m_videoSourceId, [&](unsigned framesToPush) { eosVideo(framesToPush); }); +} + +TEST_F(FirstFrameNotificationTest, firstAudioFrameNotification) +{ + createAndLoadSession(); + + setupElementsCommon("first-audio-frame"); + willSetupAudioDecoder(); + setupAudioDecoder(); + + audioSourceWillBeAttached(); + attachAudioSource(); + sourceWillBeSetup(); + setupSource(); + willSetupAndAddSource(&m_audioAppSrc); + willFinishSetupAndAddSource(); + indicateAllSourcesAttached({&m_audioAppSrc}); + + willPause(); + pause(); + + waitForBufferedState([&]() { pushAudioData(kFramesToPush); }); + + firstAudioFrameReceived(); + + finishStreamAndDestroy(&m_audioAppSrc, m_audioSourceId, [&](unsigned framesToPush) { eosAudio(framesToPush); }); +} +} // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp index 39ef64847..eefc8c03c 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp @@ -34,6 +34,7 @@ constexpr bool kAsync{true}; } // namespace using testing::_; +using testing::AtLeast; using testing::Invoke; using testing::Return; using testing::StrEq; @@ -45,13 +46,14 @@ class FlushTest : public MediaPipelineTest GstEvent m_flushStartEvent{}; GstEvent m_flushStopEvent{}; GstSegment m_segment{}; - GstElement *m_audioSink{nullptr}; public: FlushTest() { GstElementFactory *elementFactory = gst_element_factory_find("fakesrc"); m_audioSink = gst_element_factory_create(elementFactory, nullptr); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_audioSink))).WillRepeatedly(Return("audio_sink")); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)).Times(AtLeast(0)); gst_object_unref(elementFactory); } @@ -59,14 +61,6 @@ class FlushTest : public MediaPipelineTest void willFlush() { - EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) - .WillOnce(Invoke( - [&](gpointer object, const gchar *first_property_name, void *element) - { - GstElement **elementPtr = reinterpret_cast(element); - *elementPtr = m_audioSink; - })); - EXPECT_CALL(*m_glibWrapperMock, gTypeName(_)).WillRepeatedly(Return("GstStreamVolume")); EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(m_audioSink, StrEq("async"), _)) .WillOnce(Invoke( @@ -75,7 +69,6 @@ class FlushTest : public MediaPipelineTest gboolean *asyncPtr = reinterpret_cast(element); *asyncPtr = static_cast(kAsync); })); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)); EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) .WillOnce(Return(true)); @@ -303,7 +296,6 @@ TEST_F(FlushTest, flushAudioSourceSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp index 3b2cbdf7b..e96458c3f 100644 --- a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp @@ -26,7 +26,7 @@ namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{24}; +constexpr int kTestFrameCount{3}; } // namespace namespace firebolt::rialto::server::ct @@ -50,7 +50,7 @@ class HaveDataFailureTest : public MediaPipelineTest ASSERT_TRUE(receivedNeedData); EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); EXPECT_EQ(receivedNeedData->source_id(), needData->source_id()); - EXPECT_EQ(receivedNeedData->frame_count(), kFrameCountInPausedState); + EXPECT_EQ(receivedNeedData->frame_count(), kTestFrameCount); needData = receivedNeedData; } }; @@ -168,7 +168,6 @@ TEST_F(HaveDataFailureTest, HaveDataError) failHaveData(m_lastVideoNeedData); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp index 4f6bebdf6..4f155cd90 100644 --- a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp @@ -159,7 +159,6 @@ TEST_F(MuteTest, Mute) getMute(); // Step 7: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp index 233c27409..f448344f4 100644 --- a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp @@ -286,7 +286,6 @@ TEST_F(NonFatalPlayerErrorUpdatesTest, warningMessage) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index dc42d23ae..66a3c8838 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -25,6 +25,7 @@ #include "MessageBuilders.h" using testing::_; +using testing::AnyNumber; using testing::Invoke; using testing::Return; using testing::StrEq; @@ -39,6 +40,7 @@ constexpr bool kSyncOff{true}; constexpr int32_t kStreamSyncMode{1}; constexpr int32_t kBufferingLimit{4321}; constexpr bool kUseBuffering{true}; +constexpr int64_t kDuration{434523241}; } // namespace namespace firebolt::rialto::server::ct @@ -159,6 +161,16 @@ class PipelinePropertyTest : public MediaPipelineTest *returnVal = value ? TRUE : FALSE; })); } + else if constexpr (std::is_same_v) + { + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(propertyName.c_str()), _)) + .WillOnce(Invoke( + [&](gpointer, const gchar *, void *val) + { + guint *returnVal = reinterpret_cast(val); + *returnVal = value; + })); + } else if constexpr (std::is_same_v) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(propertyName.c_str()), _)) @@ -175,15 +187,20 @@ class PipelinePropertyTest : public MediaPipelineTest void willFailToSetSinkProperty() { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(&m_pipeline, _, _)) - .WillOnce(Invoke( + .Times(AnyNumber()) + .WillRepeatedly(Invoke( [&](gpointer object, const gchar *first_property_name, void *element) { GstElement **elementPtr = reinterpret_cast(element); *elementPtr = m_element; })); - EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_element))).WillOnce(Return(kElementTypeName.c_str())); - EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, _)).WillOnce(Return(nullptr)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_element))) + .Times(AnyNumber()) + .WillRepeatedly(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, _)).Times(AnyNumber()).WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &MediaPipelineTest::workerFinished)); } void willFailToSetDecoderProperty() @@ -215,6 +232,24 @@ class PipelinePropertyTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); } + void willGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *duration) + { + *duration = kDuration; + workerFinished(); + return TRUE; + })); + } + + void willFailToGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(DoAll(Invoke(this, &MediaPipelineTest::workerFinished), Return(FALSE))); + } + void setImmediateOutput() { auto req{createSetImmediateOutputRequest(m_sessionId, m_videoSourceId, kImmediateOutput)}; @@ -343,7 +378,7 @@ class PipelinePropertyTest : public MediaPipelineTest void getSyncFailure() { - auto req{createGetSyncRequest(m_sessionId)}; + auto req{createGetSyncRequest(m_sessionId + 1)}; ConfigureAction(m_clientStub).send(req).expectFailure(); waitWorker(); } @@ -395,6 +430,23 @@ class PipelinePropertyTest : public MediaPipelineTest ConfigureAction(m_clientStub).send(req).expectFailure(); } + void getDurationSuccess() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub) + .send(req) + .expectSuccess() + .matchResponse([&](const auto &resp) { EXPECT_EQ(resp.duration(), kDuration); }); + waitWorker(); + } + + void getDurationFailure() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub).send(req).expectFailure(); + waitWorker(); + } + private: GstElement *m_element{nullptr}; GstStructure m_testStructure; @@ -488,18 +540,21 @@ class PipelinePropertyTest : public MediaPipelineTest * Step 15: Get Use Buffering * Will get the UseBuffering property of the decodebin on the Rialto Server * - * Step 16: Remove sources + * Step 16: Get Duration + * Will get the duration of the playback on the Rialto Server + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -579,8 +634,11 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) // Step 15: Get Use Buffering getUseBuffering(); - // Step 16: Remove sources - willRemoveAudioSource(); + // Step 16: Get Duration + willGetDuration(); + getDurationSuccess(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -588,7 +646,7 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) willStop(); stop(); - // Step 19: Destroy media session + // Step 18: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } @@ -694,18 +752,22 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) * Rialto client sends UseBufferingRequest and waits for response * UseBufferingResponse is false because the sessionId is wrong * - * Step 16: Remove sources + * Step 16: Fail to Get Duration + * Rialto client sends GetDurationRequest and waits for response + * GetDurationResponse is false because the server couldn't process it + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -756,7 +818,6 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) setSyncFailure(); // Step 8: Fail to get Sync - willFailToGetSink(); getSyncFailure(); // Step 9: Fail to set Sync Off @@ -785,16 +846,19 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) // Step 15: Fail to Set Use Buffering setUseBufferingFailure(); - // Step 16: Remove sources - willRemoveAudioSource(); + // Step 16: Fail to Get Duration + willFailToGetDuration(); + getDurationFailure(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 17: Stop + // Step 18: Stop willStop(); stop(); - // Step 18: Destroy media session + // Step 19: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp index d487d4f1c..8c40ab32c 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PlayPauseStopFailuresTest.cpp @@ -28,7 +28,7 @@ using testing::Return; namespace { constexpr unsigned kFramesToPush{1}; -constexpr int kFrameCountInPausedState{3}; +constexpr int kPrerollNumFrames{3}; } // namespace namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp index c82a0ac8a..1f713c22b 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp @@ -180,8 +180,8 @@ TEST_F(MediaPipelineTest, playback) { ExpectMessage expectedNetworkStateChange{m_clientStub}; - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kPrerollNumFrames); + pushVideoData(kFramesToPush, kPrerollNumFrames); auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; ASSERT_TRUE(receivedNetworkStateChange); @@ -199,8 +199,8 @@ TEST_F(MediaPipelineTest, playback) // Step 10: Write 1 audio frame // Step 11: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 12: End of audio stream // Step 13: End of video stream @@ -211,7 +211,6 @@ TEST_F(MediaPipelineTest, playback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp index e683a603b..f4509e43f 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp @@ -227,8 +227,8 @@ TEST_F(PositionUpdatesTest, PositionUpdate) // Step 9: Write 1 audio frame // Step 10: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 11: Expect position update waitForPositionUpdate(); @@ -244,7 +244,6 @@ TEST_F(PositionUpdatesTest, PositionUpdate) gstNotifyEos(); // Step 15: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -424,8 +423,8 @@ TEST_F(PositionUpdatesTest, GetPositionSuccess) // Step 9: Write 1 audio frame // Step 10: Write 1 video frame - pushAudioData(kFramesToPush); - pushVideoData(kFramesToPush); + pushAudioData(kFramesToPush, kFrameCountInPlayingState); + pushVideoData(kFramesToPush, kFrameCountInPlayingState); // Step 11: Get Position getPosition(); @@ -442,7 +441,6 @@ TEST_F(PositionUpdatesTest, GetPositionSuccess) // Step 18: Remove sources // Step 19: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -548,8 +546,7 @@ TEST_F(PositionUpdatesTest, getPositionFailure) willSetStateInvalidForQueryPosition(); getPositionFailure(); - // Step 7: Remove sources - willRemoveAudioSource(); + // Step 7: Remove sources, kFrameCountInPlayingState removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp index eefd6a982..f3503df5a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp @@ -342,7 +342,6 @@ TEST_F(QosUpdatesTest, QosUpdates) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); @@ -526,7 +525,6 @@ TEST_F(QosUpdatesTest, StatsFailure) // Step 12: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 13: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp index 4c73eeafe..9a8987e05 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp @@ -189,7 +189,6 @@ TEST_F(RenderFrameTest, RenderFrameSuccess) renderFrame(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -303,7 +302,6 @@ TEST_F(RenderFrameTest, renderFrameFailure) renderFrameFailure(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp index a93b0d624..26eae1041 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp @@ -29,7 +29,7 @@ namespace constexpr unsigned kFramesToPush{1}; constexpr int kPositionInPaused{10}; constexpr int kPositionInPlaying{0}; -constexpr double kPlaybackRate{1.0}; +constexpr double kSeekPlaybackRate{1.0}; } // namespace using testing::Return; @@ -44,7 +44,7 @@ class SetPositionTest : public MediaPipelineTest void willSetPosition(std::int64_t position) { - EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kPlaybackRate, GST_FORMAT_TIME, + EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kSeekPlaybackRate, GST_FORMAT_TIME, static_cast(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) .WillOnce(Return(TRUE)); @@ -99,7 +99,7 @@ class SetPositionTest : public MediaPipelineTest void willFailToSetPosition() { - EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kPlaybackRate, GST_FORMAT_TIME, + EXPECT_CALL(*m_gstWrapperMock, gstElementSeek(&m_pipeline, kSeekPlaybackRate, GST_FORMAT_TIME, static_cast(GST_SEEK_FLAG_FLUSH), GST_SEEK_TYPE_SET, kPositionInPaused, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) .WillOnce(Return(FALSE)); @@ -311,7 +311,6 @@ TEST_F(SetPositionTest, SetPosition) gstNotifyEos(); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -463,7 +462,6 @@ TEST_F(SetPositionTest, SetPositionFailure) SetPositionFailure(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp index ca5f2c3b0..96f36b0a8 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp @@ -227,7 +227,6 @@ TEST_F(SetSourcePositionTest, setSourcePositionSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp index 326842175..66e66bf1e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp @@ -173,7 +173,6 @@ TEST_F(SetVideoWindowTest, SetVideoWindow) setVideoWindow(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp index dfa103706..a956bed92 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp @@ -99,7 +99,6 @@ TEST_F(MediaPipelineTest, shouldAttachAudioSourceOnly) indicateAllSourcesAttached({&m_audioAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 5: Stop @@ -196,7 +195,6 @@ TEST_F(MediaPipelineTest, shouldAttachBothSources) indicateAllSourcesAttached({&m_audioAppSrc, &m_videoAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp similarity index 57% rename from tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp rename to tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp index f5d613c95..1959300c1 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp @@ -17,71 +17,110 @@ * limitations under the License. */ +#include "ActionTraits.h" +#include "ConfigureAction.h" #include "Constants.h" #include "ExpectMessage.h" #include "Matchers.h" #include "MediaPipelineTest.h" +#include "MessageBuilders.h" using testing::_; +using testing::Invoke; using testing::Return; using testing::StrEq; namespace { constexpr int kFramesToPush{3}; +constexpr bool kResetTime{false}; +constexpr bool kAsync{true}; } // namespace namespace firebolt::rialto::server::ct { -class RemoveAudioPlaybackTest : public MediaPipelineTest +class SwitchAudioPlaybackTest : public MediaPipelineTest { public: - RemoveAudioPlaybackTest() = default; - ~RemoveAudioPlaybackTest() = default; + SwitchAudioPlaybackTest() = default; + ~SwitchAudioPlaybackTest() = default; - void willReattachAudioSource() + void willFlushAudioSource() { - EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&m_flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) + .WillOnce(Return(true)); + } + + void flushAudioSource() + { + // After successful Flush procedure, SourceFlushedEvent is sent. + ExpectMessage expectedSourceFlushed{m_clientStub}; + expectedSourceFlushed.setFilter([&](const SourceFlushedEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // After successful Flush, NeedData for source is sent. + ExpectMessage expectedAudioNeedData{m_clientStub}; + expectedAudioNeedData.setFilter([&](const NeedMediaDataEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // Send FlushRequest and expect success + auto request{createFlushRequest(m_sessionId, m_audioSourceId, kResetTime)}; + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const FlushResponse &response) { EXPECT_EQ(kAsync, response.async()); }); + + // Check received SourceFlushedEvent events + auto receivedSourceFlushed{expectedSourceFlushed.getMessage()}; + ASSERT_TRUE(receivedSourceFlushed); + EXPECT_EQ(receivedSourceFlushed->session_id(), m_sessionId); + EXPECT_EQ(receivedSourceFlushed->source_id(), m_audioSourceId); + + // Check received NeedDataReqs + auto receivedAudioNeedData{expectedAudioNeedData.getMessage()}; + ASSERT_TRUE(receivedAudioNeedData); + EXPECT_EQ(receivedAudioNeedData->session_id(), m_sessionId); + EXPECT_EQ(receivedAudioNeedData->source_id(), m_audioSourceId); + m_lastAudioNeedData = receivedAudioNeedData; + } + + void willSwitchAudioSource() + { + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); - EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_oldCaps)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_oldCaps)); + gstCapsSetSimpleIntStub(&m_newCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_newCaps, &m_audioCaps)).WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_audioCaps)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); - - willSetAudioAndVideoFlags(); } - void reattachAudioSource() + void switchAudioSource() { - ExpectMessage expectedNeedData{m_clientStub}; - - attachAudioSource(); - - auto receivedNeedData{expectedNeedData.getMessage()}; - ASSERT_TRUE(receivedNeedData); - EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); - EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), 24); - m_lastAudioNeedData = receivedNeedData; + auto attachAudioSourceReq{createAttachAudioSourceRequest(m_sessionId)}; + attachAudioSourceReq.set_switch_source(true); + ConfigureAction(m_clientStub).send(attachAudioSourceReq).expectSuccess(); + waitWorker(); } private: - GstCaps m_oldCaps{}; - gchar m_oldCapsStr{}; + GstCaps m_newCaps{}; }; /* - * Component Test: Playback content when audio source has been removed and reattached. + * Component Test: Playback content when audio source has been switched. * Test Objective: - * Test that video only playback can continue if the audio source is removed, and that audio can be restarted - * when it is reattached. + * Test that audio source can be switched mid playback and that video playback is unaffected. * * Sequence Diagrams: * Rialto Dynamic Audio Stream Switching @@ -138,49 +177,35 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * Expect that gstreamer pipeline is paused. * Expect that server notifies the client that the Network state has changed to PAUSED. * - * Step 8: Remove Audio Source - * Remove the audio source. - * Expect that audio source is removed. + * Step 8: Flush Audio Source + * Flush the audio source. + * Expect that audio source is flushed. * - * Step 9: Write video frames - * Write video frames. + * Step 9: Switch Audio Source + * Switch the audio source. + * Expect that audio source is switched. * - * Step 10: Play - * Play the content. - * Expect that gstreamer pipeline is playing. - * Expect that server notifies the client that the Network state has changed to PLAYING. - * - * Step 11: Pause - * Pause the content. - * Expect that gstreamer pipeline is paused. - * Expect that server notifies the client that the Network state has changed to PAUSED. - * - * Step 12: Reattach audio source - * Attach the audio source again. - * Expect that reattach procedure is triggered. - * Expect that audio source is attached. - * - * Step 13: Write video and audio frames + * Step 10: Write video and audio frames * Write video frames. * Write audio frames. * - * Step 14: Play + * Step 11: Play * Play the content. * Expect that gstreamer pipeline is playing. * Expect that server notifies the client that the Network state has changed to PLAYING. * - * Step 15: Remove sources + * Step 12: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 16: Stop + * Step 13: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 17: Destroy media session + * Step 14: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -194,7 +219,7 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * * Code: */ -TEST_F(RemoveAudioPlaybackTest, RemoveAudio) +TEST_F(SwitchAudioPlaybackTest, SwitchAudio) { // Step 1: Create a new media session createSession(); @@ -243,46 +268,33 @@ TEST_F(RemoveAudioPlaybackTest, RemoveAudio) pause(); willNotifyPaused(); notifyPaused(); + GST_STATE(&m_pipeline) = GST_STATE_PAUSED; - // Step 8: Remove Audio Source - willRemoveAudioSource(); - removeSource(m_audioSourceId); - - // Step 9: Write video frames - pushVideoData(kFramesToPush); - - // Step 10: Play - willPlay(); - play(); - - // Step 11: Pause - willPause(); - pause(); - willNotifyPaused(); - notifyPaused(); + // Step 8: Flush Audio Source + willFlushAudioSource(); + flushAudioSource(); - // Step 12: Reattach audio source - willReattachAudioSource(); - reattachAudioSource(); + // Step 9: Switch Audio Source + willSwitchAudioSource(); + switchAudioSource(); - // Step 13: Write video and audio frames + // Step 10: Write video and audio frames pushAudioData(kFramesToPush); pushVideoData(kFramesToPush); - // Step 14: Play + // Step 11: Play willPlay(); play(); - // Step 15: Remove sources - willRemoveAudioSource(); + // Step 12: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 16: Stop + // Step 13: Stop willStop(); stop(); - // Step 17: Destroy media session + // Step 14: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp index 00eb3d6b6..8d0560d3e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp @@ -65,6 +65,10 @@ class UnderflowTest : public MediaPipelineTest gstElementFactoryListIsType(m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, + gstElementFactoryListIsType(m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*m_glibWrapperMock, gSignalListIds(_, _)) .WillRepeatedly(Invoke( [&](GType itype, guint *n_ids) @@ -75,7 +79,7 @@ class UnderflowTest : public MediaPipelineTest EXPECT_CALL(*m_glibWrapperMock, gSignalQuery(m_signals[0], _)) .WillRepeatedly(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); - EXPECT_CALL(*m_glibWrapperMock, gFree(m_signals)).Times(2); + EXPECT_CALL(*m_glibWrapperMock, gFree(m_signals)).Times(4); } void willSetupAudioDecoder() @@ -400,7 +404,6 @@ TEST_F(UnderflowTest, underflow) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp index 80df4d42b..d54ee116a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp @@ -253,7 +253,6 @@ TEST_F(VolumeTest, Volume) getVolume(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp index 15b0015a1..2c8658313 100644 --- a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp @@ -194,8 +194,8 @@ TEST_F(MediaPipelineTest, WriteSegments) // Step 9: Write 3 audio frames // Step 10: Write 3 video frames - pushAudioData(kFramesToPushBeforePreroll); - pushVideoData(kFramesToPushBeforePreroll); + pushAudioData(kFramesToPushBeforePreroll, kFrameCountInPlayingState); + pushVideoData(kFramesToPushBeforePreroll, kFrameCountInPlayingState); // Step 11: Send 4 frames and end of audio stream // Step 12: Send 4 frames and end of video stream @@ -206,7 +206,6 @@ TEST_F(MediaPipelineTest, WriteSegments) // Step 13: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 14: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp b/tests/componenttests/server/tests/mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp index acfcef40b..b95884744 100644 --- a/tests/componenttests/server/tests/mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp @@ -39,6 +39,7 @@ const char *kAudioFade = "audio-fade"; const GstElementFactoryListType kExpectedFactoryListType{ GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO}; +const GType kDummyType{3}; }; // namespace namespace firebolt::rialto::server::ct { @@ -66,8 +67,9 @@ class MediaPipelineCapabilitiesTest : public RialtoServerComponentTest EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListGetElements(kExpectedFactoryListType, GST_RANK_NONE)) .WillOnce(Return(m_listOfFactories)); // The next calls should ensure that an object is created and then freed - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryCreate(m_elementFactory, nullptr)).WillOnce(Return(&m_object)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_object)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryGetElementType(m_elementFactory)).WillOnce(Return(kDummyType)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kDummyType)).WillOnce(Return(&m_elementClass)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_elementClass)); EXPECT_CALL(*m_glibWrapperMock, gObjectClassListProperties(_, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(kNumPropertiesOnSink), Return(m_dummyParamsPtr))); @@ -105,6 +107,7 @@ class MediaPipelineCapabilitiesTest : public RialtoServerComponentTest GstElement m_object; GstElementFactory *m_elementFactory; GstRegistry m_registry{}; + GstElementClass m_elementClass{}; }; /* diff --git a/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp b/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp index 1d34fe7e3..cb67c0f6a 100644 --- a/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp +++ b/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp @@ -17,6 +17,7 @@ * limitations under the License. */ +#include #include #include @@ -80,7 +81,12 @@ void WebAudioTestMethods::initShm() ConfigureAction(m_clientStub) .send(getShmReq) .expectSuccess() - .matchResponse([&](const auto &resp) { m_shmHandle.init(resp.fd(), resp.size()); }); + .matchResponse( + [&](const auto &resp) + { + m_shmHandle.init(resp.fd(), resp.size()); + std::memset(m_shmHandle.getShm(), 0, resp.size()); + }); } int WebAudioTestMethods::checkInitialBufferAvailable() diff --git a/tests/unittests/common/mocks/ProfilerFactoryMock.h b/tests/unittests/common/mocks/ProfilerFactoryMock.h new file mode 100644 index 000000000..9f46b47d9 --- /dev/null +++ b/tests/unittests/common/mocks/ProfilerFactoryMock.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 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ + +#include "IProfiler.h" +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerFactoryMock : public IProfilerFactory +{ +public: + ProfilerFactoryMock() = default; + virtual ~ProfilerFactoryMock() = default; + + MOCK_METHOD(std::unique_ptr, createProfiler, (std::string moduleName), (const, override)); +}; +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_FACTORY_MOCK_H_ diff --git a/tests/unittests/common/mocks/ProfilerMock.h b/tests/unittests/common/mocks/ProfilerMock.h new file mode 100644 index 000000000..a8872a150 --- /dev/null +++ b/tests/unittests/common/mocks/ProfilerMock.h @@ -0,0 +1,51 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ +#define FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ + +#include "IProfiler.h" + +#include +#include +#include +#include + +namespace firebolt::rialto::common +{ +class ProfilerMock : public IProfiler +{ +public: + ProfilerMock() = default; + ~ProfilerMock() override = default; + + MOCK_METHOD(bool, isEnabled, (), (const, noexcept, override)); + + MOCK_METHOD(std::optional, record, (const std::string &stage), (override)); + MOCK_METHOD(std::optional, record, (const std::string &stage, const std::string &info), (override)); + + MOCK_METHOD(void, log, (RecordId id), (override)); + + MOCK_METHOD(bool, dumpToFile, (), (const, override)); + + MOCK_METHOD(std::vector, getRecords, (), (const, override)); +}; +} // namespace firebolt::rialto::common + +#endif // FIREBOLT_RIALTO_COMMON_PROFILER_MOCK_H_ diff --git a/tests/unittests/common/unittests/CMakeLists.txt b/tests/unittests/common/unittests/CMakeLists.txt index b9d1ab2a8..b7977dfe4 100644 --- a/tests/unittests/common/unittests/CMakeLists.txt +++ b/tests/unittests/common/unittests/CMakeLists.txt @@ -28,6 +28,7 @@ add_gtests ( # gtest code TimerTests.cpp EventThreadTests.cpp + ProfilerTests.cpp ) target_include_directories( diff --git a/tests/unittests/common/unittests/ProfilerTests.cpp b/tests/unittests/common/unittests/ProfilerTests.cpp new file mode 100644 index 000000000..10ee1cbd5 --- /dev/null +++ b/tests/unittests/common/unittests/ProfilerTests.cpp @@ -0,0 +1,261 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "IProfiler.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace firebolt::rialto::common; + +namespace +{ +std::optional findRecord(const std::vector &records, const std::string &stage, + const std::optional &info = std::nullopt) +{ + const auto it = std::find_if(records.begin(), records.end(), [&](const auto &record) + { return record.stage == stage && (!info.has_value() || record.info == info.value()); }); + + if (it != records.end()) + { + return *it; + } + return std::nullopt; +} +} // namespace + +class ProfilerTests : public ::testing::Test +{ +protected: + void SetUp() override + { + saveEnv("PROFILER_ENABLED", m_originalProfilerEnabled); + saveEnv("PROFILER_DUMP_FILE_NAME", m_originalProfilerDumpFileName); + setenv("PROFILER_ENABLED", "true", 1); + + factory = IProfilerFactory::createFactory(); + ASSERT_TRUE(factory); + + profiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(profiler); + ASSERT_TRUE(profiler->isEnabled()); + } + + void TearDown() override + { + restoreEnv("PROFILER_ENABLED", m_originalProfilerEnabled); + restoreEnv("PROFILER_DUMP_FILE_NAME", m_originalProfilerDumpFileName); + } + + static void saveEnv(const char *name, std::optional &value) + { + const char *envValue = std::getenv(name); + if (envValue) + value = envValue; + else + value = std::nullopt; + } + + static void restoreEnv(const char *name, const std::optional &value) + { + if (value) + setenv(name, value->c_str(), 1); + else + unsetenv(name); + } + + std::shared_ptr factory; + std::unique_ptr profiler; + std::optional m_originalProfilerEnabled; + std::optional m_originalProfilerDumpFileName; +}; + +TEST_F(ProfilerTests, RecordAndFindByStage) +{ + const auto id = profiler->record("Stage1"); + ASSERT_TRUE(id.has_value()); + + const auto found = findRecord(profiler->getRecords(), "Stage1"); + ASSERT_TRUE(found.has_value()); + + EXPECT_EQ(found->id, id.value()); +} + +TEST_F(ProfilerTests, RecordAndFindByStageAndInfo) +{ + const auto id = profiler->record("Stage1", "InfoA"); + ASSERT_TRUE(id.has_value()); + + const auto found = findRecord(profiler->getRecords(), "Stage1", "InfoA"); + ASSERT_TRUE(found.has_value()); + EXPECT_EQ(found->id, id.value()); + + EXPECT_FALSE(findRecord(profiler->getRecords(), "Stage1", "InfoB").has_value()); +} + +TEST_F(ProfilerTests, GetRecordsReturnsRecordedEntries) +{ + const auto id1 = profiler->record("Stage1"); + ASSERT_TRUE(id1.has_value()); + + const auto id2 = profiler->record("Stage2", "Info2"); + ASSERT_TRUE(id2.has_value()); + + const auto records = profiler->getRecords(); + ASSERT_GE(records.size(), 2U); + + const auto &record1 = records[records.size() - 2]; + EXPECT_EQ(record1.moduleName, "UnitTestModule"); + EXPECT_EQ(record1.id, id1.value()); + EXPECT_EQ(record1.stage, "Stage1"); + EXPECT_TRUE(record1.info.empty()); + + const auto &record2 = records[records.size() - 1]; + EXPECT_EQ(record2.moduleName, "UnitTestModule"); + EXPECT_EQ(record2.id, id2.value()); + EXPECT_EQ(record2.stage, "Stage2"); + EXPECT_EQ(record2.info, "Info2"); +} + +TEST_F(ProfilerTests, DumpCreatesFile) +{ + (void)profiler->record("StageDump", "InfoDump"); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_dump_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + ASSERT_TRUE(dumpProfiler->record("StageDump", "InfoDump").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_FALSE(content.empty()); + EXPECT_NE(content.find("StageDump"), std::string::npos); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, DumpAppendsToExistingFile) +{ + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_append_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + ASSERT_TRUE(dumpProfiler->record("Stage1").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + ASSERT_TRUE(dumpProfiler->record("Stage2").has_value()); + ASSERT_TRUE(dumpProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("Stage1"), std::string::npos); + EXPECT_NE(content.find("Stage2"), std::string::npos); + EXPECT_LT(content.find("Stage1"), content.rfind("Stage2")); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, DumpToFileUsesCachedEnvValue) +{ + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_profiler_ut_configured_"} + suffix + ".txt"; + setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); + + auto configuredProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(configuredProfiler); + ASSERT_TRUE(configuredProfiler->record("StageConfigured").has_value()); + ASSERT_TRUE(configuredProfiler->dumpToFile()); + + unsetenv("PROFILER_DUMP_FILE_NAME"); + ASSERT_TRUE(configuredProfiler->record("StageConfiguredCached").has_value()); + ASSERT_TRUE(configuredProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("StageConfigured"), std::string::npos); + EXPECT_NE(content.find("StageConfiguredCached"), std::string::npos); + + std::remove(path.c_str()); +} + +TEST_F(ProfilerTests, StartsEnabledWhenEnvTrue) +{ + setenv("PROFILER_ENABLED", "true", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_TRUE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, StartsDisabledWhenEnvValueInvalid) +{ + setenv("PROFILER_ENABLED", "definitely-not-a-bool", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_FALSE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, StartsDisabledWhenEnvFalse) +{ + setenv("PROFILER_ENABLED", "false", 1); + + auto envProfiler = factory->createProfiler("UnitTestModule"); + + ASSERT_TRUE(envProfiler); + EXPECT_FALSE(envProfiler->isEnabled()); +} + +TEST_F(ProfilerTests, GetRecordsDoesNotContainMissingStage) +{ + ASSERT_TRUE(profiler->record("Stage1").has_value()); + + EXPECT_FALSE(findRecord(profiler->getRecords(), "MissingStage").has_value()); +} + +TEST_F(ProfilerTests, DumpToFileReturnsFalseForInvalidPath) +{ + setenv("PROFILER_DUMP_FILE_NAME", "/proc/rialto_profiler_ut_dump.txt", 1); + auto dumpProfiler = factory->createProfiler("UnitTestModule"); + ASSERT_TRUE(dumpProfiler); + EXPECT_FALSE(dumpProfiler->dumpToFile()); +} + +TEST_F(ProfilerTests, DumpToFileReturnsFalseWhenEnvMissing) +{ + EXPECT_FALSE(profiler->dumpToFile()); +} diff --git a/tests/unittests/common/unittests/TimerTests.cpp b/tests/unittests/common/unittests/TimerTests.cpp index f6f8aa1bb..0825e16ee 100644 --- a/tests/unittests/common/unittests/TimerTests.cpp +++ b/tests/unittests/common/unittests/TimerTests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "ITimer.h" @@ -64,6 +65,33 @@ TEST(TimerTests, ShouldCancelTimer) EXPECT_FALSE(callFlag); } +TEST(TimerTests, ShouldCancelFromSameThread) +{ + std::mutex mtx; + std::condition_variable cv; + bool cancelled = false; + + std::shared_ptr timer; + timer = ITimerFactory::getFactory()->createTimer( + std::chrono::milliseconds{50}, + [&]() + { + timer->cancel(); + { + std::lock_guard lock{mtx}; + cancelled = true; + } + cv.notify_one(); + }, + TimerType::ONE_SHOT); + + std::unique_lock lock{mtx}; + cv.wait_for(lock, kEnoughTimeForTestToComplete, [&] { return cancelled; }); + + EXPECT_TRUE(cancelled); + EXPECT_FALSE(timer->isActive()); +} + TEST(TimerTests, ShouldTimeoutPeriodicTimer) { std::mutex mtx; @@ -88,3 +116,30 @@ TEST(TimerTests, ShouldTimeoutPeriodicTimer) EXPECT_GE(callCounter, 3); } } + +TEST(TimerTests, ShouldCancelPeriodicTimerInCallback) +{ + std::mutex mtx; + { // This scope is required to suppress a false warning from cppcheck (about the mutex above) + std::condition_variable cv; + std::unique_lock lock{mtx}; + unsigned callCounter{0}; + std::unique_ptr timer{ITimerFactory::getFactory()->createTimer( + std::chrono::milliseconds{30}, + [&]() + { + std::unique_lock lock{mtx}; + ++callCounter; + if (callCounter >= 3) + { + timer->cancel(); + cv.notify_one(); + } + }, + TimerType::PERIODIC)}; + EXPECT_TRUE(timer->isActive()); + cv.wait_for(lock, kEnoughTimeForTestToComplete); + std::this_thread::sleep_for(std::chrono::milliseconds{30}); + EXPECT_EQ(callCounter, 3); + } +} diff --git a/tests/unittests/ipc/mocks/IpcServerMock.h b/tests/unittests/ipc/mocks/IpcServerMock.h index d0509ea1b..140ac9339 100644 --- a/tests/unittests/ipc/mocks/IpcServerMock.h +++ b/tests/unittests/ipc/mocks/IpcServerMock.h @@ -34,12 +34,13 @@ class ServerMock : public IServer virtual ~ServerMock() = default; MOCK_METHOD(bool, addSocket, - (const std::string &socketPath, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb), + (const std::string &socketPath, + const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb), (override)); MOCK_METHOD(bool, addSocket, - (int fd, std::function &)> clientConnectedCb, - std::function &)> clientDisconnectedCb), + (int fd, const std::function &)> &clientConnectedCb, + const std::function &)> &clientDisconnectedCb), (override)); MOCK_METHOD(std::shared_ptr, addClient, (int socketFd, std::function &)> clientDisconnectedCb), (override)); diff --git a/tests/unittests/media/client/ipc/CMakeLists.txt b/tests/unittests/media/client/ipc/CMakeLists.txt index c4018151c..8c4835390 100644 --- a/tests/unittests/media/client/ipc/CMakeLists.txt +++ b/tests/unittests/media/client/ipc/CMakeLists.txt @@ -35,8 +35,11 @@ add_gtests ( mediaPipelineIpc/SetPositionTest.cpp mediaPipelineIpc/SetPlaybackRateTest.cpp mediaPipelineIpc/GetPositionTest.cpp + mediaPipelineIpc/GetDurationTest.cpp mediaPipelineIpc/SetImmediateOutputTest.cpp mediaPipelineIpc/GetImmediateOutputTest.cpp + mediaPipelineIpc/SetReportDecodeErrorsTest.cpp + mediaPipelineIpc/GetQueuedFramesTest.cpp mediaPipelineIpc/GetStatsTest.cpp mediaPipelineIpc/RenderFrameTest.cpp mediaPipelineIpc/GetVolumeTest.cpp @@ -99,6 +102,7 @@ add_gtests ( mediaKeysCapabilitiesIpc/CreateTest.cpp mediaKeysCapabilitiesIpc/KeySystemsTest.cpp mediaKeysCapabilitiesIpc/CertificateTest.cpp + mediaKeysCapabilitiesIpc/RobustnessTest.cpp # WebAudioPlayer tests webAudioPlayerIpc/base/WebAudioPlayerIpcTestBase.cpp diff --git a/tests/unittests/media/client/ipc/mediaKeysCapabilitiesIpc/RobustnessTest.cpp b/tests/unittests/media/client/ipc/mediaKeysCapabilitiesIpc/RobustnessTest.cpp new file mode 100644 index 000000000..1353d583b --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaKeysCapabilitiesIpc/RobustnessTest.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 Sky UK + * + * 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 "IpcModuleBase.h" +#include "MediaKeysCapabilitiesIpc.h" +#include +#include +#include +#include + +using namespace firebolt::rialto::client; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::WithArgs; + +namespace +{ +const std::string kKeySystem{"com.netflix.playready"}; +const std::vector kRobustnessLevels{"HW_SECURE_ALL", "SW_SECURE_CRYPTO"}; +} // namespace + +class RialtoClientMediaKeysCapabilitiesIpcRobustnessTest : public IpcModuleBase, public ::testing::Test +{ +protected: + std::unique_ptr m_mediaKeysCapabilitiesIpc; + + RialtoClientMediaKeysCapabilitiesIpcRobustnessTest() + { + expectInitIpc(); + + EXPECT_NO_THROW(m_mediaKeysCapabilitiesIpc = std::make_unique(*m_ipcClientMock)); + EXPECT_NE(m_mediaKeysCapabilitiesIpc, nullptr); + } + +public: + void setRobustnessLevelsInResponse(google::protobuf::Message *response) + { + firebolt::rialto::GetSupportedRobustnessLevelsResponse *robustnessResponse = + dynamic_cast(response); + for (const auto &level : kRobustnessLevels) + { + robustnessResponse->add_robustness_levels(level); + } + } +}; + +/** + * Test that getSupportedRobustnessLevels succeeds and populates the levels from the IPC response. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesIpcRobustnessTest, GetSupportedRobustnessLevelsSuccess) +{ + std::vector levels; + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getSupportedRobustnessLevels"), m_controllerMock.get(), _, _, + m_blockingClosureMock.get())) + .WillOnce(WithArgs<3>( + Invoke(this, &RialtoClientMediaKeysCapabilitiesIpcRobustnessTest::setRobustnessLevelsInResponse))); + + EXPECT_TRUE(m_mediaKeysCapabilitiesIpc->getSupportedRobustnessLevels(kKeySystem, levels)); + EXPECT_EQ(levels, kRobustnessLevels); +} + +/** + * Test that getSupportedRobustnessLevels fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesIpcRobustnessTest, GetSupportedRobustnessLevelsChannelDisconnected) +{ + std::vector levels; + expectIpcApiCallDisconnected(); + + EXPECT_FALSE(m_mediaKeysCapabilitiesIpc->getSupportedRobustnessLevels(kKeySystem, levels)); +} + +/** + * Test that getSupportedRobustnessLevels fails if the ipc channel disconnected and succeeds on reconnect. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesIpcRobustnessTest, GetSupportedRobustnessLevelsReconnectChannel) +{ + std::vector levels; + expectIpcApiCallReconnected(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getSupportedRobustnessLevels"), _, _, _, _)) + .WillOnce(WithArgs<3>( + Invoke(this, &RialtoClientMediaKeysCapabilitiesIpcRobustnessTest::setRobustnessLevelsInResponse))); + + EXPECT_TRUE(m_mediaKeysCapabilitiesIpc->getSupportedRobustnessLevels(kKeySystem, levels)); + EXPECT_EQ(levels, kRobustnessLevels); +} + +/** + * Test that getSupportedRobustnessLevels fails when the ipc controller reports failure. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesIpcRobustnessTest, GetSupportedRobustnessLevelsFailure) +{ + std::vector levels; + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getSupportedRobustnessLevels"), _, _, _, _)); + + EXPECT_FALSE(m_mediaKeysCapabilitiesIpc->getSupportedRobustnessLevels(kKeySystem, levels)); +} diff --git a/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp b/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp index 527f55d4e..c19048ad2 100644 --- a/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp +++ b/tests/unittests/media/client/ipc/mediaKeysIpc/CreateKeySessionTest.cpp @@ -25,7 +25,6 @@ class RialtoClientMediaKeysIpcCreateKeySessionTest : public MediaKeysIpcTestBase { protected: KeySessionType m_keySessionType = KeySessionType::PERSISTENT_LICENCE; - bool m_isLdl = false; RialtoClientMediaKeysIpcCreateKeySessionTest() { createMediaKeysIpc(); } @@ -51,13 +50,12 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, Success) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), m_controllerMock.get(), - createKeySessionRequestMatcher(m_mediaKeysHandle, convertKeySessionType(m_keySessionType), - m_isLdl), + createKeySessionRequestMatcher(m_mediaKeysHandle, convertKeySessionType(m_keySessionType)), _, m_blockingClosureMock.get())) .WillOnce(WithArgs<3>( Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); EXPECT_EQ(returnKeySessionid, m_kKeySessionId); @@ -76,7 +74,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ChannelDisconnected) expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::FAIL); // Reattach channel on destroySession @@ -98,7 +96,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ReconnectChannel) .WillOnce(WithArgs<3>( Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); } @@ -112,7 +110,7 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, Failure) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), _, _, _, _)); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::FAIL); } @@ -128,6 +126,6 @@ TEST_F(RialtoClientMediaKeysIpcCreateKeySessionTest, ErrorReturn) .WillOnce( WithArgs<3>(Invoke(this, &RialtoClientMediaKeysIpcCreateKeySessionTest::setCreateKeySessionResponseFailed))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLdl, returnKeySessionid), + EXPECT_EQ(m_mediaKeysIpc->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionid), m_errorStatus); } diff --git a/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp b/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp index e4de26e0d..9cd4a207f 100644 --- a/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp +++ b/tests/unittests/media/client/ipc/mediaKeysIpc/base/MediaKeysIpcTestBase.cpp @@ -103,7 +103,7 @@ void MediaKeysIpcTestBase::createKeySession() EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("createKeySession"), _, _, _, _)) .WillOnce(WithArgs<3>(Invoke(this, &MediaKeysIpcTestBase::setCreateKeySessionResponseSuccess))); - EXPECT_EQ(m_mediaKeysIpc->createKeySession(KeySessionType::PERSISTENT_LICENCE, m_mediaKeysClientMock, false, + EXPECT_EQ(m_mediaKeysIpc->createKeySession(KeySessionType::PERSISTENT_LICENCE, m_mediaKeysClientMock, returnKeySessionid), MediaKeyErrorStatus::OK); } @@ -165,6 +165,10 @@ ProtoMediaKeyErrorStatus MediaKeysIpcTestBase::convertMediaKeyErrorStatus(firebo { return firebolt::rialto::ProtoMediaKeyErrorStatus::INVALID_STATE; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return firebolt::rialto::ProtoMediaKeyErrorStatus::OUTPUT_RESTRICTED; + } case firebolt::rialto::MediaKeyErrorStatus::FAIL: { return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/CallbackTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/CallbackTest.cpp index de1090470..d37f2c4a7 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/CallbackTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/CallbackTest.cpp @@ -134,6 +134,35 @@ TEST_F(RialtoClientMediaPipelineIpcCallbackTest, InvalidSessionIdQos) m_qosCb(updateQosEvent); } +/** + * Test that a first frame received notification over IPC is forwarded to the client. + */ +TEST_F(RialtoClientMediaPipelineIpcCallbackTest, NotifyFirstFrameReceived) +{ + auto firstFrameReceivedEvent = std::make_shared(); + firstFrameReceivedEvent->set_session_id(m_sessionId); + firstFrameReceivedEvent->set_source_id(m_sourceId); + + EXPECT_CALL(*m_eventThreadMock, addImpl(_)).WillOnce(Invoke([](std::function &&func) { func(); })); + EXPECT_CALL(*m_clientMock, notifyFirstFrameReceived(m_sourceId)); + + m_firstFrameReceivedCb(firstFrameReceivedEvent); +} + +/** + * Test that if the session id of the event is not the same as the playback session the event will be ignored. + */ +TEST_F(RialtoClientMediaPipelineIpcCallbackTest, InvalidSessionIdFirstFrameReceived) +{ + auto firstFrameReceivedEvent = std::make_shared(); + firstFrameReceivedEvent->set_session_id(-1); + firstFrameReceivedEvent->set_source_id(m_sourceId); + + EXPECT_CALL(*m_eventThreadMock, addImpl(_)).WillOnce(Invoke([](std::function &&func) { func(); })); + + m_firstFrameReceivedCb(firstFrameReceivedEvent); +} + /** * Test that a playback error notification over IPC is forwarded to the client. */ diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp new file mode 100644 index 000000000..12d83b993 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp @@ -0,0 +1,97 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcGetDurationTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } +}; + +/** + * Test that getDuration can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), m_controllerMock.get(), + getDurationRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that getDuration fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, GetDurationFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp new file mode 100644 index 000000000..17bdd5c84 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp @@ -0,0 +1,100 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcGetQueuedFramesTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } + + const int32_t m_kSourceId{1}; +}; + +/** + * Test that getQueuedFrames can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, + CallMethod(methodMatcher("getQueuedFrames"), m_controllerMock.get(), + getQueuedFramesRequestMatcher(m_sessionId, m_kSourceId), _, m_blockingClosureMock.get())); + + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} + +/** + * Test that getQueuedFrames fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that getQueuedFrames fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getQueuedFrames"), _, _, _, _)); + + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} + +/** + * Test that getQueuedFrames fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, GetQueuedFramesFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getQueuedFrames"), _, _, _, _)); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp index cd2a96e34..e8cbccbcd 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/LoadTest.cpp @@ -27,6 +27,7 @@ class RialtoClientMediaPipelineIpcLoadTest : public MediaPipelineIpcTestBase firebolt::rialto::LoadRequest_MediaType m_protoType = firebolt::rialto::LoadRequest_MediaType_MSE; const std::string m_mimeType = "mime"; const std::string m_url = "mse://1"; + const bool m_isLive = true; virtual void SetUp() { @@ -51,10 +52,10 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, Success) expectIpcApiCallSuccess(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), m_controllerMock.get(), - loadRequestMatcher(m_sessionId, m_protoType, m_mimeType, m_url), _, + loadRequestMatcher(m_sessionId, m_protoType, m_mimeType, m_url, m_isLive), _, m_blockingClosureMock.get())); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), true); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), true); } /** @@ -65,7 +66,7 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, ChannelDisconnected) expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), false); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), false); // Reattach channel on destroySession EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); @@ -83,7 +84,7 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, ReconnectChannel) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), true); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), true); } /** @@ -95,5 +96,5 @@ TEST_F(RialtoClientMediaPipelineIpcLoadTest, LoadFailure) EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("load"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url), false); + EXPECT_EQ(m_mediaPipelineIpc->load(m_type, m_mimeType, m_url, m_isLive), false); } diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp index 2db4edf0b..dec195144 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp @@ -43,12 +43,13 @@ class RialtoClientMediaPipelineIpcPlayPauseTest : public MediaPipelineIpcTestBas */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) { + bool async{false}; expectIpcApiCallSuccess(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), m_controllerMock.get(), playRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -56,10 +57,11 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) { + bool async{false}; expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); // Reattach channel on destroySession EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); @@ -71,13 +73,14 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) { + bool async{false}; expectIpcApiCallReconnected(); expectUnsubscribeEvents(); expectSubscribeEvents(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -85,11 +88,12 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayFailure) { + bool async{false}; expectIpcApiCallFailure(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); } /** diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..d7e5fb8d2 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp @@ -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 Sky UK + * + * 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 "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } + + const int32_t m_kSourceId{1}; +}; + +/** + * Test that setReportDecodeErrors can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), m_controllerMock.get(), + setReportDecodeErrorsRequestMatcher(m_sessionId, m_kSourceId), _, + m_blockingClosureMock.get())); + + EXPECT_TRUE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + EXPECT_FALSE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that setReportDecodeErrors fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), _, _, _, _)); + + EXPECT_TRUE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, SetReportDecodeErrorsFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), _, _, _, _)); + + EXPECT_FALSE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.cpp index 919729f74..3d0e56854 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.cpp @@ -112,6 +112,15 @@ void MediaPipelineIpcTestBase::expectSubscribeEvents() return static_cast(EventTags::BufferUnderflowEvent); })) .RetiresOnSaturation(); + EXPECT_CALL(*m_channelMock, subscribeImpl("firebolt.rialto.FirstFrameReceivedEvent", _, _)) + .WillOnce(Invoke( + [this](const std::string &eventName, const google::protobuf::Descriptor *descriptor, + std::function &msg)> &&handler) + { + m_firstFrameReceivedCb = std::move(handler); + return static_cast(EventTags::FirstFrameReceivedEvent); + })) + .RetiresOnSaturation(); EXPECT_CALL(*m_channelMock, subscribeImpl("firebolt.rialto.PlaybackErrorEvent", _, _)) .WillOnce(Invoke( [this](const std::string &eventName, const google::protobuf::Descriptor *descriptor, @@ -149,6 +158,7 @@ void MediaPipelineIpcTestBase::expectUnsubscribeEvents() EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::NeedMediaDataEvent))).WillOnce(Return(true)); EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::QosEvent))).WillOnce(Return(true)); EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::BufferUnderflowEvent))).WillOnce(Return(true)); + EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::FirstFrameReceivedEvent))).WillOnce(Return(true)); EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::PlaybackErrorEvent))).WillOnce(Return(true)); EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::SourceFlushedEvent))).WillOnce(Return(true)); EXPECT_CALL(*m_channelMock, unsubscribe(static_cast(EventTags::PlaybackInfoEvent))).WillOnce(Return(true)); diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.h b/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.h index af26f067f..58e3a5898 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.h +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/base/MediaPipelineIpcTestBase.h @@ -65,6 +65,7 @@ class MediaPipelineIpcTestBase : public IpcModuleBase, public ::testing::Test NeedMediaDataEvent, QosEvent, BufferUnderflowEvent, + FirstFrameReceivedEvent, PlaybackErrorEvent, SourceFlushedEvent, PlaybackInfoEvent @@ -77,6 +78,7 @@ class MediaPipelineIpcTestBase : public IpcModuleBase, public ::testing::Test std::function &msg)> m_positionChangeCb; std::function &msg)> m_qosCb; std::function &msg)> m_bufferUnderflowCb; + std::function &msg)> m_firstFrameReceivedCb; std::function &msg)> m_playbackErrorCb; std::function &msg)> m_sourceFlushedCb; std::function &msg)> m_playbackInfoCb; diff --git a/tests/unittests/media/client/main/CMakeLists.txt b/tests/unittests/media/client/main/CMakeLists.txt index 17a5b0d95..8b0de5571 100644 --- a/tests/unittests/media/client/main/CMakeLists.txt +++ b/tests/unittests/media/client/main/CMakeLists.txt @@ -32,8 +32,11 @@ add_gtests ( mediaPipeline/SetPositionTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/GetPositionTest.cpp + mediaPipeline/GetDurationTest.cpp mediaPipeline/SetImmediateOutputTest.cpp mediaPipeline/GetImmediateOutputTest.cpp + mediaPipeline/SetReportDecodeErrorsTest.cpp + mediaPipeline/GetQueuedFramesTest.cpp mediaPipeline/GetStatsTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetVolumeTest.cpp @@ -71,6 +74,7 @@ add_gtests ( mediaKeysCapabilities/CreateTest.cpp mediaKeysCapabilities/KeySystemsTest.cpp mediaKeysCapabilities/CertificateTest.cpp + mediaKeysCapabilities/RobustnessTest.cpp # ClientController tests clientController/CreateTest.cpp diff --git a/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp b/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp index b4040ac2c..91f501c43 100644 --- a/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp +++ b/tests/unittests/media/client/main/mediaKeys/KeySessionTest.cpp @@ -65,14 +65,12 @@ TEST_F(RialtoClientMediaKeysKeySessionTest, CreateKeySession) KeySessionType sessionType = KeySessionType::PERSISTENT_LICENCE; std::shared_ptr> mediaKeysClientMock = std::make_shared>(); - bool isLDL = false; int32_t returnKeySessionId; - EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, isLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(m_kKeySessionId), Return(m_mediaKeyErrorStatus))); + EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(m_kKeySessionId), Return(m_mediaKeyErrorStatus))); - EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, isLDL, returnKeySessionId), - m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, returnKeySessionId), m_mediaKeyErrorStatus); EXPECT_EQ(returnKeySessionId, m_kKeySessionId); } @@ -83,11 +81,12 @@ TEST_F(RialtoClientMediaKeysKeySessionTest, GenerateRequest) { InitDataType initDataType = InitDataType::KEY_IDS; std::vector initData{7, 8, 9}; + LimitedDurationLicense ldlState{LimitedDurationLicense::NOT_SPECIFIED}; - EXPECT_CALL(*m_mediaKeysIpcMock, generateRequest(m_kKeySessionId, initDataType, initData)) + EXPECT_CALL(*m_mediaKeysIpcMock, generateRequest(m_kKeySessionId, initDataType, initData, ldlState)) .WillOnce(Return(m_mediaKeyErrorStatus)); - EXPECT_EQ(m_mediaKeys->generateRequest(m_kKeySessionId, initDataType, initData), m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->generateRequest(m_kKeySessionId, initDataType, initData, ldlState), m_mediaKeyErrorStatus); } /** diff --git a/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp b/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp index ee2001606..16548c9cf 100644 --- a/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp +++ b/tests/unittests/media/client/main/mediaKeys/NetflixKeySessionTest.cpp @@ -69,14 +69,12 @@ TEST_F(RialtoClientMediaKeysNetflixKeySessionTest, CreateKeySession) KeySessionType sessionType = KeySessionType::PERSISTENT_LICENCE; std::shared_ptr> mediaKeysClientMock = std::make_shared>(); - bool isLDL = false; int32_t returnKeySessionId; - EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, isLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(m_keySessionId), Return(m_mediaKeyErrorStatus))); + EXPECT_CALL(*m_mediaKeysIpcMock, createKeySession(sessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(m_keySessionId), Return(m_mediaKeyErrorStatus))); - EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, isLDL, returnKeySessionId), - m_mediaKeyErrorStatus); + EXPECT_EQ(m_mediaKeys->createKeySession(sessionType, mediaKeysClientMock, returnKeySessionId), m_mediaKeyErrorStatus); EXPECT_EQ(returnKeySessionId, m_keySessionId); // Update key should be possible, as keySession should be present in KeyIdMap EXPECT_TRUE(KeyIdMap::instance().updateKey(m_keySessionId, m_keyId)); diff --git a/tests/unittests/media/client/main/mediaKeysCapabilities/RobustnessTest.cpp b/tests/unittests/media/client/main/mediaKeysCapabilities/RobustnessTest.cpp new file mode 100644 index 000000000..024202745 --- /dev/null +++ b/tests/unittests/media/client/main/mediaKeysCapabilities/RobustnessTest.cpp @@ -0,0 +1,83 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * 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 "MediaKeysCapabilities.h" +#include "MediaKeysCapabilitiesIpcFactoryMock.h" +#include "MediaKeysCapabilitiesIpcMock.h" +#include + +using namespace firebolt::rialto; +using namespace firebolt::rialto::client; + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgReferee; +using ::testing::StrictMock; + +namespace +{ +const std::string kKeySystem{"com.netflix.playready"}; +const std::vector kRobustnessLevels{"HW_SECURE_ALL", "SW_SECURE_CRYPTO"}; +} // namespace + +class RialtoClientMediaKeysCapabilitiesRobustnessTest : public ::testing::Test +{ +protected: + std::shared_ptr m_mediaKeysCapabilities; + std::shared_ptr> m_mediaKeysCapabilitiesIpcMock; + + RialtoClientMediaKeysCapabilitiesRobustnessTest() + : m_mediaKeysCapabilitiesIpcMock{std::make_shared>()} + { + std::shared_ptr> mediaKeysCapabilitiesIpcFactoryMock = + std::make_shared>(); + + EXPECT_CALL(*mediaKeysCapabilitiesIpcFactoryMock, getMediaKeysCapabilitiesIpc()) + .WillOnce(Return(m_mediaKeysCapabilitiesIpcMock)); + + EXPECT_NO_THROW( + m_mediaKeysCapabilities = std::make_shared(mediaKeysCapabilitiesIpcFactoryMock)); + EXPECT_NE(m_mediaKeysCapabilities, nullptr); + } +}; + +/** + * Test that getSupportedRobustnessLevels returns success and forwards the IPC result. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesRobustnessTest, GetSupportedRobustnessLevelsSuccess) +{ + std::vector levels; + EXPECT_CALL(*m_mediaKeysCapabilitiesIpcMock, getSupportedRobustnessLevels(kKeySystem, _)) + .WillOnce(DoAll(SetArgReferee<1>(kRobustnessLevels), Return(true))); + + EXPECT_TRUE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystem, levels)); + EXPECT_EQ(levels, kRobustnessLevels); +} + +/** + * Test that getSupportedRobustnessLevels returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaKeysCapabilitiesRobustnessTest, GetSupportedRobustnessLevelsFailure) +{ + std::vector levels; + EXPECT_CALL(*m_mediaKeysCapabilitiesIpcMock, getSupportedRobustnessLevels(kKeySystem, _)).WillOnce(Return(false)); + + EXPECT_FALSE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystem, levels)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/CallbackTest.cpp b/tests/unittests/media/client/main/mediaPipeline/CallbackTest.cpp index 53b48f0d6..b0c74eef0 100644 --- a/tests/unittests/media/client/main/mediaPipeline/CallbackTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/CallbackTest.cpp @@ -98,6 +98,18 @@ TEST_F(RialtoClientMediaPipelineCallbackTest, SourceFlushed) m_mediaPipelineCallback->notifySourceFlushed(sourceId); } +/** + * Test a notification of firstFrameReceived is forwarded to the registered client. + */ +TEST_F(RialtoClientMediaPipelineCallbackTest, FirstFrameReceived) +{ + int32_t sourceId = 1; + + EXPECT_CALL(*m_mediaPipelineClientMock, notifyFirstFrameReceived(sourceId)); + + m_mediaPipelineCallback->notifyFirstFrameReceived(sourceId); +} + /** * Test a notification of playbackInfo is forwarded to the registered client. */ diff --git a/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp new file mode 100644 index 000000000..64122f833 --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.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 2026 Sky UK + * + * 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 "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineGetDurationTest : public MediaPipelineTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that GetDuration returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationSuccess) +{ + constexpr int64_t kExpectedDuration{123}; + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)) + .WillOnce(Invoke( + [&](int64_t &duration) + { + duration = kExpectedDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(resultDuration)); + EXPECT_EQ(resultDuration, kExpectedDuration); +} + +/** + * Test that GetDuration returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationFailure) +{ + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(resultDuration)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp b/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp new file mode 100644 index 000000000..277b46872 --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp @@ -0,0 +1,63 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * 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 "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineGetQueuedFramesTest : public MediaPipelineTestBase +{ +protected: + const int32_t m_kSourceId{1}; + + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that getQueuedFrames returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineGetQueuedFramesTest, GetQueuedFramesSuccess) +{ + constexpr uint32_t kExpectedQueuedFrames{123}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getQueuedFrames(m_kSourceId, _)) + .WillOnce(DoAll(SetArgReferee<1>(123), Return(true))); + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(m_kSourceId, queuedFrames)); + EXPECT_EQ(kExpectedQueuedFrames, queuedFrames); +} + +/** + * Test that getQueuedFrames returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineGetQueuedFramesTest, GetQueuedFramesFailure) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, getQueuedFrames(m_kSourceId, _)).WillOnce(Return(false)); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kSourceId, queuedFrames)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp b/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp index 977168d82..7d2486d65 100644 --- a/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/LoadTest.cpp @@ -25,6 +25,7 @@ class RialtoClientMediaPipelineLoadTest : public MediaPipelineTestBase MediaType m_type = MediaType::MSE; const std::string m_kMimeType = "mime"; const std::string m_kUrl = "mse://1"; + const bool m_kIsLive = false; virtual void SetUp() { @@ -46,9 +47,9 @@ class RialtoClientMediaPipelineLoadTest : public MediaPipelineTestBase */ TEST_F(RialtoClientMediaPipelineLoadTest, Success) { - EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl)).WillOnce(Return(true)); + EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl, m_kIsLive)).WillOnce(Return(true)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), true); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), true); } /** @@ -56,7 +57,7 @@ TEST_F(RialtoClientMediaPipelineLoadTest, Success) */ TEST_F(RialtoClientMediaPipelineLoadTest, Failure) { - EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl)).WillOnce(Return(false)); + EXPECT_CALL(*m_mediaPipelineIpcMock, load(m_type, m_kMimeType, m_kUrl, m_kIsLive)).WillOnce(Return(false)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), false); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), false); } diff --git a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp index be98cad21..563291b6b 100644 --- a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp @@ -77,11 +77,12 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) constexpr bool kEnabled{true}; constexpr uint32_t kBufferingLimit{5326}; constexpr uint64_t kStopPosition{4234}; + constexpr bool kIsLive{false}; ///////////////////////////////////////////// - EXPECT_CALL(*mediaPipelineMock, load(MediaType::MSE, StrEq(kMimeType), StrEq(kUrl))).WillOnce(Return(true)); - EXPECT_TRUE(proxy->load(MediaType::MSE, kMimeType, kUrl)); + EXPECT_CALL(*mediaPipelineMock, load(MediaType::MSE, StrEq(kMimeType), StrEq(kUrl), kIsLive)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->load(MediaType::MSE, kMimeType, kUrl, kIsLive)); ///////////////////////////////////////////// @@ -100,8 +101,9 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// - EXPECT_CALL(*mediaPipelineMock, play()).WillOnce(Return(true)); - EXPECT_TRUE(proxy->play()); + bool async{false}; + EXPECT_CALL(*mediaPipelineMock, play(_)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->play(async)); ///////////////////////////////////////////// @@ -134,6 +136,15 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getDuration(_)).WillOnce(DoAll(SetArgReferee<0>(kDuration), Return(true))); + { + int64_t duration; + EXPECT_TRUE(proxy->getDuration(duration)); + EXPECT_EQ(duration, kDuration); + } + + ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getStats(_, _, _)) .WillOnce(DoAll(SetArgReferee<1>(kRenderedFrames), SetArgReferee<2>(kDroppedFrames), Return(true))); { @@ -162,6 +173,21 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, setReportDecodeErrors(kSourceId, true)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->setReportDecodeErrors(kSourceId, true)); + + ///////////////////////////////////////////// + + { + const uint32_t kQueuedFrames{123}; + uint32_t returnQueuedFrames; + EXPECT_CALL(*mediaPipelineMock, getQueuedFrames(kSourceId, _)) + .WillOnce(DoAll(SetArgReferee<1>(kQueuedFrames), Return(true))); + EXPECT_TRUE(proxy->getQueuedFrames(kSourceId, returnQueuedFrames)); + EXPECT_EQ(returnQueuedFrames, kQueuedFrames); + } + ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, setVideoWindow(1, 2, 3, 4)).WillOnce(Return(true)); EXPECT_TRUE(proxy->setVideoWindow(1, 2, 3, 4)); diff --git a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp index a58b80891..8c78c550f 100644 --- a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp @@ -42,9 +42,10 @@ class RialtoClientMediaPipelinePlayPauseTest : public MediaPipelineTestBase */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(true)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(true)); - EXPECT_EQ(m_mediaPipeline->play(), true); + EXPECT_EQ(m_mediaPipeline->play(async), true); } /** @@ -52,9 +53,10 @@ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlayFailure) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(false)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(false)); - EXPECT_EQ(m_mediaPipeline->play(), false); + EXPECT_EQ(m_mediaPipeline->play(async), false); } /** diff --git a/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..29f10533d --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp @@ -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 Sky UK + * + * 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 "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineSetReportDecodeErrorsTest : public MediaPipelineTestBase +{ +protected: + const int32_t m_kSourceId{1}; + + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that setReportDecodeErrors returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineSetReportDecodeErrorsTest, SetReportDecodeErrorsSuccess) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setReportDecodeErrors(m_kSourceId, _)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineSetReportDecodeErrorsTest, SetReportDecodeErrorsFailure) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setReportDecodeErrors(m_kSourceId, _)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kSourceId, true)); +} diff --git a/tests/unittests/media/client/mocks/ipc/MediaKeysCapabilitiesIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaKeysCapabilitiesIpcMock.h index f9ccbe376..327722736 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaKeysCapabilitiesIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaKeysCapabilitiesIpcMock.h @@ -38,6 +38,8 @@ class MediaKeysCapabilitiesIpcMock : public IMediaKeysCapabilities MOCK_METHOD(bool, supportsKeySystem, (const std::string &keySystem), (override)); MOCK_METHOD(bool, getSupportedKeySystemVersion, (const std::string &keySystem, std::string &version), (override)); MOCK_METHOD(bool, isServerCertificateSupported, (const std::string &keySystem), (override)); + MOCK_METHOD(bool, getSupportedRobustnessLevels, + (const std::string &keySystem, std::vector &robustnessLevels), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcClientMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcClientMock.h index 5d34a7a49..51716ac0d 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcClientMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcClientMock.h @@ -41,6 +41,7 @@ class MediaPipelineIpcClientMock : public IMediaPipelineIpcClient MOCK_METHOD(void, notifyPosition, (int64_t position), (override)); MOCK_METHOD(void, notifyQos, (int32_t sourceId, const QosInfo &qosInfo), (override)); MOCK_METHOD(void, notifyBufferUnderflow, (int32_t sourceId), (override)); + MOCK_METHOD(void, notifyFirstFrameReceived, (int32_t sourceId), (override)); MOCK_METHOD(void, notifyPlaybackError, (int32_t sourceId, PlaybackError error), (override)); MOCK_METHOD(void, notifySourceFlushed, (int32_t sourceId), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (const PlaybackInfo &playbackInfo), (override)); diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index ed99bfb8e..2d90eb922 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -37,9 +37,10 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc (override)); MOCK_METHOD(bool, removeSource, (int32_t sourceId), (override)); MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, haveData, (MediaSourceStatus status, uint32_t numFrames, uint32_t requestId), (override)); @@ -47,6 +48,8 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, getPosition, (int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setPlaybackRate, (double rate), (override)); MOCK_METHOD(bool, renderFrame, (), (override)); @@ -74,6 +77,7 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h index 0d1427fd4..c6fd26247 100644 --- a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h +++ b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h @@ -31,7 +31,8 @@ namespace firebolt::rialto::client class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient { public: - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, attachSource, (const std::unique_ptr &source), (override)); @@ -39,7 +40,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); @@ -50,6 +51,8 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, getPosition, (int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); @@ -89,6 +92,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/interface/mocks/MediaKeysCapabilitiesMock.h b/tests/unittests/media/interface/mocks/MediaKeysCapabilitiesMock.h index 7308f9c2f..a8fe7b52c 100644 --- a/tests/unittests/media/interface/mocks/MediaKeysCapabilitiesMock.h +++ b/tests/unittests/media/interface/mocks/MediaKeysCapabilitiesMock.h @@ -38,6 +38,8 @@ class MediaKeysCapabilitiesMock : public IMediaKeysCapabilities MOCK_METHOD(bool, supportsKeySystem, (const std::string &keySystem), (override)); MOCK_METHOD(bool, getSupportedKeySystemVersion, (const std::string &keySystem, std::string &version), (override)); MOCK_METHOD(bool, isServerCertificateSupported, (const std::string &keySystem), (override)); + MOCK_METHOD(bool, getSupportedRobustnessLevels, + (const std::string &keySystem, std::vector &robustnessLevels), (override)); }; } // namespace firebolt::rialto diff --git a/tests/unittests/media/interface/mocks/MediaKeysMock.h b/tests/unittests/media/interface/mocks/MediaKeysMock.h index 450fd0197..14d3a6171 100644 --- a/tests/unittests/media/interface/mocks/MediaKeysMock.h +++ b/tests/unittests/media/interface/mocks/MediaKeysMock.h @@ -37,10 +37,11 @@ class MediaKeysMock : public IMediaKeys MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(bool, containsKey, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, - (KeySessionType sessionType, std::weak_ptr client, bool isLDL, int32_t &keySessionId), - (override)); + (KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, - (int32_t keySessionId, InitDataType initDataType, const std::vector &initData), (override)); + (int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState), + (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (int32_t keySessionId, const std::vector &responseData), (override)); diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index 7f6edfd76..29bc23640 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -35,6 +35,7 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/EnoughDataTest.cpp genericPlayer/tasksTests/EosTest.cpp genericPlayer/tasksTests/FinishSetupSourceTest.cpp + genericPlayer/tasksTests/FirstFrameReceivedTest.cpp genericPlayer/tasksTests/FlushTest.cpp genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp genericPlayer/tasksTests/HandleBusMessageTest.cpp @@ -50,6 +51,7 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/SetBufferingLimitTest.cpp genericPlayer/tasksTests/SetLowLatencyTest.cpp genericPlayer/tasksTests/SetImmediateOutputTest.cpp + genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp genericPlayer/tasksTests/SetMuteTest.cpp genericPlayer/tasksTests/SetPlaybackRateTest.cpp genericPlayer/tasksTests/SetPositionTest.cpp @@ -121,6 +123,9 @@ add_gtests(RialtoServerGstPlayerUnitTests #FlushWatcher unittests flushWatcher/FlushWatcherTests.cpp + #GstProfiler unittests + profiler/GstProfilerTests.cpp + ) target_include_directories(RialtoServerGstPlayerUnitTests diff --git a/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp b/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp index 42d82fc60..37afc3470 100644 --- a/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp +++ b/tests/unittests/media/server/gstplayer/decryptor/DecryptTest.cpp @@ -144,12 +144,12 @@ class RialtoServerDecryptorPrivateDecryptTest : public ::testing::Test void expectWidevineKeySystem() { - EXPECT_CALL(*m_decryptionServiceMock, isNetflixPlayreadyKeySystem(m_keySessionId)).WillOnce(Return(false)); + EXPECT_CALL(*m_decryptionServiceMock, isExtendedInterfaceUsed(m_keySessionId)).WillOnce(Return(false)); } void expectPlayreadyKeySystem() { - EXPECT_CALL(*m_decryptionServiceMock, isNetflixPlayreadyKeySystem(m_keySessionId)).WillOnce(Return(true)); + EXPECT_CALL(*m_decryptionServiceMock, isExtendedInterfaceUsed(m_keySessionId)).WillOnce(Return(true)); } void expectKeyMappingFailure() @@ -263,6 +263,39 @@ TEST_F(RialtoServerDecryptorPrivateDecryptTest, DecryptionServiceDecryptFailure) EXPECT_EQ(GST_BASE_TRANSFORM_FLOW_DROPPED, m_gstRialtoDecryptorPrivate->decrypt(&m_buffer, &m_caps)); } +/** + * Test GstRialtoDecryptorPrivate posts an HDCP protection message after decryption recovers. + */ +TEST_F(RialtoServerDecryptorPrivateDecryptTest, ShouldPostOutputProtectionMessageAfterRestrictedDecryptRecovers) +{ + GstStructure hdcpFailureStructure{}; + GstMessage hdcpFailureMessage{}; + + expectGetInfoFromProtectionMeta(); + expectWidevineKeySystem(); + expectAddGstProtectionMeta(true); + + EXPECT_CALL(*m_decryptionServiceMock, decrypt(m_keySessionId, &m_buffer, &m_caps)) + .WillOnce(Return(firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED)); + + EXPECT_EQ(GST_BASE_TRANSFORM_FLOW_DROPPED, m_gstRialtoDecryptorPrivate->decrypt(&m_buffer, &m_caps)); + + expectGetInfoFromProtectionMeta(); + expectWidevineKeySystem(); + expectAddGstProtectionMeta(true); + EXPECT_CALL(*m_decryptionServiceMock, decrypt(m_keySessionId, &m_buffer, &m_caps)) + .WillOnce(Return(firebolt::rialto::MediaKeyErrorStatus::OK)); + EXPECT_CALL(*m_gstWrapperMock, gstStructureNewStringStub(StrEq("HDCPProtectionFailure"), StrEq("message"), + G_TYPE_STRING, StrEq("HDCP Output Protection Error"))) + .WillOnce(Return(&hdcpFailureStructure)); + EXPECT_CALL(*m_gstWrapperMock, gstMessageNewApplication(GST_OBJECT_CAST(&m_decryptorBase), &hdcpFailureStructure)) + .WillOnce(Return(&hdcpFailureMessage)); + EXPECT_CALL(*m_gstWrapperMock, gstElementPostMessage(GST_ELEMENT_CAST(&m_decryptorBase), &hdcpFailureMessage)) + .WillOnce(Return(TRUE)); + + EXPECT_EQ(GST_FLOW_OK, m_gstRialtoDecryptorPrivate->decrypt(&m_buffer, &m_caps)); +} + /** * Test GstRialtoDecryptorPrivate decrypt returns success for a playready encrypted sample with mapping problem. */ diff --git a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp index 54aeffd82..2805d68ab 100644 --- a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp +++ b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp @@ -18,6 +18,7 @@ */ #include "GstDispatcherThread.h" +#include "FlushOnPrerollControllerMock.h" #include "GenericPlayerTaskFactoryMock.h" #include "GstDispatcherThreadClientMock.h" #include "GstWrapperMock.h" @@ -56,6 +57,8 @@ class GstDispatcherThreadTest : public ::testing::Test dynamic_cast &>(*workerThreadFactory)}; std::unique_ptr workerThread{std::make_unique>()}; StrictMock &m_workerThreadMock{dynamic_cast &>(*workerThread)}; + std::shared_ptr> m_flushOnPrerollControllerMock{ + std::make_shared>()}; std::mutex m_dispatcherThreadMutex; std::condition_variable m_dispatcherThreadCond; @@ -88,7 +91,8 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -98,7 +102,7 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) } /** - * Test that a GST_MESSAGE_STATE_CHANGED message is handled correctly. + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED) is handled correctly. */ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) { @@ -127,7 +131,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); EXPECT_CALL(m_client, handleBusMessage(_)); } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PLAYING) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPlaying) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PLAYING; + GstState pending = GST_STATE_VOID_PENDING; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PLAYING)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -137,7 +191,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -163,6 +218,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); EXPECT_CALL(m_client, handleBusMessage(_)); + EXPECT_CALL(*m_flushOnPrerollControllerMock, reset()); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED, pending PAUSED) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPrerolling) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PAUSED; + GstState pending = GST_STATE_PAUSED; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, setPrerolling()); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -172,7 +278,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -200,7 +307,8 @@ TEST_F(GstDispatcherThreadTest, Error) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -243,7 +351,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPausedNonPipeline) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp index 901d222ad..5e9a6f454 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/CreateTest.cpp @@ -49,6 +49,13 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon GstStructure m_contextStructure{}; GstElement m_westerosSink{}; GParamSpec m_rectangleSpec{}; + const bool m_kIsLive{false}; + + void expectCreateProfiler() + { + EXPECT_CALL(*m_gstProfilerFactoryMock, createGstProfiler(&m_pipeline, _, _)) + .WillOnce(Return(ByMove(std::move(m_gstProfiler)))); + } void expectCreatePipeline() { @@ -59,6 +66,7 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon expectSetSignalCallbacks(); expectSetUri(); expectCheckPlaySink(); + expectCreateProfiler(); EXPECT_CALL(*m_gstSrcMock, initSrc()); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); @@ -71,13 +79,14 @@ class RialtoServerCreateGstGenericPlayerTest : public GstGenericPlayerTestCommon gstPlayerWillBeCreated(); EXPECT_NO_THROW( - m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, - m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, - m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), - std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), - m_gstProtectionMetadataFactoryMock)); + m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, + m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), + std::move(gstDispatcherThreadFactory), + m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); } @@ -145,18 +154,20 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, FactoryCreatesObject) expectCheckPlaySink(); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&m_pipeline)).WillOnce(Return(&m_pipeline)); std::shared_ptr factory = firebolt::rialto::server::IGstGenericPlayerFactory::getFactory(); ASSERT_NE(factory, nullptr); auto player{factory->createGstGenericPlayer(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, - m_rdkGstreamerUtilsWrapperFactoryMock)}; + m_kIsLive, m_rdkGstreamerUtilsWrapperFactoryMock)}; EXPECT_NE(player, nullptr); // Destroy expectations EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(nullptr, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(_, GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(_)).Times(2); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(_)).Times(3); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolStopUnusedThreads()); player.reset(); // Cleanup @@ -336,9 +347,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, CreateWesterossinkFailsCreateCont EXPECT_CALL(*m_gstWrapperMock, gstContextNew(StrEq("erm"), false)).WillOnce(Return(nullptr)); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -354,11 +366,11 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, GstSrcFactoryNull) { EXPECT_CALL(m_gstInitialiserMock, waitForInitialisation()); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), nullptr, - m_timerFactoryMock, std::move(m_taskFactory), - std::move(workerThreadFactory), + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + nullptr, m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock), std::runtime_error); @@ -374,9 +386,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, TimerFactoryFails) initFactories(); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, nullptr, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -394,9 +407,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, GstSrcFactoryFails) EXPECT_CALL(*m_gstSrcFactoryMock, getGstSrc()).WillOnce(Return(nullptr)); EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, - m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, + m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, + m_gstInitialiserMock, std::move(m_flushWatcher), + m_gstSrcFactoryMock, m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), @@ -419,14 +433,15 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, UnknownMediaType) EXPECT_CALL(*m_gstProtectionMetadataFactoryMock, createProtectionMetadataWrapper(_)) .WillOnce(Return(ByMove(std::move(m_gstProtectionMetadataWrapper)))); - EXPECT_THROW(m_gstPlayer = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, - MediaType::UNKNOWN, m_videoReq, m_gstWrapperMock, - m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, - m_gstInitialiserMock, std::move(m_flushWatcher), - m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), - m_gstProtectionMetadataFactoryMock), + EXPECT_THROW(m_gstPlayer = + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::UNKNOWN, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, + m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, + std::move(m_taskFactory), std::move(workerThreadFactory), + std::move(gstDispatcherThreadFactory), + m_gstProtectionMetadataFactoryMock), std::runtime_error); EXPECT_EQ(m_gstPlayer, nullptr); } @@ -445,6 +460,7 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, PlaysinkNotFound) expectSetUri(); expectSetMessageCallback(); + expectCreateProfiler(); EXPECT_CALL(*m_gstSrcMock, initSrc()); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); @@ -457,10 +473,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, PlaysinkNotFound) EXPECT_NO_THROW( m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); @@ -483,6 +499,7 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, SetNativeAudioForBrcmAudioSink) expectSetUri(); expectCheckPlaySink(); expectSetMessageCallback(); + expectCreateProfiler(); EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); @@ -493,10 +510,10 @@ TEST_F(RialtoServerCreateGstGenericPlayerTest, SetNativeAudioForBrcmAudioSink) EXPECT_NO_THROW( m_gstPlayer = - std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, + std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, m_type, m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, std::move(m_flushWatcher), m_gstSrcFactoryMock, - m_timerFactoryMock, std::move(m_taskFactory), + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock)); EXPECT_NE(m_gstPlayer, nullptr); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp index 803ae1eb3..ed5a0904b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp @@ -19,6 +19,7 @@ #include "FlushOnPrerollController.h" #include +#include using firebolt::rialto::MediaSourceType; using firebolt::rialto::server::FlushOnPrerollController; @@ -29,37 +30,65 @@ class FlushOnPrerollControllerTest : public ::testing::Test FlushOnPrerollController m_sut; }; -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNoFlushSet) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaithWhenNoFlushSet) { - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNotPrerolled) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenNotPrerolled) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldPostponeAudioFlush) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenReset) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - EXPECT_TRUE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::VIDEO)); + m_sut.reset(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenReset) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPrerolling) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.setPrerolling(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPreviousProcedureIsFinished) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - m_sut.reset(); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.stateReached(GST_STATE_PLAYING); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWithVideoFlushWhenOnlyAudioIsOngoing) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.stateReached(GST_STATE_PAUSED); + m_sut.waitIfRequired(MediaSourceType::VIDEO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenPreviousProcedureIsFinished) +TEST_F(FlushOnPrerollControllerTest, shouldWaitForAudioFlushFinish) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); + std::thread waitThread([this]() { m_sut.waitIfRequired(MediaSourceType::AUDIO); }); m_sut.stateReached(GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + waitThread.join(); + // No deadlock here } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstCapabilitiesTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstCapabilitiesTest.cpp index badf3c219..b5f4cd976 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstCapabilitiesTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstCapabilitiesTest.cpp @@ -50,6 +50,7 @@ namespace const GstElementFactoryListType kExpectedFactoryListType{ GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO}; +const GType kDummyType{3}; }; // namespace template class GListWrapper { @@ -184,9 +185,9 @@ class GstCapabilitiesTest : public testing::Test .WillOnce(Return(m_listOfFactories)); EXPECT_CALL(*m_gstWrapperMock, gstPluginFeatureListFree(m_listOfFactories)).Times(1); - // The next calls should ensure that an object is created and then freed - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryCreate(m_elementFactory, nullptr)).WillOnce(Return(&m_object)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_object)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryGetElementType(m_elementFactory)).WillOnce(Return(kDummyType)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kDummyType)).WillOnce(Return(&m_elementClass)); + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_elementClass)); } std::shared_ptr> m_gstWrapperMock{std::make_shared>()}; @@ -206,6 +207,7 @@ class GstCapabilitiesTest : public testing::Test // Common sink factory type variables to be used in tests char m_dummySink = 0; GstElementFactory *m_sinkFactory{reinterpret_cast(&m_dummySink)}; + GstElementClass m_elementClass{}; GstStaticPadTemplate m_sinkPadTemplateSink; GstStaticPadTemplate m_sinkPadTemplateSrc; GstCaps m_sinkTemplateCaps; @@ -404,7 +406,7 @@ TEST_F(GstCapabilitiesTest, getSupportedPropertiesForBlacklistedFactories) EXPECT_CALL(*m_gstWrapperMock, gstPluginFeatureListFree(listOfFactories)).Times(1); // element will never be created from blacklisted factory list - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryCreate(_, nullptr)).Times(0); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryGetElementType(_)).Times(0); std::vector kParamNames{"test-name-123", "test2"}; std::vector supportedProperties{m_sut->getSupportedProperties(MediaSourceType::VIDEO, kParamNames)}; @@ -466,6 +468,27 @@ TEST_F(GstCapabilitiesTest, getSupportedPropertiesWithNoPropertiesSupported) m_listOfFactories = nullptr; } +TEST_F(GstCapabilitiesTest, getSupportedPropertiesWithInvalidType) +{ + createSutWithNoDecoderAndNoSink(); + + m_listOfFactories = g_list_append(m_listOfFactories, m_elementFactory); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListGetElements(kExpectedFactoryListType, GST_RANK_NONE)) + .WillOnce(Return(m_listOfFactories)); + EXPECT_CALL(*m_gstWrapperMock, gstPluginFeatureListFree(m_listOfFactories)).Times(1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryGetElementType(m_elementFactory)).WillOnce(Return(G_TYPE_INVALID)); + EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, isSocAudioFadeSupported()).WillOnce(Return(false)); + + std::vector kParamNames{"test-name-123", "test2", "audio-fade"}; + std::vector supportedProperties{m_sut->getSupportedProperties(MediaSourceType::VIDEO, kParamNames)}; + + EXPECT_EQ(supportedProperties, std::vector{}); + + gst_plugin_feature_list_free(m_listOfFactories); + m_listOfFactories = nullptr; +} + TEST_F(GstCapabilitiesTest, CreateGstCapabilities_OnlyOneDecoderWithTwoPadsWithTheSameCaps) { m_decoderPadTemplateSink = createSinkPadTemplate(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp index ed883ce93..f0f455fad 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstDispatcherThreadClientTest.cpp @@ -33,16 +33,17 @@ class GstDispatcherThreadClientTest : public GstGenericPlayerTestCommon protected: std::unique_ptr m_sut; VideoRequirements m_videoReq = {kMinPrimaryVideoWidth, kMinPrimaryVideoHeight}; + const bool m_kIsLive{false}; GstDispatcherThreadClientTest() { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, m_kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 44152f81c..43056feff 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -72,6 +72,7 @@ const std::string kAutoVideoSinkTypeName{"GstAutoVideoSink"}; const std::string kAutoAudioSinkTypeName{"GstAutoAudioSink"}; constexpr bool kResetTime{true}; const std::string kImmediateOutputStr{"immediate-output"}; +const std::string kReportDecodeErrorsStr{"report-decode-errors"}; const std::string kLowLatencyStr{"low-latency"}; const std::string kSyncStr{"sync"}; const std::string kSyncOffStr{"sync-off"}; @@ -84,6 +85,7 @@ constexpr bool kShowVideoWindow{true}; constexpr gint64 kPosition{123}; constexpr double kVolume{0.5}; constexpr firebolt::rialto::PlaybackInfo kPlaybackInfo{kPosition, kVolume}; +constexpr bool kIsLive{false}; } // namespace bool operator==(const GstRialtoProtectionData &lhs, const GstRialtoProtectionData &rhs) @@ -117,11 +119,11 @@ class GstGenericPlayerPrivateTest : public GstGenericPlayerTestCommon { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, kIsLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); m_realElement = initRealElement(); } @@ -207,6 +209,29 @@ class GstGenericPlayerPrivateTest : public GstGenericPlayerTestCommon EXPECT_CALL(m_gstPlayerClient, notifyPlaybackInfo(kPlaybackInfo)); } + + void willNotifyPlaybackInfoWithAudioFade() + { + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioFadeEnabled = true; + context.audioFadeVolume = kVolume; + }); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PLAYING)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *cur) + { + *cur = kPosition; + return TRUE; + })); + + EXPECT_CALL(m_gstPlayerClient, notifyPlaybackInfo(kPlaybackInfo)); + } }; TEST_F(GstGenericPlayerPrivateTest, shouldScheduleNeedData) @@ -304,6 +329,101 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleVideoUnderflowWithUnderflowDis m_sut->scheduleVideoUnderflow(); } +TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstVideoFrameReceived) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::VIDEO, AudioFirstFrameAction::CLEAR_PROBE)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->scheduleFirstVideoFrameReceived(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameFromSignal) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::AUDIO, AudioFirstFrameAction::CLEAR_PROBE)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameFromFallbackProbe) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::AUDIO, AudioFirstFrameAction::CLEAR_PROBE_STATE)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE_STATE); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldSetAudioFirstFrameFallbackProbe) +{ + GstPad pad{}; + m_sut->setAudioFirstFrameFallbackProbe(&pad, 42); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(&pad, context.audioFirstFrameProbePad); + EXPECT_EQ(42U, context.audioFirstFrameProbeId); + }); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&pad, 42)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + m_sut->clearAudioFirstFrameFallbackProbe(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldClearAudioFirstFrameFallbackProbe) +{ + GstPad pad{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioFirstFrameProbePad = &pad; + context.audioFirstFrameProbeId = 42; + }); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&pad, 42)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + + m_sut->clearAudioFirstFrameFallbackProbe(); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(nullptr, context.audioFirstFrameProbePad); + EXPECT_EQ(0U, context.audioFirstFrameProbeId); + }); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldClearAudioFirstFrameFallbackProbeState) +{ + GstPad pad{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioFirstFrameProbePad = &pad; + context.audioFirstFrameProbeId = 7; + }); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + + m_sut->clearAudioFirstFrameFallbackProbeState(); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(nullptr, context.audioFirstFrameProbePad); + EXPECT_EQ(0U, context.audioFirstFrameProbeId); + }); +} + TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkIsNull) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(kVideoSinkStr), _)); @@ -312,7 +432,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkIsNul TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkDoesNotHaveRectangleProperty) { - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("rectangle"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_FALSE(m_sut->setVideoSinkRectangle()); @@ -320,7 +440,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkDoesN TEST_F(GstGenericPlayerPrivateTest, shouldSetVideoRectangle) { - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("rectangle"))).WillOnce(Return(&m_rectangleSpec)); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq("rectangle"))); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); @@ -364,7 +484,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetImmediateOutputIfPropertyDoes { modifyContext([&](GenericPlayerContext &context) { context.pendingImmediateOutputForVideo = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kImmediateOutputStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -375,13 +495,42 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetImmediateOutput) { modifyContext([&](GenericPlayerContext &context) { context.pendingImmediateOutputForVideo = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kImmediateOutputStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); EXPECT_TRUE(m_sut->setImmediateOutput()); } +TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetReportDecodeErrorsIfPropertyDoesntExist) +{ + modifyContext([&](GenericPlayerContext &context) { context.pendingReportDecodeErrorsForVideo = true; }); + + expectGetVideoDecoder(m_realElement); + + expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kReportDecodeErrorsStr); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); + EXPECT_FALSE(m_sut->setReportDecodeErrors()); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldSetReportDecodeErrors) +{ + modifyContext([&](GenericPlayerContext &context) { context.pendingReportDecodeErrorsForVideo = true; }); + + expectGetVideoDecoder(m_realElement); + + GParamSpec gParamSpec{}; + + EXPECT_CALL(*m_glibWrapperMock, + gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_realElement), StrEq(kReportDecodeErrorsStr))) + .WillOnce(Return(&gParamSpec)); + + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq(kReportDecodeErrorsStr))).Times(1); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); + EXPECT_TRUE(m_sut->setReportDecodeErrors()); +} + TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetLowLatencyIfSinkIsNull) { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); @@ -399,7 +548,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetLowLatencyIfPropertyDoesntExi { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kLowLatencyStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -410,7 +559,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetLowLatency) { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kLowLatencyStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -456,7 +605,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetSyncIfPropertyDoesntExist) { modifyContext([&](GenericPlayerContext &context) { context.pendingSync = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kSyncStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -467,7 +616,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetSync) { modifyContext([&](GenericPlayerContext &context) { context.pendingSync = true; }); - expectGetSink(kAudioSinkStr, m_realElement); + expectGetAVSink(kAudioSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kSyncStr, true); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -628,7 +777,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetRenderFrameIfPropertyDoesntEx { modifyContext([&](GenericPlayerContext &context) { context.pendingRenderFrame = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kFrameStepOnPrerollStr); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); @@ -639,7 +788,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetRenderFrame) { modifyContext([&](GenericPlayerContext &context) { context.pendingRenderFrame = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); expectSetProperty(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kFrameStepOnPrerollStr, 1); @@ -668,6 +817,15 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotifyNeedVideoData) m_sut->notifyNeedMediaData(MediaSourceType::VIDEO); } +TEST_F(GstGenericPlayerPrivateTest, shouldNotifyNeedAudioDataWithDelay) +{ + modifyContext([&](GenericPlayerContext &context) + { context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].isDataNeeded = true; }); + + EXPECT_CALL(m_gstPlayerClient, notifyNeedMediaDataWithDelay(MediaSourceType::AUDIO)).WillOnce(Return(true)); + m_sut->notifyNeedMediaDataWithDelay(MediaSourceType::AUDIO); +} + TEST_F(GstGenericPlayerPrivateTest, shouldNotNotifyNeedAudioDataWhenNotNeeded) { m_sut->notifyNeedMediaData(MediaSourceType::AUDIO); @@ -678,6 +836,11 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotNotifyNeedVideoDataWhenNotNeeded) m_sut->notifyNeedMediaData(MediaSourceType::VIDEO); } +TEST_F(GstGenericPlayerPrivateTest, shouldNotNotifyNeedAudioDataWithDelayWhenNotNeeded) +{ + m_sut->notifyNeedMediaDataWithDelay(MediaSourceType::AUDIO); +} + TEST_F(GstGenericPlayerPrivateTest, shouldCreateClearGstBuffer) { GstBuffer buffer{}; @@ -1654,12 +1817,32 @@ TEST_F(GstGenericPlayerPrivateTest, shouldStartPositionReportingTimer) std::unique_ptr audioUnderflowTimerMock = std::make_unique>(); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(audioUnderflowTimerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldStartPlaybackInfoTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(true)); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->startNotifyPlaybackInfoTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldNotifyPlaybackInfoWithAudioFade) +{ + willNotifyPlaybackInfoWithAudioFade(); + std::unique_ptr playbackInfoTimerMock = std::make_unique>(); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(true)); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), cancel()); + EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) + .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); + + m_sut->startNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPositionReportingTimerWhenItIsActive) @@ -1668,14 +1851,23 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPositionReportingTimerWhenItIs EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(true)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); - willNotifyPlaybackInfo(); - std::unique_ptr playbackInfoTimerMock = std::make_unique>(); - EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) - .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); } +TEST_F(GstGenericPlayerPrivateTest, shouldNotStartPlaybackInfoTimerWhenItIsActive) +{ + std::unique_ptr timerMock = std::make_unique>(); + EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(true)).WillOnce(Return(true)); + EXPECT_CALL(dynamic_cast &>(*timerMock), cancel()); + EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) + .WillOnce(Return(ByMove(std::move(timerMock)))); + willNotifyPlaybackInfo(); + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->startNotifyPlaybackInfoTimer(); +} + TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionReportingTimerIsFired) { std::unique_ptr timerMock = std::make_unique>(); @@ -1692,8 +1884,16 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionRepo callback(); return std::move(timerMock); })); + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldSchedulePlaybackInfoWhenPlaybackInfoTimerIsFired) +{ + std::unique_ptr timerMock = std::make_unique>(); willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(true)); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Invoke( [&](const std::chrono::milliseconds &timeout, const std::function &callback, common::TimerType timerType) @@ -1702,7 +1902,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleReportPositionWhenPositionRepo callback(); return std::move(playbackInfoTimerMock); })); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->startNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePositionReportingTimer) @@ -1712,14 +1912,24 @@ TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePositionReportingTimer) EXPECT_CALL(dynamic_cast &>(*timerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldStopActivePlaybackInfoTimerTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); - EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(true)); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()) + .WillOnce(Return(true)) + .WillRepeatedly(Return(false)); EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); - m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); + + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->stopNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimer) @@ -1728,13 +1938,21 @@ TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimer) EXPECT_CALL(dynamic_cast &>(*timerMock), isActive()).WillOnce(Return(false)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPositionReportTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(timerMock)))); + + m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); + m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePlaybackInfoTimer) +{ willNotifyPlaybackInfo(); std::unique_ptr playbackInfoTimerMock = std::make_unique>(); - EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillOnce(Return(false)); + EXPECT_CALL(dynamic_cast &>(*playbackInfoTimerMock), isActive()).WillRepeatedly(Return(false)); EXPECT_CALL(*m_timerFactoryMock, createTimer(kPlaybackInfoTimerMs, _, common::TimerType::PERIODIC)) .WillOnce(Return(ByMove(std::move(playbackInfoTimerMock)))); - m_sut->startPositionReportingAndCheckAudioUnderflowTimer(); - m_sut->stopPositionReportingAndCheckAudioUnderflowTimer(); + + m_sut->startNotifyPlaybackInfoTimer(); + m_sut->stopNotifyPlaybackInfoTimer(); } TEST_F(GstGenericPlayerPrivateTest, shouldNotStopInactivePositionReportingTimerWhenThereIsNoTimer) @@ -1752,14 +1970,29 @@ TEST_F(GstGenericPlayerPrivateTest, shouldUpdatePlaybackGroup) { GstElement typefind; GstCaps caps; + GstCaps copiedCaps; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createUpdatePlaybackGroup(_, _, &typefind, &caps)) + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&typefind)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsCopy(&caps)).WillOnce(Return(&copiedCaps)); + EXPECT_CALL(m_taskFactoryMock, createUpdatePlaybackGroup(_, _, &typefind, &copiedCaps)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->updatePlaybackGroup(&typefind, &caps); } +TEST_F(GstGenericPlayerPrivateTest, shouldUpdatePlaybackGroupWithNullCaps) +{ + GstElement typefind; + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&typefind)); + EXPECT_CALL(m_taskFactoryMock, createUpdatePlaybackGroup(_, _, &typefind, nullptr)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->updatePlaybackGroup(&typefind, nullptr); +} + TEST_F(GstGenericPlayerPrivateTest, shouldAddAutoVideoSinkChildSink) { const GenericPlayerContext *context = getPlayerContext(); @@ -1966,6 +2199,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[13]{"audio/x-eac3"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -1987,6 +2221,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("fakesink"), StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -1995,6 +2235,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) std::unique_ptr source = std::make_unique("audio/aac", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) @@ -2003,6 +2244,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink0"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -2023,6 +2265,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2031,6 +2279,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) std::unique_ptr source = std::make_unique("audio/x-eac3", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) @@ -2039,6 +2288,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink1"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -2059,6 +2309,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2067,6 +2323,189 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) std::unique_ptr source = std::make_unique("audio/x-raw", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceNoCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + gchar capsStr[11]{"audio/mpeg"}; // old caps was AAC + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink0"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = true; // current codec is AAC + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink0"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (AAC->AAC, no codec switch): + // configAudioCap unrefs original caps and creates new ones + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // no codec switch - just set new caps + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // end of reattachSource: caps was updated to configCaps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceWithFirstTimeCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + GstPad typefindSrcPad{}; + GstPad typefindSrcPeerPad{}; + GstElement newAudioDecoder{}; + GstElement newAudioParse{}; + GstElement newQueue{}; + GstPad newAudioDecoderSrcPad{}; + GstElement typefind{}; + GstElement decodeBin{}; + GstElement playsinkBin{}; + gchar capsStr[13]{"audio/x-eac3"}; // old caps was EAC3 (no "audio/mpeg" → audioAac=false) + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink1"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = false; // current codec is EAC3 + context.playbackGroup.m_curAudioDecoder = nullptr; // first time switch: no existing decoder + context.playbackGroup.m_curAudioTypefind = &typefind; + context.playbackGroup.m_curAudioDecodeBin = &decodeBin; + context.playbackGroup.m_curAudioPlaysinkBin = &playsinkBin; + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink1"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (EAC3->AAC, codec switch): + // configAudioCap unrefs original caps and creates new AAC caps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // haltAudioPlayback + resumeAudioPlayback: playsinkBin and decodeBin each called twice + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&playsinkBin, GST_STATE_READY)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&playsinkBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&decodeBin, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&decodeBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + // switchAudioCodec -> firstTimeSwitchFromAC3toAAC + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&typefind, StrEq("src"))).WillOnce(Return(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadGetPeer(&typefindSrcPad)).WillOnce(Return(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadUnlink(&typefindSrcPad, &typefindSrcPeerPad)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("aacparse"), StrEq("aacparse"))) + .WillOnce(Return(&newAudioParse)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("avdec_aac"), StrEq("avdec_aac"))) + .WillOnce(Return(&newAudioDecoder)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("queue"), StrEq("aqueue"))).WillOnce(Return(&newQueue)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&newAudioDecoder, StrEq("src"))) + .WillOnce(Return(&newAudioDecoderSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadLink(&newAudioDecoderSrcPad, &typefindSrcPeerPad)).WillOnce(Return(GST_PAD_LINK_OK)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newAudioParse, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newQueue, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&typefind, GST_STATE_READY)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&typefind, StrEq("force-caps"))); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&typefind)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&typefind, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioDecoder, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newQueue, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioParse, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&newAudioDecoderSrcPad)); + // gstAppSrcSetCaps after codec switch + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // resumeAudioPlayback + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&playsinkBin)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&decodeBin)).WillOnce(Return(TRUE)); + // end of reattachSource: caps was updated to configCaps inside performAudioTrackCodecChannelSwitch + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&playsinkBin)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldSetSourceFlushed) @@ -2090,7 +2529,7 @@ TEST_F(GstGenericPlayerPrivateTest, failToSetShowVideoWindowNoSink) TEST_F(GstGenericPlayerPrivateTest, failToSetShowVideoWindowNoProperty) { modifyContext([&](GenericPlayerContext &context) { context.pendingShowVideoWindow = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("show-video-window"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_FALSE(m_sut->setShowVideoWindow()); @@ -2100,22 +2539,10 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetShowVideoWindow) { modifyContext([&](GenericPlayerContext &context) { context.pendingShowVideoWindow = true; }); - expectGetSink(kVideoSinkStr, m_realElement); + expectGetAVSink(kVideoSinkStr, m_realElement); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("show-video-window"))) .WillOnce(Return(&m_showVideoWindowSpec)); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq("show-video-window"))); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_TRUE(m_sut->setShowVideoWindow()); } - -TEST_F(GstGenericPlayerPrivateTest, shouldExecutePostponedFlush) -{ - constexpr MediaSourceType kSourceType{MediaSourceType::AUDIO}; - constexpr bool kResetTime{true}; - m_sut->postponeFlush(kSourceType, kResetTime); - - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, kSourceType, kResetTime)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->executePostponedFlushes(); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index b2c5158b6..eda5401b7 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -41,6 +41,7 @@ class GstGenericPlayerTest : public GstGenericPlayerTestCommon protected: std::unique_ptr m_sut; VideoRequirements m_videoReq = {kMinPrimaryVideoWidth, kMinPrimaryVideoHeight}; + bool m_isLive{false}; GstElement *m_pipeline; GstIterator m_it{}; char m_dummy{0}; @@ -52,11 +53,11 @@ class GstGenericPlayerTest : public GstGenericPlayerTestCommon { gstPlayerWillBeCreated(); m_sut = std::make_unique(&m_gstPlayerClient, m_decryptionServiceMock, MediaType::MSE, - m_videoReq, m_gstWrapperMock, m_glibWrapperMock, + m_videoReq, m_isLive, m_gstWrapperMock, m_glibWrapperMock, m_rdkGstreamerUtilsWrapperMock, m_gstInitialiserMock, - std::move(m_flushWatcher), m_gstSrcFactoryMock, m_timerFactoryMock, - std::move(m_taskFactory), std::move(workerThreadFactory), - std::move(gstDispatcherThreadFactory), + std::move(m_flushWatcher), m_gstSrcFactoryMock, + m_gstProfilerFactoryMock, m_timerFactoryMock, std::move(m_taskFactory), + std::move(workerThreadFactory), std::move(gstDispatcherThreadFactory), m_gstProtectionMetadataFactoryMock); m_element = fakeElement(); } @@ -165,13 +166,15 @@ TEST_F(GstGenericPlayerTest, shouldAllSourcesAttached) m_sut->allSourcesAttached(); } -TEST_F(GstGenericPlayerTest, shouldPlay) +TEST_F(GstGenericPlayerTest, shouldPlayOnWorkerThread) { + bool async = false; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); EXPECT_CALL(m_taskFactoryMock, createPlay(_)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->play(); + m_sut->play(async); + EXPECT_TRUE(async); } TEST_F(GstGenericPlayerTest, shouldPause) @@ -280,6 +283,7 @@ TEST_F(GstGenericPlayerTest, shouldAddDeepElement) GstElement element{}; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(&element)); EXPECT_CALL(m_taskFactoryMock, createDeepElementAdded(_, _, _, _, &element)).WillOnce(Return(ByMove(std::move(task)))); triggerDeepElementAdded(&element); @@ -381,7 +385,7 @@ TEST_F(GstGenericPlayerTest, shouldGetImmediateOutputInPlayingState) const bool kTestImmediateOutputValue{true}; const std::string kPropertyStr{"immediate-output"}; - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); willGetElementProperty(kPropertyStr, kTestImmediateOutputValue); bool immediateOutputState; @@ -395,7 +399,7 @@ TEST_F(GstGenericPlayerTest, shouldGetImmediateOutputInPlayingStateForAudio) const bool kTestImmediateOutputValue{true}; const std::string kPropertyStr{"immediate-output"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kTestImmediateOutputValue); bool immediateOutputState; @@ -426,7 +430,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetImmediateOutputInPlayingStateIfPrope { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("immediate-output"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); @@ -435,6 +439,43 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetImmediateOutputInPlayingStateIfPrope EXPECT_FALSE(m_sut->getImmediateOutput(MediaSourceType::VIDEO, immediateOutputState)); } +TEST_F(GstGenericPlayerTest, shouldSetReportDecodeErrors) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createSetReportDecodeErrors(_, _, MediaSourceType::VIDEO, true)) + .WillOnce(Return(ByMove(std::move(task)))); + + EXPECT_TRUE(m_sut->setReportDecodeErrors(MediaSourceType::VIDEO, true)); +} + +TEST_F(GstGenericPlayerTest, shouldGetQueuedFramesInPlayingState) +{ + setPipelineState(GST_STATE_PLAYING); + const uint32_t kTestQueuedFramesValue{123}; + const std::string kPropertyStr{"queued-frames"}; + + expectGetVideoDecoder(m_element); + willGetElementProperty(kPropertyStr, kTestQueuedFramesValue); + + uint32_t queuedFrames; + EXPECT_TRUE(m_sut->getQueuedFrames(queuedFrames)); + EXPECT_EQ(queuedFrames, kTestQueuedFramesValue); +} + +TEST_F(GstGenericPlayerTest, shouldFailToGetQueuedFramesInPlayingStateIfPropertyDoesntExist) +{ + setPipelineState(GST_STATE_PLAYING); + + expectGetVideoDecoder(m_element); + + EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("queued-frames"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); + + uint32_t queuedFrames; + EXPECT_FALSE(m_sut->getQueuedFrames(queuedFrames)); +} + TEST_F(GstGenericPlayerTest, shouldGetStatsInPlayingState) { constexpr guint64 kRenderedFrames{1234}; @@ -443,7 +484,7 @@ TEST_F(GstGenericPlayerTest, shouldGetStatsInPlayingState) uint64_t returnedDroppedFrames{}; setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); GstStructure testStructure; EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)) @@ -491,7 +532,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetStatsInPlayingStateIfStructureNull) { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); // Fail to get GstStructure which should cause the getStats() call to return false EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)).Times(1); @@ -506,7 +547,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetStatsInPlayingStateIfStructIncomplet { setPipelineState(GST_STATE_PLAYING); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); GstStructure testStructure; EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("stats"), _)) @@ -546,7 +587,7 @@ TEST_F(GstGenericPlayerTest, shouldGetVolumeWithNegativeFadeVolume) const gint kNegativeFadeVolume{-100}; const std::string kPropertyStr{"fade-volume"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kNegativeFadeVolume); constexpr double kVolume{0.5}; @@ -565,7 +606,7 @@ TEST_F(GstGenericPlayerTest, shouldGetVolumeWithPositiveFadeVolume) const gint kFadeVolume{70}; const std::string kPropertyStr{"fade-volume"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kFadeVolume); @@ -732,7 +773,7 @@ TEST_F(GstGenericPlayerTest, shouldGetSync) const bool kSyncValue{true}; const std::string kPropertyStr{"sync"}; - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); willGetElementProperty(kPropertyStr, kSyncValue); bool sync; @@ -762,7 +803,7 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetSyncIfStubNull) TEST_F(GstGenericPlayerTest, shouldFailToGetSyncIfPropertyDoesntExist) { - expectGetSink(kAudioSinkStr, m_element); + expectGetAVSink(kAudioSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("sync"))).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); @@ -880,7 +921,7 @@ TEST_F(GstGenericPlayerTest, shouldFlush) bool isAsync{true}; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(m_flushWatcherMock, setFlushing(MediaSourceType::VIDEO, isAsync)); - expectGetSink(kVideoSinkStr, m_element); + expectGetAVSink(kVideoSinkStr, m_element); EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("async"), _)) .WillOnce(Invoke( [&](gpointer object, const gchar *first_property_name, void *val) @@ -890,12 +931,34 @@ TEST_F(GstGenericPlayerTest, shouldFlush) })); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)); EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime)) + EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime, isAsync)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->flush(MediaSourceType::VIDEO, kResetTime, isAsync); } +TEST_F(GstGenericPlayerTest, shouldFlushSubtitles) +{ + constexpr bool kResetTime{true}; + bool isAsync{false}; + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(m_flushWatcherMock, setFlushing(MediaSourceType::SUBTITLE, isAsync)); + expectGetSink(kTextSinkStr, m_element); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("async"), _)) + .WillOnce(Invoke( + [&](gpointer object, const gchar *first_property_name, void *val) + { + gboolean *returnVal = reinterpret_cast(val); + *returnVal = FALSE; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)); + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::SUBTITLE, kResetTime, isAsync)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->flush(MediaSourceType::SUBTITLE, kResetTime, isAsync); +} + TEST_F(GstGenericPlayerTest, shouldSetSourcePosition) { constexpr int64_t kPosition{1234}; @@ -1087,3 +1150,25 @@ TEST_F(GstGenericPlayerTest, shouldSwitchSource) m_sut->switchSource(source); } + +TEST_F(GstGenericPlayerTest, shouldReturnInvalidDurationWhenQueryFails) +{ + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)).WillOnce(Return(FALSE)); + EXPECT_FALSE(m_sut->getDuration(targetDuration)); +} + +TEST_F(GstGenericPlayerTest, shouldReturnDuration) +{ + constexpr gint64 kExpectedDuration{123}; + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *cur) + { + *cur = kExpectedDuration; + return TRUE; + })); + EXPECT_TRUE(m_sut->getDuration(targetDuration)); + EXPECT_EQ(kExpectedDuration, targetDuration); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index f40976337..0814f8227 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -47,6 +47,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSubtitleOffset.h" @@ -89,7 +90,7 @@ constexpr auto kAudioSourceId{static_cast(firebolt::rialto::MediaS constexpr auto kVideoSourceId{static_cast(firebolt::rialto::MediaSourceType::VIDEO)}; constexpr auto kSubtitleSourceId{static_cast(firebolt::rialto::MediaSourceType::SUBTITLE)}; constexpr gint64 kItHappenedInThePast = 1238450934; -constexpr gint64 kItWillHappenInTheFuture = 3823530248; +constexpr gint64 kItWillHappenInTheFuture = 9823530248; constexpr int64_t kDuration{9000000000}; constexpr int32_t kSampleRate{13}; constexpr int32_t kNumberOfChannels{4}; @@ -139,6 +140,7 @@ constexpr uint64_t kStopPosition{4523}; const std::vector kStreamHeaderVector{1, 2, 3, 4}; constexpr bool kFramed{true}; constexpr uint64_t kDisplayOffset{35}; +constexpr bool kIsAsync{true}; firebolt::rialto::IMediaPipeline::MediaSegmentVector buildAudioSamples() { @@ -269,6 +271,7 @@ GenericTasksTestsBase::GenericTasksTestsBase() testContext->m_context.gstSrc = testContext->m_gstSrc; testContext->m_context.source = testContext->m_element; testContext->m_context.decryptionService = testContext->m_decryptionServiceMock.get(); + testContext->m_context.gstProfiler = std::move(testContext->m_gstProfilerMock); } GenericTasksTestsBase::~GenericTasksTestsBase() @@ -408,19 +411,26 @@ void GenericTasksTestsBase::setContextSetupSourceFinished() testContext->m_context.setupSourceFinished = true; } +void GenericTasksTestsBase::setContextAudioInitialPosition() +{ + testContext->m_context.initialPositions[&testContext->m_appSrcAudio].emplace_back( + firebolt::rialto::server::SegmentData{kPosition, kResetTime, kAppliedRate, kStopPosition}); +} + void GenericTasksTestsBase::expectVideoUnderflowSignalConnection() { EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) - .WillOnce(Invoke( + .WillRepeatedly(Invoke( [&](GType itype, guint *n_ids) { *n_ids = 1; return testContext->m_signals; })); EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) - .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); - EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)); + .WillRepeatedly( + Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) .WillOnce(Invoke( [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) @@ -430,19 +440,34 @@ void GenericTasksTestsBase::expectVideoUnderflowSignalConnection() })); } +void GenericTasksTestsBase::expectFirstVideoFrameSignalConnection() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce( + Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "first-video-frame-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("first-video-frame-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_firstVideoFrameCallback = c_handler; + return kSignalId; + })); +} + void GenericTasksTestsBase::expectAudioUnderflowSignalConnection() { EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) - .WillOnce(Invoke( + .WillRepeatedly(Invoke( [&](GType itype, guint *n_ids) { *n_ids = 1; return testContext->m_signals; })); EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) - .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); - EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)); + .WillRepeatedly( + Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) .WillOnce(Invoke( [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) @@ -456,26 +481,21 @@ void GenericTasksTestsBase::expectSetupVideoSinkElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectVideoUnderflowSignalConnection(); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectRef(testContext->m_element)); @@ -486,6 +506,26 @@ void GenericTasksTestsBase::expectSetupVideoDecoderElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillRepeatedly(Return(TRUE)); + + expectVideoUnderflowSignalConnection(); + + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); +} + +void GenericTasksTestsBase::expectSetupVideoDecoderElementWithFirstVideoFrameCallback() +{ + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) .WillOnce(Return(TRUE)); @@ -496,7 +536,8 @@ void GenericTasksTestsBase::expectSetupVideoDecoderElement() EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); + .Times(2) + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, @@ -518,7 +559,26 @@ void GenericTasksTestsBase::expectSetupVideoDecoderElement() GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) .WillOnce(Return(FALSE)); - expectVideoUnderflowSignalConnection(); + EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return testContext->m_signals; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); + expectFirstVideoFrameSignalConnection(); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })) + .RetiresOnSaturation(); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_videoUnderflowCallback = c_handler; + return kSignalId; + })); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); } @@ -527,31 +587,23 @@ void GenericTasksTestsBase::expectSetupAudioSinkElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetStaticPad(testContext->m_element, StrEq("sink"))) + .WillOnce(Return(nullptr)); expectAudioUnderflowSignalConnection(); @@ -566,23 +618,21 @@ void GenericTasksTestsBase::expectSetupAudioDecoderElement() EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectAudioUnderflowSignalConnection(); @@ -685,6 +735,17 @@ void GenericTasksTestsBase::shouldSetupVideoDecoderElementOnly() expectSetupVideoDecoderElement(); } +void GenericTasksTestsBase::shouldSetupVideoDecoderElementWithFirstVideoFrameCallback() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); + expectSetupVideoDecoderElementWithFirstVideoFrameCallback(); +} + void GenericTasksTestsBase::shouldSetupVideoElementWithPendingGeometry() { testContext->m_context.pendingGeometry = kRectangle; @@ -787,6 +848,21 @@ void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithPendingBufferingLi expectSetupAudioDecoderElement(); } +void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithIsLiveParameter() +{ + testContext->m_context.isLive = true; + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + + EXPECT_CALL(*testContext->m_glibWrapper, + gObjectClassFindProperty(G_OBJECT_GET_CLASS(testContext->m_element), StrEq("enable-rate-correction"))) + .WillOnce(Return(&testContext->m_paramSpec)); + EXPECT_CALL(*testContext->m_glibWrapper, + gObjectSetStub(G_OBJECT(testContext->m_element), StrEq("enable-rate-correction"))); + + expectSetupAudioDecoderElement(); +} + void GenericTasksTestsBase::shouldSetupVideoSinkElementWithPendingRenderFrame() { testContext->m_context.pendingRenderFrame = true; @@ -850,27 +926,21 @@ void GenericTasksTestsBase::shouldSetupAudioElementBrcmAudioSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectAudioUnderflowSignalConnection(); @@ -972,12 +1042,153 @@ void GenericTasksTestsBase::shouldSetupAudioDecoderElementOnly() expectSetupAudioDecoderElement(); } +void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithFirstAudioFrameCallback() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return testContext->m_signals; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); + + testing::InSequence sequence; + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_audioUnderflowCallback = c_handler; + return kSignalId; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce( + Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "first-audio-frame-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("first-audio-frame-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_firstAudioFrameCallback = c_handler; + testContext->m_audioUserData = data; + return kSignalId; + })); + + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); +} + +void GenericTasksTestsBase::shouldSetupAudioSinkElementWithFirstAudioFrameProbe() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return testContext->m_signals; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); + + testing::InSequence sequence; + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_audioUnderflowCallback = c_handler; + return kSignalId; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "not-first-frame-signal"; })); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetStaticPad(testContext->m_element, StrEq("sink"))) + .WillOnce(Return(&testContext->m_pad)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstPadAddProbe(&testContext->m_pad, GST_PAD_PROBE_TYPE_BUFFER, _, &testContext->m_gstPlayer, nullptr)) + .WillOnce(Invoke( + [&](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) + { + testContext->m_firstAudioFrameProbeCallback = callback; + testContext->m_firstAudioFrameProbeUserData = userData; + return kSignalId; + })); + EXPECT_CALL(testContext->m_gstPlayer, setAudioFirstFrameFallbackProbe(&testContext->m_pad, kSignalId)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); +} + void GenericTasksTestsBase::shouldSetVideoUnderflowCallback() { ASSERT_TRUE(testContext->m_videoUnderflowCallback); EXPECT_CALL(testContext->m_gstPlayer, scheduleVideoUnderflow()); } +void GenericTasksTestsBase::shouldSetFirstVideoFrameCallback() +{ + ASSERT_TRUE(testContext->m_firstVideoFrameCallback); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstVideoFrameReceived()); +} + +void GenericTasksTestsBase::shouldSetFirstAudioFrameCallback() +{ + ASSERT_TRUE(testContext->m_firstAudioFrameCallback); + ASSERT_TRUE(testContext->m_audioUserData); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE)); +} + +void GenericTasksTestsBase::shouldSetFirstAudioFrameFallbackProbeCallback() +{ + ASSERT_TRUE(testContext->m_firstAudioFrameProbeCallback); + ASSERT_TRUE(testContext->m_firstAudioFrameProbeUserData); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE_STATE)); +} + void GenericTasksTestsBase::shouldSetupBaseParse() { EXPECT_CALL(*testContext->m_gstWrapper, gstBaseParseSetPtsInterpolation(_, FALSE)); @@ -990,6 +1201,18 @@ void GenericTasksTestsBase::triggerVideoUnderflowCallback() testContext->m_videoUnderflowCallback)(testContext->m_element, 0, nullptr, &testContext->m_gstPlayer); } +void GenericTasksTestsBase::triggerFirstVideoFrameCallback() +{ + reinterpret_cast( + testContext->m_firstVideoFrameCallback)(testContext->m_element, 0, nullptr, &testContext->m_gstPlayer); +} + +void GenericTasksTestsBase::triggerFirstAudioFrameCallback() +{ + reinterpret_cast( + testContext->m_firstAudioFrameCallback)(testContext->m_element, 0, nullptr, testContext->m_audioUserData); +} + void GenericTasksTestsBase::shouldSetAudioUnderflowCallback() { ASSERT_TRUE(testContext->m_audioUnderflowCallback); @@ -1002,6 +1225,17 @@ void GenericTasksTestsBase::triggerAudioUnderflowCallback() testContext->m_audioUnderflowCallback)(testContext->m_element, 0, nullptr, &testContext->m_gstPlayer); } +void GenericTasksTestsBase::triggerFirstAudioFrameFallbackProbeCallback() +{ + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = &testContext->m_audioBuffer; + + EXPECT_EQ(GST_PAD_PROBE_REMOVE, + testContext->m_firstAudioFrameProbeCallback(&testContext->m_pad, &info, + testContext->m_firstAudioFrameProbeUserData)); +} + void GenericTasksTestsBase::shouldAddAutoVideoSinkChildCallback() { EXPECT_CALL(testContext->m_gstPlayer, addAutoVideoSinkChild(&testContext->m_gObj)); @@ -1310,6 +1544,20 @@ void GenericTasksTestsBase::shouldAttachAllAudioSamples() EXPECT_CALL(testContext->m_gstPlayer, notifyNeedMediaData(MediaSourceType::AUDIO)); } +void GenericTasksTestsBase::shouldAttachAllAudioSamplesWithDelay() +{ + testContext->m_context.streamPosition = kItHappenedInThePast - 10; + std::shared_ptr kNullCodecData{}; + EXPECT_CALL(testContext->m_gstPlayer, createBuffer(_)).Times(2).WillRepeatedly(Return(&testContext->m_audioBuffer)); + EXPECT_CALL(testContext->m_gstPlayer, updateAudioCaps(kSampleRate, kNumberOfChannels, kNullCodecData)); + EXPECT_CALL(testContext->m_gstPlayer, updateAudioCaps(kSampleRate, kNumberOfChannels, kCodecDataBuffer)); + EXPECT_CALL(testContext->m_gstPlayer, + addAudioClippingToBuffer(&testContext->m_audioBuffer, kClippingStart, kClippingEnd)) + .Times(2); + EXPECT_CALL(testContext->m_gstPlayer, attachData(MediaSourceType::AUDIO)).Times(2); + EXPECT_CALL(testContext->m_gstPlayer, notifyNeedMediaDataWithDelay(MediaSourceType::AUDIO)); +} + void GenericTasksTestsBase::shouldAttachData(firebolt::rialto::MediaSourceType sourceType) { EXPECT_CALL(testContext->m_gstPlayer, attachData(sourceType)).Times(1); @@ -1862,9 +2110,8 @@ void GenericTasksTestsBase::shouldReattachAudioSource() EXPECT_CALL(testContext->m_gstPlayer, reattachSource(_)).WillOnce(Return(true)); } -void GenericTasksTestsBase::shouldEnableAudioFlagsAndSendNeedData() +void GenericTasksTestsBase::shouldRequestAudioData() { - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(true)); EXPECT_CALL(testContext->m_gstPlayer, notifyNeedMediaData(MediaSourceType::AUDIO)); } @@ -1945,6 +2192,8 @@ void GenericTasksTestsBase::shouldNotRegisterCallbackWhenPtrsAreNotEqual() void GenericTasksTestsBase::constructDeepElementAdded() { + // The element reference taken in GstGenericPlayer::deepElementAdded is released in the task destructor. + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(testContext->m_element)); firebolt::rialto::server::tasks::generic::DeepElementAdded task{testContext->m_context, testContext->m_gstPlayer, testContext->m_gstWrapper, @@ -1969,7 +2218,7 @@ void GenericTasksTestsBase::shouldNotRegisterCallbackWhenElementNameIsNotTypefin EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("typefind"))) - .WillOnce(Return(nullptr)); + .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } @@ -1980,7 +2229,7 @@ void GenericTasksTestsBase::shouldRegisterCallbackForTypefindElement() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("typefind"))) - .WillOnce(Return(testContext->m_typefindElementName)); + .WillRepeatedly(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(G_OBJECT(testContext->m_element), StrEq("have-type"), _, &testContext->m_gstPlayer)) .WillOnce(Return(kSignalId)); @@ -2010,13 +2259,25 @@ void GenericTasksTestsBase::shouldUpdatePlaybackGroupWhenCallbackIsCalled() void GenericTasksTestsBase::shouldSetTypefindElement() { - EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) + .WillOnce(Return(&testContext->m_obj1)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::triggerDeepElementAdded() { + // The element reference taken in GstGenericPlayer::deepElementAdded is released in the task destructor. + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(testContext->m_element)); firebolt::rialto::server::tasks::generic::DeepElementAdded task{testContext->m_context, testContext->m_gstPlayer, testContext->m_gstWrapper, @@ -2058,8 +2319,11 @@ void GenericTasksTestsBase::shouldSetParseElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_parseElementName, StrEq("parse"))) - .WillOnce(Return(testContext->m_parseElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkParsePlaybackGroupAdded() @@ -2084,10 +2348,15 @@ void GenericTasksTestsBase::shouldSetDecoderElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("parse"))) - .WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("dec"))) - .WillOnce(Return(testContext->m_decoderElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() @@ -2102,8 +2371,11 @@ void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() void GenericTasksTestsBase::shouldSetGenericElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::shouldSetAudioSinkElement() @@ -2117,8 +2389,11 @@ void GenericTasksTestsBase::shouldSetAudioSinkElement() EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_audioSinkElementName)); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_audioSinkElementName, StrEq("audiosink"))) - .WillOnce(Return(testContext->m_audioSinkElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::shouldHaveNullParentSink() @@ -2126,6 +2401,7 @@ void GenericTasksTestsBase::shouldHaveNullParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(nullptr)); } @@ -2136,11 +2412,16 @@ void GenericTasksTestsBase::shouldHaveNonBinParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("bin"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } void GenericTasksTestsBase::shouldHaveBinParentSink() { + // Previous bin should be unrefed + testContext->m_context.playbackGroup.m_curAudioPlaysinkBin = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_childElement)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) @@ -2161,6 +2442,8 @@ void GenericTasksTestsBase::checkAudioSinkPlaybackGroupAdded() void GenericTasksTestsBase::triggerUpdatePlaybackGroupNoCaps() { + // The typefind reference taken in GstGenericPlayer::updatePlaybackGroup is released in the task destructor. + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(testContext->m_element)); firebolt::rialto::server::tasks::generic::UpdatePlaybackGroup task{testContext->m_context, testContext->m_gstPlayer, testContext->m_gstWrapper, @@ -2183,6 +2466,10 @@ void GenericTasksTestsBase::shouldReturnNullCaps() void GenericTasksTestsBase::triggerUpdatePlaybackGroup() { + // The typefind and caps references (owned copy) taken in GstGenericPlayer::updatePlaybackGroup are released in the + // task destructor. + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(testContext->m_element)); + EXPECT_CALL(*testContext->m_gstWrapper, gstCapsUnref(&testContext->m_gstCaps1)); firebolt::rialto::server::tasks::generic::UpdatePlaybackGroup task{testContext->m_context, testContext->m_gstPlayer, testContext->m_gstWrapper, @@ -2263,6 +2550,24 @@ void GenericTasksTestsBase::shouldTriggerSetUseBuffering() EXPECT_CALL(testContext->m_gstPlayer, setUseBuffering()).WillOnce(Return(true)); } +void GenericTasksTestsBase::shouldLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(TRUE)); +} + +void GenericTasksTestsBase::shouldFailToLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(FALSE)); +} + void GenericTasksTestsBase::shouldStopGstPlayer() { auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; @@ -2272,8 +2577,10 @@ void GenericTasksTestsBase::shouldStopGstPlayer() videoStreamIt->second.isDataNeeded = true; audioStreamIt->second.isDataNeeded = true; + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)); + EXPECT_CALL(testContext->m_gstPlayer, stopNotifyPlaybackInfoTimer()); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerStop() @@ -2589,12 +2896,12 @@ void GenericTasksTestsBase::checkNoEos() void GenericTasksTestsBase::shouldChangeStatePlayingSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(true)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::shouldChangeStatePlayingFailure() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(false)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_FAILURE)); } void GenericTasksTestsBase::triggerPlay() @@ -2612,7 +2919,7 @@ void GenericTasksTestsBase::triggerPing() void GenericTasksTestsBase::shouldPause() { EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerPause() @@ -3002,12 +3309,13 @@ void GenericTasksTestsBase::triggerRenderFrame() void GenericTasksTestsBase::shouldInvalidateActiveAudioRequests() { + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); } -void GenericTasksTestsBase::shouldDisableAudioFlag() +void GenericTasksTestsBase::shouldUnrefAudioBuffer() { - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(false)); + EXPECT_CALL(*testContext->m_gstWrapper, gstBufferUnref(&testContext->m_audioBuffer)); } void GenericTasksTestsBase::triggerRemoveSourceAudio() @@ -3040,7 +3348,6 @@ void GenericTasksTestsBase::checkAudioSourceNotRemoved() void GenericTasksTestsBase::shouldFlushAudioSrcSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcAudio, &testContext->m_event)) .WillOnce(Return(TRUE)); @@ -3051,7 +3358,6 @@ void GenericTasksTestsBase::shouldFlushAudioSrcSuccess() void GenericTasksTestsBase::shouldFlushAudioSrcFailure() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcAudio, &testContext->m_event)) .WillOnce(Return(FALSE)); @@ -3063,21 +3369,31 @@ void GenericTasksTestsBase::shouldFlushAudioSrcFailure() void GenericTasksTestsBase::shouldReadAudioData() { EXPECT_CALL(*testContext->m_dataReader, readData()).WillOnce(Invoke([&]() { return buildAudioSamples(); })); + EXPECT_CALL(*testContext->m_dataReader, isBufferFull()).WillOnce(Return(true)); +} + +void GenericTasksTestsBase::shouldReadAudioDataFromShmWithAvailableSpace() +{ + EXPECT_CALL(*testContext->m_dataReader, readData()).WillOnce(Invoke([&]() { return buildAudioSamples(); })); + EXPECT_CALL(*testContext->m_dataReader, isBufferFull()).WillOnce(Return(false)); } void GenericTasksTestsBase::shouldReadVideoData() { EXPECT_CALL(*testContext->m_dataReader, readData()).WillOnce(Invoke([&]() { return buildVideoSamples(); })); + EXPECT_CALL(*testContext->m_dataReader, isBufferFull()).WillOnce(Return(true)); } void GenericTasksTestsBase::shouldReadSubtitleData() { EXPECT_CALL(*testContext->m_dataReader, readData()).WillOnce(Invoke([&]() { return buildSubtitleSamples(); })); + EXPECT_CALL(*testContext->m_dataReader, isBufferFull()).WillOnce(Return(true)); } void GenericTasksTestsBase::shouldReadUnknownData() { EXPECT_CALL(*testContext->m_dataReader, readData()).WillOnce(Invoke([&]() { return buildUnknownSamples(); })); + EXPECT_CALL(*testContext->m_dataReader, isBufferFull()).WillOnce(Return(true)); } void GenericTasksTestsBase::shouldNotAttachUnknownSamples() @@ -3121,7 +3437,9 @@ void GenericTasksTestsBase::triggerReadShmDataAndAttachSamples() void GenericTasksTestsBase::shouldFlushAudio() { + testContext->m_context.firstAudioFrameReceived = true; EXPECT_CALL(*testContext->m_gstWrapper, gstBufferUnref(&testContext->m_audioBuffer)); + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); EXPECT_CALL(testContext->m_gstPlayerClient, notifySourceFlushed(firebolt::rialto::MediaSourceType::AUDIO)); EXPECT_CALL(testContext->m_gstPlayer, setSourceFlushed(firebolt::rialto::MediaSourceType::AUDIO)); @@ -3142,7 +3460,8 @@ void GenericTasksTestsBase::triggerFlush(firebolt::rialto::MediaSourceType sourc &testContext->m_gstPlayerClient, testContext->m_gstWrapper, sourceType, - kResetTime}; + kResetTime, + kIsAsync}; task.execute(); } @@ -3159,6 +3478,7 @@ void GenericTasksTestsBase::checkAudioFlushed() testContext->m_context.endOfStreamInfo.end()); EXPECT_NE(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::VIDEO), testContext->m_context.endOfStreamInfo.end()); + EXPECT_FALSE(testContext->m_context.firstAudioFrameReceived); } void GenericTasksTestsBase::checkVideoFlushed() @@ -3178,7 +3498,6 @@ void GenericTasksTestsBase::checkVideoFlushed() void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(*testContext->m_gstWrapper, gstEventNewFlushStart()).WillOnce(Return(&testContext->m_event)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcVideo, &testContext->m_event)) .WillOnce(Return(TRUE)); @@ -3187,14 +3506,6 @@ void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() .WillOnce(Return(TRUE)); } -void GenericTasksTestsBase::shouldPostponeVideoFlush() -{ - testContext->m_context.flushOnPrerollController.setFlushing(firebolt::rialto::MediaSourceType::VIDEO, - GST_STATE_PLAYING); - testContext->m_context.flushOnPrerollController.stateReached(GST_STATE_PAUSED); - EXPECT_CALL(testContext->m_gstPlayer, postponeFlush(firebolt::rialto::MediaSourceType::VIDEO, kResetTime)); -} - void GenericTasksTestsBase::shouldSetSubtitleSourcePosition() { EXPECT_CALL(*testContext->m_glibWrapper, gObjectSetStub(&testContext->m_textTrackSink, StrEq("position"))); @@ -3369,6 +3680,20 @@ void GenericTasksTestsBase::triggerSetImmediateOutput() EXPECT_EQ(testContext->m_context.pendingImmediateOutputForVideo, true); } +void GenericTasksTestsBase::shouldSetReportDecodeErrors() +{ + EXPECT_CALL(testContext->m_gstPlayer, setReportDecodeErrors()).WillOnce(Return(true)); +} + +void GenericTasksTestsBase::triggerSetReportDecodeErrors() +{ + firebolt::rialto::server::tasks::generic::SetReportDecodeErrors task{testContext->m_context, testContext->m_gstPlayer, + MediaSourceType::VIDEO, true}; + task.execute(); + + EXPECT_EQ(testContext->m_context.pendingReportDecodeErrorsForVideo, true); +} + void GenericTasksTestsBase::shouldSetLowLatency() { EXPECT_CALL(testContext->m_gstPlayer, setLowLatency()).WillOnce(Return(true)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 646641e1d..97dc9dfcf 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -76,10 +76,12 @@ class GenericTasksTestsBase : public ::testing::Test void setContextStreamInfoEmpty(); void setContextNeedDataAudioOnly(); void setContextSetupSourceFinished(); + void setContextAudioInitialPosition(); // SetupElement test methods void shouldSetupVideoSinkElementOnly(); void shouldSetupVideoDecoderElementOnly(); + void shouldSetupVideoDecoderElementWithFirstVideoFrameCallback(); void shouldSetupVideoElementWithPendingGeometry(); void shouldSetupVideoElementWithPendingImmediateOutput(); void shouldSetupAudioSinkElementWithPendingLowLatency(); @@ -88,6 +90,7 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetupAudioDecoderElementWithPendingStreamSyncMode(); void shouldSetupVideoParserElementWithPendingStreamSyncMode(); void shouldSetupAudioDecoderElementWithPendingBufferingLimit(); + void shouldSetupAudioDecoderElementWithIsLiveParameter(); void shouldSetupVideoSinkElementWithPendingRenderFrame(); void shouldSetupVideoSinkElementWithPendingShowVideoWindow(); void shouldSetupAudioElementAmlhalasinkWhenNoVideo(); @@ -99,12 +102,20 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetupAudioElementAutoAudioSinkWithMultipleChildren(); void shouldSetupAudioSinkElementOnly(); void shouldSetupAudioDecoderElementOnly(); + void shouldSetupAudioDecoderElementWithFirstAudioFrameCallback(); + void shouldSetupAudioSinkElementWithFirstAudioFrameProbe(); void shouldSetVideoUnderflowCallback(); + void shouldSetFirstVideoFrameCallback(); + void shouldSetFirstAudioFrameCallback(); + void shouldSetFirstAudioFrameFallbackProbeCallback(); void shouldSetupBaseParse(); void triggerSetupElement(); void triggerVideoUnderflowCallback(); + void triggerFirstVideoFrameCallback(); + void triggerFirstAudioFrameCallback(); void shouldSetAudioUnderflowCallback(); void triggerAudioUnderflowCallback(); + void triggerFirstAudioFrameFallbackProbeCallback(); void shouldAddFirstAutoVideoSinkChild(); void shouldAddFirstAutoAudioSinkChild(); void shouldNotAddAutoVideoSinkChild(); @@ -148,6 +159,7 @@ class GenericTasksTestsBase : public ::testing::Test // AttachSamples test methods void shouldAttachAllAudioSamples(); + void shouldAttachAllAudioSamplesWithDelay(); void shouldAttachData(firebolt::rialto::MediaSourceType sourceType); void triggerAttachSamplesAudio(); void shouldAttachAllVideoSamples(); @@ -190,7 +202,6 @@ class GenericTasksTestsBase : public ::testing::Test void shouldAttachVideoSourceWithDolbyVisionSource(); void triggerAttachVideoSourceWithDolbyVisionSource(); void shouldReattachAudioSource(); - void shouldEnableAudioFlagsAndSendNeedData(); void shouldFailToReattachAudioSource(); void triggerReattachAudioSource(); void checkNewAudioSourceAttached(); @@ -239,6 +250,8 @@ class GenericTasksTestsBase : public ::testing::Test void checkPlaybackGroupAdded(); void setUseBufferingPending(); void shouldTriggerSetUseBuffering(); + void shouldLinkTypefindAndParser(); + void shouldFailToLinkTypefindAndParser(); // Stop test methods void shouldStopGstPlayer(); @@ -281,6 +294,10 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetVideoMute(); void shouldSetSubtitleMute(); + // report-decode-errors decoder property test method + void shouldSetReportDecodeErrors(); + void triggerSetReportDecodeErrors(); + // immediate-output sink property test methods void shouldSetImmediateOutput(); void triggerSetImmediateOutput(); @@ -392,6 +409,7 @@ class GenericTasksTestsBase : public ::testing::Test // ReadShmDataAndAttachSamples test methods void shouldReadAudioData(); + void shouldReadAudioDataFromShmWithAvailableSpace(); void shouldReadVideoData(); void shouldReadSubtitleData(); void shouldReadUnknownData(); @@ -402,7 +420,8 @@ class GenericTasksTestsBase : public ::testing::Test // RemoveSource test methods void shouldInvalidateActiveAudioRequests(); - void shouldDisableAudioFlag(); + void shouldUnrefAudioBuffer(); + void shouldRequestAudioData(); void triggerRemoveSourceAudio(); void triggerRemoveSourceVideo(); void checkAudioSourceRemoved(); @@ -415,7 +434,6 @@ class GenericTasksTestsBase : public ::testing::Test void checkAudioFlushed(); void checkVideoFlushed(); void shouldFlushVideoSrcSuccess(); - void shouldPostponeVideoFlush(); // Set Source Position test methods void shouldSetSubtitleSourcePosition(); @@ -442,9 +460,11 @@ class GenericTasksTestsBase : public ::testing::Test private: // SetupElement helper methods void expectVideoUnderflowSignalConnection(); + void expectFirstVideoFrameSignalConnection(); void expectAudioUnderflowSignalConnection(); void expectSetupVideoSinkElement(); void expectSetupVideoDecoderElement(); + void expectSetupVideoDecoderElementWithFirstVideoFrameCallback(); void expectSetupAudioSinkElement(); void expectSetupAudioDecoderElement(); void expectSetupVideoParserElement(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h index e51d1095e..4b78f1a75 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h @@ -26,6 +26,7 @@ #include "GlibWrapperMock.h" #include "GstGenericPlayerClientMock.h" #include "GstGenericPlayerPrivateMock.h" +#include "GstProfilerMock.h" #include "GstSrcMock.h" #include "GstTextTrackSinkFactoryMock.h" #include "GstWrapperMock.h" @@ -61,6 +62,8 @@ class GenericTasksTestsContext std::make_shared>()}; std::shared_ptr m_gstTextTrackSinkFactoryMock{ std::make_shared>()}; + std::unique_ptr<::testing::NiceMock> m_gstProfilerMock{ + std::make_unique<::testing::NiceMock>()}; // Gstreamer members GstElement *m_element{}; @@ -96,6 +99,9 @@ class GenericTasksTestsContext guint m_signals[1]{123}; GCallback m_audioUnderflowCallback; GCallback m_videoUnderflowCallback; + GCallback m_firstVideoFrameCallback; + GCallback m_firstAudioFrameCallback; + GstPadProbeCallback m_firstAudioFrameProbeCallback{}; GCallback m_childAddedCallback; GCallback m_childRemovedCallback; gchar m_capsStr{}; @@ -110,6 +116,7 @@ class GenericTasksTestsContext gchar m_xEac3Str[13]{"audio/x-eac3"}; gpointer m_videoUserData{}; gpointer m_audioUserData{}; + gpointer m_firstAudioFrameProbeUserData{}; GValue m_value{}; GParamSpec m_paramSpec{}; GObject m_gObj{}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp index 22c7b56b5..648509956 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp @@ -44,6 +44,8 @@ void GstGenericPlayerTestCommon::gstPlayerWillBeCreated() EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_READY)) .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); EXPECT_CALL(*m_gstSrcMock, initSrc()); + EXPECT_CALL(*m_gstProfilerFactoryMock, createGstProfiler(&m_pipeline, _, _)) + .WillOnce(Return(ByMove(std::move(m_gstProfiler)))); EXPECT_CALL(m_workerThreadFactoryMock, createWorkerThread()).WillOnce(Return(ByMove(std::move(workerThread)))); EXPECT_CALL(*m_gstProtectionMetadataFactoryMock, createProtectionMetadataWrapper(_)) .WillOnce(Return(ByMove(std::move(m_gstProtectionMetadataWrapper)))); @@ -54,10 +56,12 @@ void GstGenericPlayerTestCommon::gstPlayerWillBeDestroyed() { expectShutdown(); expectStop(); + EXPECT_CALL(*m_timerFactoryMock, createTimer(_, _, _)).Times(0); EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusSetSyncHandler(&m_bus, nullptr, nullptr, nullptr)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pipeline)); + EXPECT_CALL(*m_glibWrapperMock, gThreadPoolStopUnusedThreads()); } void GstGenericPlayerTestCommon::expectShutdown() @@ -124,7 +128,7 @@ void GstGenericPlayerTestCommon::expectSetFlags() { EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(4).WillRepeatedly(Return(m_gstPlayFlagsType)); EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(m_gstPlayFlagsType)).Times(4).WillRepeatedly(Return(&m_flagsClass)); - + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_flagsClass)).Times(4); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); @@ -139,7 +143,7 @@ void GstGenericPlayerTestCommon::expectSetFlagsWithNativeAudio() { EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(5).WillRepeatedly(Return(m_gstPlayFlagsType)); EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(m_gstPlayFlagsType)).Times(5).WillRepeatedly(Return(&m_flagsClass)); - + EXPECT_CALL(*m_glibWrapperMock, gTypeClassUnref(&m_flagsClass)).Times(5); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); @@ -199,7 +203,7 @@ void GstGenericPlayerTestCommon::expectCheckPlaySink() void GstGenericPlayerTestCommon::expectSetMessageCallback() { - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } @@ -217,6 +221,20 @@ void GstGenericPlayerTestCommon::expectGetDecoder(GstElement *element) EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); } +void GstGenericPlayerTestCommon::expectGetVideoDecoder(GstElement *element) +{ + EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); + EXPECT_CALL(*m_gstWrapperMock, gstIteratorNext(&m_it, _)).WillOnce(Return(GST_ITERATOR_OK)); + EXPECT_CALL(*m_glibWrapperMock, gValueGetObject(_)).WillOnce(Return(element)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(element)).WillOnce(Return(m_factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_factory, (GST_ELEMENT_FACTORY_TYPE_DECODER | + GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO))) + .WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(element)).WillOnce(Return(element)); + EXPECT_CALL(*m_glibWrapperMock, gValueUnset(_)); + EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); +} + void GstGenericPlayerTestCommon::expectGetVideoParser(GstElement *element) { EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); @@ -231,7 +249,7 @@ void GstGenericPlayerTestCommon::expectGetVideoParser(GstElement *element) EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); } -void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstElement *elementObj) +void GstGenericPlayerTestCommon::expectGetAVSink(const std::string &sinkName, GstElement *elementObj) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(sinkName.c_str()), _)) .WillOnce(Invoke( @@ -243,6 +261,17 @@ void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstE EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(elementObj))).WillOnce(Return(kElementTypeName.c_str())); } +void GstGenericPlayerTestCommon::expectGetSink(const std::string &sinkName, GstElement *elementObj) +{ + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(sinkName.c_str()), _)) + .WillOnce(Invoke( + [elementObj](gpointer object, const gchar *first_property_name, void *element) + { + GstElement **elementPtr = reinterpret_cast(element); + *elementPtr = elementObj; + })); +} + void GstGenericPlayerTestCommon::expectNoDecoder() { EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h index 8f5e11dff..6b3c97c2d 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h @@ -29,6 +29,8 @@ #include "GstGenericPlayer.h" #include "GstGenericPlayerClientMock.h" #include "GstInitialiserMock.h" +#include "GstProfilerFactoryMock.h" +#include "GstProfilerMock.h" #include "GstProtectionMetadataHelperFactoryMock.h" #include "GstProtectionMetadataHelperMock.h" #include "GstSrcFactoryMock.h" @@ -47,6 +49,7 @@ using namespace firebolt::rialto; using namespace firebolt::rialto::server; using namespace firebolt::rialto::wrappers; +using ::testing::NiceMock; using ::testing::StrictMock; namespace @@ -55,6 +58,7 @@ namespace const std::string kElementTypeName{"GenericSink"}; const std::string kAudioSinkStr{"audio-sink"}; const std::string kVideoSinkStr{"video-sink"}; +const std::string kTextSinkStr{"text-sink"}; } // namespace class GstGenericPlayerTestCommon : public ::testing::Test @@ -74,6 +78,10 @@ class GstGenericPlayerTestCommon : public ::testing::Test std::make_shared>()}; std::shared_ptr> m_gstSrcFactoryMock{std::make_shared>()}; std::shared_ptr> m_gstSrcMock{std::make_shared>()}; + std::shared_ptr> m_gstProfilerFactoryMock{ + std::make_shared>()}; + std::unique_ptr> m_gstProfiler{std::make_unique>()}; + NiceMock *m_gstProfilerMock{m_gstProfiler.get()}; std::shared_ptr> m_timerFactoryMock{std::make_shared>()}; std::unique_ptr m_taskFactory{std::make_unique>()}; StrictMock &m_taskFactoryMock{ @@ -118,7 +126,9 @@ class GstGenericPlayerTestCommon : public ::testing::Test void expectCheckPlaySink(); void expectSetMessageCallback(); void expectGetDecoder(GstElement *element); + void expectGetVideoDecoder(GstElement *element); void expectGetVideoParser(GstElement *element); + void expectGetAVSink(const std::string &sinkName, GstElement *elementObj); void expectGetSink(const std::string &sinkName, GstElement *elementObj); void expectNoDecoder(); void expectNoParser(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp index 2b241b9fd..8b2775fc0 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp @@ -161,18 +161,12 @@ TEST_F(AttachSourceTest, shouldFailToAttachUnknownSource) triggerAttachUnknownSource(); } -TEST_F(AttachSourceTest, shouldSkipSwitchAudioSourceWhenSourceIsNotRemoved) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - triggerReattachAudioSource(); -} - TEST_F(AttachSourceTest, shouldReattachAudioSource) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); setContextAudioSourceRemoved(); shouldReattachAudioSource(); - shouldEnableAudioFlagsAndSendNeedData(); + shouldRequestAudioData(); triggerReattachAudioSource(); checkNewAudioSourceAttached(); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp index 6afd71360..15acbc1f8 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp @@ -83,7 +83,6 @@ TEST_F(FinishSetupSourceTest, shouldScheduleAudioSeekData) shouldFinishSetupSource(); triggerFinishSetupSource(); checkSetupSourceFinished(); - shouldScheduleEnoughDataAudio(); triggerAudioCallbackSeekData(); } @@ -92,7 +91,6 @@ TEST_F(FinishSetupSourceTest, shouldScheduleVideoSeekData) shouldFinishSetupSource(); triggerFinishSetupSource(); checkSetupSourceFinished(); - shouldScheduleEnoughDataVideo(); triggerVideoCallbackSeekData(); } TEST_F(FinishSetupSourceTest, shouldntFinishSetupSourceWhenSourceNotSet) diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp new file mode 100644 index 000000000..76c2563ee --- /dev/null +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp @@ -0,0 +1,69 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "tasks/generic/FirstFrameReceived.h" +#include "GstGenericPlayerClientMock.h" +#include "GstGenericPlayerPrivateMock.h" + +#include +#include + +using testing::StrictMock; + +namespace +{ +constexpr firebolt::rialto::MediaSourceType kSourceType{firebolt::rialto::MediaSourceType::VIDEO}; +constexpr firebolt::rialto::MediaSourceType kAudioSourceType{firebolt::rialto::MediaSourceType::AUDIO}; +} // namespace + +class FirstFrameReceivedTest : public testing::Test +{ +protected: + firebolt::rialto::server::GenericPlayerContext m_context; + StrictMock m_gstPlayer; + StrictMock m_gstPlayerClient; +}; + +TEST_F(FirstFrameReceivedTest, shouldNotifyFirstFrameReceived) +{ + firebolt::rialto::server::tasks::generic::FirstFrameReceived task{m_context, m_gstPlayer, &m_gstPlayerClient, + kSourceType}; + + EXPECT_CALL(m_gstPlayerClient, notifyFirstFrameReceived(kSourceType)); + + task.execute(); +} + +TEST_F(FirstFrameReceivedTest, shouldNotNotifyFirstFrameReceivedWhenClientIsNull) +{ + firebolt::rialto::server::tasks::generic::FirstFrameReceived task{m_context, m_gstPlayer, nullptr, kSourceType}; + + task.execute(); +} + +TEST_F(FirstFrameReceivedTest, shouldNotifyFirstAudioFrameReceived) +{ + firebolt::rialto::server::tasks::generic::FirstFrameReceived task{m_context, m_gstPlayer, &m_gstPlayerClient, + kAudioSourceType}; + + EXPECT_CALL(m_gstPlayer, clearAudioFirstFrameFallbackProbe()); + EXPECT_CALL(m_gstPlayerClient, notifyFirstFrameReceived(kAudioSourceType)); + + task.execute(); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp index 64017d24d..6603d681b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp @@ -108,9 +108,3 @@ TEST_F(FlushTest, ShouldFlushVideoWithNeedData) triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); checkVideoFlushed(); } - -TEST_F(FlushTest, ShouldPostponeFlush) -{ - shouldPostponeVideoFlush(); - triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 7693164c0..bc7362869 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -36,6 +36,7 @@ #include "tasks/generic/EnoughData.h" #include "tasks/generic/Eos.h" #include "tasks/generic/FinishSetupSource.h" +#include "tasks/generic/FirstFrameReceived.h" #include "tasks/generic/Flush.h" #include "tasks/generic/GenericPlayerTaskFactory.h" #include "tasks/generic/HandleBusMessage.h" @@ -54,6 +55,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSync.h" @@ -112,11 +114,14 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateAttachSource) TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateDeepElementAdded) { + GstElement element{}; EXPECT_CALL(*m_gstWrapper, gstObjectParent(_)).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapper, gstObjectCast(_)).WillOnce(Return(nullptr)); EXPECT_CALL(*m_gstWrapper, gstElementGetName(_)).WillOnce(Return(nullptr)); EXPECT_CALL(*m_glibWrapper, gFree(nullptr)); - auto task = m_sut.createDeepElementAdded(m_context, m_gstPlayer, nullptr, nullptr, nullptr); + // The task destructor releases the element reference taken when the task is scheduled. + EXPECT_CALL(*m_gstWrapper, gstObjectUnref(&element)); + auto task = m_sut.createDeepElementAdded(m_context, m_gstPlayer, nullptr, nullptr, &element); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } @@ -303,6 +308,13 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateUnderflow) EXPECT_NO_THROW(dynamic_cast(*task)); } +TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateFirstFrameReceived) +{ + auto task = m_sut.createFirstFrameReceived(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_NE(task, nullptr); + EXPECT_NO_THROW(dynamic_cast(*task)); +} + TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetPlaybackRate) { auto task = m_sut.createSetPlaybackRate(m_context, 1.25); @@ -312,7 +324,10 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetPlaybackRate) TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateUpdatePlaybackGroup) { - auto task = m_sut.createUpdatePlaybackGroup(m_context, m_gstPlayer, nullptr, nullptr); + GstElement typefind{}; + // The task destructor releases the typefind reference taken when the task is scheduled. + EXPECT_CALL(*m_gstWrapper, gstObjectUnref(&typefind)); + auto task = m_sut.createUpdatePlaybackGroup(m_context, m_gstPlayer, &typefind, nullptr); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } @@ -326,7 +341,7 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreatePing) TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateFlush) { - auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true); + auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true, true); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } @@ -352,6 +367,13 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetImmediateOutput) EXPECT_NO_THROW(dynamic_cast(*task)); } +TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetReportDecodeErrors) +{ + auto task = m_sut.createSetReportDecodeErrors(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::VIDEO, true); + EXPECT_NE(task, nullptr); + EXPECT_NO_THROW(dynamic_cast(*task)); +} + TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetTextTrackIdentifier) { auto task = m_sut.createSetTextTrackIdentifier(m_context, ""); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp index 1295f2b9b..0ac970859 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp @@ -23,6 +23,7 @@ #include "GlibWrapperMock.h" #include "GstGenericPlayerClientMock.h" #include "GstGenericPlayerPrivateMock.h" +#include "GstProfilerMock.h" #include "GstWrapperMock.h" #include "Matchers.h" #include @@ -56,6 +57,8 @@ class HandleBusMessageTest : public testing::Test std::shared_ptr m_glibWrapper{ std::make_shared>()}; StrictMock m_flushWatcherMock; + std::unique_ptr<::testing::NiceMock> m_gstProfilerMock{ + std::make_unique<::testing::NiceMock>()}; GstElement m_pipeline{}; GstAppSrc m_audioSrc{}; GstAppSrc m_videoSrc{}; @@ -72,6 +75,7 @@ class HandleBusMessageTest : public testing::Test m_context.pipeline = &m_pipeline; m_context.streamInfo.emplace(firebolt::rialto::MediaSourceType::AUDIO, GST_ELEMENT(&m_audioSrc)); m_context.streamInfo.emplace(firebolt::rialto::MediaSourceType::VIDEO, GST_ELEMENT(&m_videoSrc)); + m_context.gstProfiler = std::move(m_gstProfilerMock); } void expectFreeErrorMessage() @@ -227,8 +231,8 @@ TEST_F(HandleBusMessageTest, shouldNotHandleStateChangedMessageWhenGstPlayerClie EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Null")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Null")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); @@ -250,8 +254,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToNullMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Null")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Null")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::STOPPED)); @@ -276,12 +280,13 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::PAUSED)); - EXPECT_CALL(m_gstPlayer, notifyPlaybackInfo()); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -305,12 +310,13 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedMessageWhenSyncFlus EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayerClient, notifyPlaybackState(firebolt::rialto::PlaybackState::PAUSED)); - EXPECT_CALL(m_gstPlayer, notifyPlaybackInfo()); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -333,10 +339,12 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPausedMessageWhenAs EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kFlushOngoing)); @@ -359,10 +367,12 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPausedMessageWhenAs EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Paused")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kNoFlushOngoing)).WillOnce(Return(kFlushOngoing)); @@ -386,6 +396,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPausedAndPendingPausedMes EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(GST_STATE_PAUSED)).WillRepeatedly(Return("Paused")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); + EXPECT_CALL(m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); + EXPECT_CALL(m_gstPlayer, startNotifyPlaybackInfoTimer()); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); @@ -408,8 +420,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessage) EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -417,7 +429,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessage) EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -441,8 +452,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageWhenSyncFlu EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -450,7 +461,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageWhenSyncFlu EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -473,14 +483,13 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -501,14 +510,13 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kNoFlushOngoing)).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -528,8 +536,8 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageAndSetPendi EXPECT_CALL(*m_gstWrapper, gstMessageParseStateChanged(&m_message, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); EXPECT_CALL(m_gstPlayer, hasSourceType(firebolt::rialto::MediaSourceType::SUBTITLE)).WillOnce(Return(false)); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(2).WillRepeatedly(Return("Ready")); - EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(2).WillRepeatedly(Return("Playing")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(oldState)).Times(1).WillRepeatedly(Return("Ready")); + EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(newState)).Times(1).WillRepeatedly(Return("Playing")); EXPECT_CALL(*m_gstWrapper, gstElementStateGetName(pending)).WillOnce(Return("Void")); EXPECT_CALL(*m_gstWrapper, gstDebugBinToDotFileWithTs(GST_BIN(&m_pipeline), _, _)); EXPECT_CALL(m_gstPlayer, startPositionReportingAndCheckAudioUnderflowTimer()); @@ -538,7 +546,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageAndSetPendi EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -912,3 +919,61 @@ TEST_F(HandleBusMessageTest, shouldHandleWarningMessageForUnknownSrcTypeDecrypti g_object_unref(element); } + +/** + * Test HandleBusMessage notifies output protection errors for HDCP application messages from video decryptors. + */ +TEST_F(HandleBusMessageTest, shouldHandleApplicationMessageForVideoOutputProtection) +{ + GstObject *decryptor = GST_OBJECT_CAST(g_object_new(GST_TYPE_BIN, nullptr)); + gst_object_set_name(decryptor, "rialtodecryptorvideo_0"); + GstStructure *hdcpFailureStructure = + gst_structure_new("HDCPProtectionFailure", "message", G_TYPE_STRING, "HDCP Output Protection Error", NULL); + GstMessage *message = gst_message_new_application(decryptor, hdcpFailureStructure); + + EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); + EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); + EXPECT_CALL(*m_gstWrapper, gstStructureHasName(hdcpFailureStructure, StrEq("HDCPProtectionFailure"))) + .WillOnce(Return(true)); + EXPECT_CALL(m_gstPlayerClient, notifyPlaybackError(firebolt::rialto::MediaSourceType::VIDEO, + firebolt::rialto::PlaybackError::OUTPUT_PROTECTION)); + EXPECT_CALL(*m_gstWrapper, gstMessageUnref(message)); + + firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, + &m_gstPlayerClient, m_gstWrapper, + m_glibWrapper, message, + m_flushWatcherMock}; + task.execute(); + + gst_message_unref(message); + g_object_unref(decryptor); +} + +/** + * Test HandleBusMessage notifies output protection errors for HDCP application messages from audio decryptors. + */ +TEST_F(HandleBusMessageTest, shouldHandleApplicationMessageForAudioOutputProtection) +{ + GstObject *decryptor = GST_OBJECT_CAST(g_object_new(GST_TYPE_BIN, nullptr)); + gst_object_set_name(decryptor, "rialtodecryptoraudio_0"); + GstStructure *hdcpFailureStructure = + gst_structure_new("HDCPProtectionFailure", "message", G_TYPE_STRING, "HDCP Output Protection Error", NULL); + GstMessage *message = gst_message_new_application(decryptor, hdcpFailureStructure); + + EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); + EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); + EXPECT_CALL(*m_gstWrapper, gstStructureHasName(hdcpFailureStructure, StrEq("HDCPProtectionFailure"))) + .WillOnce(Return(true)); + EXPECT_CALL(m_gstPlayerClient, notifyPlaybackError(firebolt::rialto::MediaSourceType::AUDIO, + firebolt::rialto::PlaybackError::OUTPUT_PROTECTION)); + EXPECT_CALL(*m_gstWrapper, gstMessageUnref(message)); + + firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, + &m_gstPlayerClient, m_gstWrapper, + m_glibWrapper, message, + m_flushWatcherMock}; + task.execute(); + + gst_message_unref(message); + g_object_unref(decryptor); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp index 1b7663034..0406c6040 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp @@ -65,3 +65,10 @@ TEST_F(ReadShmDataAndAttachSamplesTest, shouldSkipAttachingUnknownSamples) shouldNotAttachUnknownSamples(); triggerReadShmDataAndAttachSamples(); } + +TEST_F(ReadShmDataAndAttachSamplesTest, shouldAttachAllAudioSamplesWithDelay) +{ + shouldReadAudioDataFromShmWithAvailableSpace(); + shouldAttachAllAudioSamplesWithDelay(); + triggerReadShmDataAndAttachSamplesAudio(); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp index a9153ff75..3288a7d43 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp @@ -50,21 +50,21 @@ TEST_F(RemoveSourceTest, shouldNotRemoveVideoSource) TEST_F(RemoveSourceTest, shouldRemoveAudioSource) { + setContextAudioInitialPosition(); shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcSuccess(); + shouldUnrefAudioBuffer(); triggerRemoveSourceAudio(); checkNoMoreNeedData(); checkNoNeedDataPendingForBothSources(); checkAudioSourceRemoved(); checkBuffersEmpty(); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::AUDIO); } TEST_F(RemoveSourceTest, shouldRemoveAudioSourceFlushEventError) { shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcFailure(); + shouldUnrefAudioBuffer(); triggerRemoveSourceAudio(); checkNoMoreNeedData(); checkNoNeedDataPendingForBothSources(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..3d3f89f96 --- /dev/null +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.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 2025 Sky UK + * + * 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 "GenericTasksTestsBase.h" + +class SetReportDecodeErrorsTest : public GenericTasksTestsBase +{ +}; + +TEST_F(SetReportDecodeErrorsTest, shouldSetReportDecodeErrors) +{ + shouldSetReportDecodeErrors(); + triggerSetReportDecodeErrors(); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp index 89b5bc979..fa0d75007 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp @@ -77,6 +77,12 @@ TEST_F(SetupElementTest, shouldSetupAudioElementWithPendingBufferingLimit) triggerSetupElement(); } +TEST_F(SetupElementTest, shouldSetupAudioElementWithIsLiveParameter) +{ + shouldSetupAudioDecoderElementWithIsLiveParameter(); + triggerSetupElement(); +} + TEST_F(SetupElementTest, shouldSetupVideoElementWithPendingRenderFrame) { shouldSetupVideoSinkElementWithPendingRenderFrame(); @@ -164,6 +170,15 @@ TEST_F(SetupElementTest, shouldReportVideoUnderflow) triggerVideoUnderflowCallback(); } +TEST_F(SetupElementTest, shouldReportFirstVideoFrame) +{ + shouldSetupVideoDecoderElementWithFirstVideoFrameCallback(); + triggerSetupElement(); + + shouldSetFirstVideoFrameCallback(); + triggerFirstVideoFrameCallback(); +} + TEST_F(SetupElementTest, shouldReportAudioUnderflow) { shouldSetupAudioDecoderElementOnly(); @@ -173,6 +188,24 @@ TEST_F(SetupElementTest, shouldReportAudioUnderflow) triggerAudioUnderflowCallback(); } +TEST_F(SetupElementTest, shouldReportFirstAudioFrameFromSignal) +{ + shouldSetupAudioDecoderElementWithFirstAudioFrameCallback(); + triggerSetupElement(); + + shouldSetFirstAudioFrameCallback(); + triggerFirstAudioFrameCallback(); +} + +TEST_F(SetupElementTest, shouldReportFirstAudioFrameFromFallbackProbe) +{ + shouldSetupAudioSinkElementWithFirstAudioFrameProbe(); + triggerSetupElement(); + + shouldSetFirstAudioFrameFallbackProbeCallback(); + triggerFirstAudioFrameFallbackProbeCallback(); +} + TEST_F(SetupElementTest, shouldReportAutoVideoSinkChildAdded) { shouldSetupVideoElementAutoVideoSink(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp index dfed351b4..74312e50f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp @@ -72,3 +72,19 @@ TEST_F(UpdatePlaybackGroupTest, shouldTriggerUseBuffering) triggerUpdatePlaybackGroup(); checkPlaybackGroupAdded(); } + +TEST_F(UpdatePlaybackGroupTest, shouldLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} + +TEST_F(UpdatePlaybackGroupTest, shouldFailToLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldFailToLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} diff --git a/tests/unittests/media/server/gstplayer/main.cpp b/tests/unittests/media/server/gstplayer/main.cpp index 22ab93d53..7138410b3 100644 --- a/tests/unittests/media/server/gstplayer/main.cpp +++ b/tests/unittests/media/server/gstplayer/main.cpp @@ -17,6 +17,8 @@ * limitations under the License. */ +#include "GlibWrapperFactoryMock.h" +#include "GlibWrapperMock.h" #include "GstInitialiser.h" #include "GstWrapperFactoryMock.h" #include "GstWrapperMock.h" @@ -39,6 +41,12 @@ void initialiseGstreamer() EXPECT_CALL(*gstWrapperFactoryMock, getGstWrapper()).WillOnce(Return(gstWrapperMock)); IFactoryAccessor::instance().gstWrapperFactory() = gstWrapperFactoryMock; + std::shared_ptr> glibWrapperFactoryMock{ + std::make_shared>()}; + std::shared_ptr> glibWrapperMock{std::make_shared>()}; + EXPECT_CALL(*glibWrapperFactoryMock, getGlibWrapper()).WillOnce(Return(glibWrapperMock)); + IFactoryAccessor::instance().glibWrapperFactory() = glibWrapperFactoryMock; + // Hack to mock GstPlugin to cover one of ifs in code uint8_t dummyMemory; GstPlugin *plugin{reinterpret_cast(&dummyMemory)}; @@ -47,10 +55,13 @@ void initialiseGstreamer() EXPECT_CALL(*gstWrapperMock, gstRegistryFindPlugin(nullptr, _)).WillOnce(Return(plugin)); EXPECT_CALL(*gstWrapperMock, gstRegistryRemovePlugin(nullptr, plugin)); EXPECT_CALL(*gstWrapperMock, gstObjectUnref(plugin)); + EXPECT_CALL(*glibWrapperMock, gThreadPoolSetMaxUnusedThreads(2)); + EXPECT_CALL(*glibWrapperMock, gThreadPoolSetMaxIdleTime(5 * 1000)); firebolt::rialto::server::IGstInitialiser::instance().initialise(nullptr, nullptr); firebolt::rialto::server::IGstInitialiser::instance().waitForInitialisation(); IFactoryAccessor::instance().gstWrapperFactory() = nullptr; + IFactoryAccessor::instance().glibWrapperFactory() = nullptr; } int main(int argc, char **argv) // NOLINT(build/filename_format) diff --git a/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp b/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp new file mode 100644 index 000000000..0a31abe16 --- /dev/null +++ b/tests/unittests/media/server/gstplayer/profiler/GstProfilerTests.cpp @@ -0,0 +1,563 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "GlibWrapperMock.h" +#include "GstWrapperMock.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "GstProfiler.h" + +using namespace firebolt::rialto::server; +using namespace firebolt::rialto::wrappers; + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::ReturnNull; +using ::testing::StrEq; +using ::testing::StrictMock; + +class GstProfilerTests : public ::testing::Test +{ +protected: + std::shared_ptr> m_gstWrapperMock; + std::shared_ptr> m_glibWrapperMock; + std::unique_ptr m_gstProfiler; + std::optional m_profilerEnabledEnv; + std::optional m_profilerDumpFileNameEnv; + GstElement *m_realElement{nullptr}; + + GstElement m_pipeline = {}; + GstElement m_element = {}; + GstPad m_pad = {}; + + void SetUp() override + { + saveEnv("PROFILER_ENABLED", m_profilerEnabledEnv); + saveEnv("PROFILER_DUMP_FILE_NAME", m_profilerDumpFileNameEnv); + + m_gstWrapperMock = std::make_shared>(); + m_glibWrapperMock = std::make_shared>(); + + gst_init(nullptr, nullptr); + m_realElement = gst_element_factory_make("fakesrc", "profiler-test-source"); + ASSERT_NE(m_realElement, nullptr); + } + + void TearDown() override + { + if (m_realElement) + { + gst_object_unref(m_realElement); + m_realElement = nullptr; + } + + if (m_profilerEnabledEnv) + setenv("PROFILER_ENABLED", m_profilerEnabledEnv->c_str(), 1); + else + unsetenv("PROFILER_ENABLED"); + + if (m_profilerDumpFileNameEnv) + setenv("PROFILER_DUMP_FILE_NAME", m_profilerDumpFileNameEnv->c_str(), 1); + else + unsetenv("PROFILER_DUMP_FILE_NAME"); + } + + void setProfilerEnabledEnv(bool enabled) { setenv("PROFILER_ENABLED", enabled ? "true" : "false", 1); } + void setProfilerDumpFileEnv(const std::string &path) { setenv("PROFILER_DUMP_FILE_NAME", path.c_str(), 1); } + + static void saveEnv(const char *name, std::optional &value) + { + if (const char *env = std::getenv(name)) + value = env; + else + value = std::nullopt; + } + + void createGstProfiler(GstElement *pipeline = nullptr) + { + const char *profilerEnabledEnv = std::getenv("PROFILER_ENABLED"); + const bool expectPipelineRef = pipeline && profilerEnabledEnv && std::string_view{profilerEnabledEnv} == "true"; + if (expectPipelineRef) + { + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(pipeline)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(pipeline)); + } + + EXPECT_NO_THROW(m_gstProfiler = std::make_unique(pipeline, m_gstWrapperMock, m_glibWrapperMock)); + ASSERT_NE(m_gstProfiler, nullptr); + } + + void expectElementRecognizedAsSource(GstElement *element) + { + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + } +}; + +/** + * Test that GstProfiler can be created with null pipeline. + */ +TEST_F(GstProfilerTests, CreateWithNullPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + EXPECT_TRUE(m_gstProfiler->createRecord("CreateWithNullPipeline").has_value()); +} + +/** + * Test that GstProfiler can be created with valid pipeline. + */ +TEST_F(GstProfilerTests, CreateWithPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(&m_pipeline); + EXPECT_TRUE(m_gstProfiler->createRecord("CreateWithPipeline").has_value()); +} + +/** + * Test that GstProfiler can create a record with stage only. + */ +TEST_F(GstProfilerTests, CreateRecordStageOnly) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW({ [[maybe_unused]] const auto id = m_gstProfiler->createRecord("PipelineCreated"); }); +} + +/** + * Test that GstProfiler can create a record with stage and info. + */ +TEST_F(GstProfilerTests, CreateRecordStageAndInfo) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW({ [[maybe_unused]] const auto id = m_gstProfiler->createRecord("SourceAttached", "video"); }); +} + +/** + * Test that GstProfiler can log a record. + */ +TEST_F(GstProfilerTests, LogRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->logRecord(1)); +} + +TEST_F(GstProfilerTests, DumpToFileCreatesAndAppendsDump) +{ + setProfilerEnabledEnv(true); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_gst_profiler_ut_dump_"} + suffix + ".txt"; + setProfilerDumpFileEnv(path); + createGstProfiler(); + + ASSERT_TRUE(m_gstProfiler->createRecord("Pipeline Created").has_value()); + ASSERT_TRUE(m_gstProfiler->createRecord("Pipeline Terminated").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("Pipeline Created"), std::string::npos); + EXPECT_NE(content.find("Pipeline Terminated"), std::string::npos); + EXPECT_NE(content.find("Pipeline Created"), content.rfind("Pipeline Created")); + + std::remove(path.c_str()); +} + +TEST_F(GstProfilerTests, DumpToFileUsesCachedEnvValue) +{ + setProfilerEnabledEnv(true); + const auto suffix = std::to_string(std::random_device{}()); + const std::string path = std::string{"/tmp/rialto_gst_profiler_cached_dump_"} + suffix + ".txt"; + setProfilerDumpFileEnv(path); + createGstProfiler(); + + unsetenv("PROFILER_DUMP_FILE_NAME"); + ASSERT_TRUE(m_gstProfiler->createRecord("CachedDumpStage").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->dumpToFile()); + + std::ifstream in(path); + ASSERT_TRUE(in.good()); + + const std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + EXPECT_NE(content.find("CachedDumpStage"), std::string::npos); + + std::remove(path.c_str()); +} + +/** + * Test that GstProfiler can log pipeline metrics. + */ +TEST_F(GstProfilerTests, LogPipelineSummary) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->logPipelineSummary()); +} + +/** + * Test that disabled profiler prevents record creation and scheduling. + */ +TEST_F(GstProfilerTests, DisabledProfilerPreventsRecordCreationAndScheduling) +{ + setProfilerEnabledEnv(false); + createGstProfiler(); + + EXPECT_FALSE(m_gstProfiler->createRecord("Pipeline Created").has_value()); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that enabled profiler allows scheduling path to proceed up to pad lookup. + */ +TEST_F(GstProfilerTests, EnabledProfilerAllowsScheduling) +{ + setProfilerEnabledEnv(true); + EXPECT_NO_THROW(m_gstProfiler = std::make_unique(nullptr, m_gstWrapperMock, m_glibWrapperMock)); + ASSERT_NE(m_gstProfiler, nullptr); + ASSERT_TRUE(m_gstProfiler->createRecord("EnabledProfilerAllowsScheduling").has_value()); + + expectElementRecognizedAsSource(m_realElement); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduling record for null element is safe. + */ +TEST_F(GstProfilerTests, ScheduleNullElementRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(nullptr)); +} + +/** + * Test that scheduling record for valid element is safe. + */ +TEST_F(GstProfilerTests, ScheduleElementRecord) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) -> gulong + { + EXPECT_EQ(destroyData, nullptr); + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&m_pad, 1)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduled probe creates record with normalized video info. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordCreatesProbeRecordWithVideoInfo) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(true)); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "Video"); +} + +/** + * Test that scheduled probe falls back to element name classification when media type helpers fail. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordCreatesProbeRecordWithVideoInfoFromNameFallback) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "Video"); +} + +/** + * Test that scheduled probe falls back to raw element name when media type cannot be classified. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordFallsBackToRawElementName) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("custom-element"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + GstPadProbeCallback callback = nullptr; + gpointer userData = nullptr; + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)) + .WillOnce(Invoke( + [&callback, &userData](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback probeCallback, + gpointer probeUserData, GDestroyNotify destroyData) -> gulong + { + callback = probeCallback; + userData = probeUserData; + EXPECT_EQ(destroyData, nullptr); + + return 1; + })); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + m_gstProfiler->scheduleGstElementRecord(m_realElement); + + ASSERT_NE(callback, nullptr); + ASSERT_NE(userData, nullptr); + GstBuffer *buffer = gst_buffer_new(); + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = buffer; + + EXPECT_EQ(callback(&m_pad, &info, userData), GST_PAD_PROBE_REMOVE); + + gst_buffer_unref(buffer); + + const auto records = m_gstProfiler->getRecords(); + ASSERT_FALSE(records.empty()); + EXPECT_EQ(records.back().stage, "Source FB Exit"); + EXPECT_EQ(records.back().info, "custom-element"); +} + +/** + * Test that scheduling record handles missing src pad. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordNoPad) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + expectElementRecognizedAsSource(m_realElement); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that scheduling record handles probe installation failure without leaking context ownership. + */ +TEST_F(GstProfilerTests, ScheduleElementRecordProbeAddFails) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + auto *factory = reinterpret_cast(0x1); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(m_realElement)).WillOnce(Return(factory)).WillOnce(Return(factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementClassGetMetadata(_, _)).WillOnce(Return("Source")); + EXPECT_CALL(*m_glibWrapperMock, gStrrstr(_, _)).WillOnce(Return(const_cast("Source"))); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(&m_pad)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(_, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(false)); + + gchar *rawName = g_strdup("videoDecoder"); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetName(m_realElement)).WillOnce(Return(rawName)); + EXPECT_CALL(*m_glibWrapperMock, gFree(rawName)).WillOnce(Invoke([](gpointer ptr) { g_free(ptr); })); + + EXPECT_CALL(*m_gstWrapperMock, gstPadAddProbe(&m_pad, _, _, _, _)).WillOnce(Return(0)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_pad)); + + EXPECT_NO_THROW(m_gstProfiler->scheduleGstElementRecord(m_realElement)); +} + +/** + * Test that GstProfiler can be destroyed after creation with pipeline. + */ +TEST_F(GstProfilerTests, DestroyAfterCreateWithPipeline) +{ + setProfilerEnabledEnv(true); + createGstProfiler(&m_pipeline); + + EXPECT_NO_THROW(m_gstProfiler.reset()); +} + +/** + * Test that public API can be called multiple times. + */ +TEST_F(GstProfilerTests, MultipleCalls) +{ + setProfilerEnabledEnv(true); + createGstProfiler(); + + expectElementRecognizedAsSource(m_realElement); + + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(m_realElement, StrEq("src"))).WillOnce(Return(nullptr)); + + EXPECT_NO_THROW({ + [[maybe_unused]] const auto id1 = m_gstProfiler->createRecord("PipelineCreated"); + [[maybe_unused]] const auto id2 = m_gstProfiler->createRecord("AllSourcesAttached", "audio+video"); + m_gstProfiler->logRecord(1); + m_gstProfiler->scheduleGstElementRecord(m_realElement); + m_gstProfiler->logPipelineSummary(); + }); +} diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp index 0a61ceeb5..a0fa43a6b 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp @@ -500,7 +500,7 @@ TEST_F(RialtoServerCreateGstWebAudioPlayerTest, createGstDispatcherThreadFailure expectInitAppSrc(); expectAddElementsAutoAudioSink(); expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)).WillOnce(Return(nullptr)); // Reset worker thread and pipeline on failure gstPlayerWillBeDestroyed(); diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/GstWebAudioPlayerTest.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/GstWebAudioPlayerTest.cpp index 2f8b9ad37..2c6be6022 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/GstWebAudioPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/GstWebAudioPlayerTest.cpp @@ -21,6 +21,7 @@ #include "HeartbeatHandlerMock.h" #include "PlayerTaskMock.h" #include "WebAudioUtil.h" +#include #include using testing::DoAll; @@ -63,6 +64,7 @@ class GstWebAudioPlayerTest : public GstWebAudioPlayerTestCommon { std::unique_lock lock(context.writeBufferMutex); context.lastBytesWritten = m_bytesWritten; + ++context.writeCompletionCounter; } context.writeBufferCond.notify_one(); } diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp index 96980db37..42e890c8d 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp @@ -107,7 +107,7 @@ void GstWebAudioPlayerTestCommon::expectInitWorkerThread() void GstWebAudioPlayerTestCommon::expectInitThreads() { expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } diff --git a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp index 89d61c1e7..d1b79780f 100644 --- a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp @@ -106,3 +106,12 @@ TEST_F(ControlModuleServiceTests, FactoryCreatesObject) { testFactoryCreatesObject(); } + +TEST_F(ControlModuleServiceTests, shouldRegisterClientWithoutSchemaVersion) +{ + clientWillConnect(); + sendClientConnected(); + controlServiceWillRegisterClient(); + sendRegisterClientRequestAndReceiveResponse(std::nullopt); + controlServiceWillRemoveControl(); +} diff --git a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTests.cpp index 8df5690b4..444e9c698 100644 --- a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTests.cpp @@ -55,6 +55,18 @@ TEST_F(MediaKeysCapabilitiesModuleServiceTests, shouldSupportServerCertificate) sendIsServerCertificateSupportedRequestAndReceiveResponse(); } +TEST_F(MediaKeysCapabilitiesModuleServiceTests, shouldGetSupportedRobustnessLevels) +{ + cdmServiceWillGetSupportedRobustnessLevels(); + sendGetSupportedRobustnessLevelsRequestAndReceiveResponse(); +} + +TEST_F(MediaKeysCapabilitiesModuleServiceTests, shouldFailToGetSupportedRobustnessLevels) +{ + cdmServiceWillFailToGetSupportedRobustnessLevels(); + sendGetSupportedRobustnessLevelsRequestAndExpectFailure(); +} + TEST_F(MediaKeysCapabilitiesModuleServiceTests, FactoryCreatesObject) { testFactoryCreatesObject(); diff --git a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.cpp index c6462712e..bf6cc0cc1 100644 --- a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.cpp @@ -36,6 +36,7 @@ namespace const std::vector keySystems{"expectedKeySystem1", "expectedKeySystem2", "expectedKeySystem3"}; const std::string kVersion{"123"}; constexpr bool kIsKeySystemSupported{true}; +const std::vector kRobustnessLevels{"HW_SECURE_ALL", "SW_SECURE_CRYPTO"}; } // namespace MediaKeysCapabilitiesModuleServiceTests::MediaKeysCapabilitiesModuleServiceTests() @@ -85,6 +86,19 @@ void MediaKeysCapabilitiesModuleServiceTests::cdmServiceWillSupportServerCertifi EXPECT_CALL(m_cdmServiceMock, isServerCertificateSupported(keySystems[0])).WillOnce(Return(true)); } +void MediaKeysCapabilitiesModuleServiceTests::cdmServiceWillGetSupportedRobustnessLevels() +{ + expectRequestSuccess(); + EXPECT_CALL(m_cdmServiceMock, getSupportedRobustnessLevels(keySystems[0], _)) + .WillOnce(DoAll(SetArgReferee<1>(kRobustnessLevels), Return(true))); +} + +void MediaKeysCapabilitiesModuleServiceTests::cdmServiceWillFailToGetSupportedRobustnessLevels() +{ + expectRequestFailure(); + EXPECT_CALL(m_cdmServiceMock, getSupportedRobustnessLevels(keySystems[0], _)).WillOnce(Return(false)); +} + void MediaKeysCapabilitiesModuleServiceTests::sendClientConnected() { m_service->clientConnected(m_clientMock); @@ -143,6 +157,29 @@ void MediaKeysCapabilitiesModuleServiceTests::sendIsServerCertificateSupportedRe EXPECT_TRUE(response.is_supported()); } +void MediaKeysCapabilitiesModuleServiceTests::sendGetSupportedRobustnessLevelsRequestAndReceiveResponse() +{ + firebolt::rialto::GetSupportedRobustnessLevelsRequest request; + firebolt::rialto::GetSupportedRobustnessLevelsResponse response; + + request.set_key_system(keySystems[0]); + + m_service->getSupportedRobustnessLevels(m_controllerMock.get(), &request, &response, m_closureMock.get()); + EXPECT_EQ((std::vector{response.robustness_levels().begin(), response.robustness_levels().end()}), + kRobustnessLevels); +} + +void MediaKeysCapabilitiesModuleServiceTests::sendGetSupportedRobustnessLevelsRequestAndExpectFailure() +{ + firebolt::rialto::GetSupportedRobustnessLevelsRequest request; + firebolt::rialto::GetSupportedRobustnessLevelsResponse response; + + request.set_key_system(keySystems[0]); + + m_service->getSupportedRobustnessLevels(m_controllerMock.get(), &request, &response, m_closureMock.get()); + EXPECT_EQ(response.robustness_levels_size(), 0); +} + void MediaKeysCapabilitiesModuleServiceTests::expectRequestSuccess() { EXPECT_CALL(*m_closureMock, Run()); diff --git a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.h index 8f133e0c6..9893b2255 100644 --- a/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaKeysCapabilitiesModuleService/MediaKeysCapabilitiesModuleServiceTestsFixture.h @@ -45,6 +45,8 @@ class MediaKeysCapabilitiesModuleServiceTests : public testing::Test void cdmServiceWillGetSupportedKeySystemVersion(); void cdmServiceWillFailToGetSupportedKeySystemVersion(); void cdmServiceWillSupportServerCertificate(); + void cdmServiceWillGetSupportedRobustnessLevels(); + void cdmServiceWillFailToGetSupportedRobustnessLevels(); void sendClientConnected(); void sendGetSupportedKeySystemsRequestAndReceiveResponse(); @@ -52,6 +54,8 @@ class MediaKeysCapabilitiesModuleServiceTests : public testing::Test void sendGetSupportedKeySystemVersionRequestAndReceiveResponse(); void sendGetSupportedKeySystemVersionRequestAndExpectFailure(); void sendIsServerCertificateSupportedRequestAndReceiveResponse(); + void sendGetSupportedRobustnessLevelsRequestAndReceiveResponse(); + void sendGetSupportedRobustnessLevelsRequestAndExpectFailure(); private: std::shared_ptr> m_clientMock; diff --git a/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp index 44720cfae..e1b3dfd5b 100644 --- a/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaKeysModuleService/MediaKeysModuleServiceTestsFixture.cpp @@ -39,7 +39,6 @@ namespace const std::string keySystem{"expectedKeySystem"}; constexpr int kHardcodedMediaKeysHandle{2}; constexpr firebolt::rialto::KeySessionType kKeySessionType{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsLDL{false}; constexpr int kKeySessionId{3}; constexpr firebolt::rialto::MediaKeyErrorStatus kErrorStatus{firebolt::rialto::MediaKeyErrorStatus::FAIL}; constexpr firebolt::rialto::InitDataType kInitDataType{firebolt::rialto::InitDataType::CENC}; @@ -50,6 +49,7 @@ const std::vector kDrmHeader{6, 3, 8}; const std::vector kLicenseRequestMessage{3, 2, 1}; const std::vector kLicenseRenewalMessage{0, 4, 8}; const std::string kUrl{"http://"}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlState{firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED}; } // namespace MATCHER_P4(LicenseRequestEventMatcher, kKeySessionId, mediaKeysHandle, requestMessage, kUrl, "") @@ -142,29 +142,31 @@ void MediaKeysModuleServiceTests::cdmServiceWillCreateKeySession() { expectRequestSuccess(); EXPECT_CALL(*m_controllerMock, getClient()).WillOnce(Return(m_clientMock)); - EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, kIsLDL, _)) - .WillOnce(DoAll(SetArgReferee<4>(kKeySessionId), Return(firebolt::rialto::MediaKeyErrorStatus::OK))); + EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<3>(kKeySessionId), Return(firebolt::rialto::MediaKeyErrorStatus::OK))); } void MediaKeysModuleServiceTests::cdmServiceWillFailToCreateKeySession() { expectRequestSuccess(); EXPECT_CALL(*m_controllerMock, getClient()).WillOnce(Return(m_clientMock)); - EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, kIsLDL, _)) + EXPECT_CALL(m_cdmServiceMock, createKeySession(kHardcodedMediaKeysHandle, kKeySessionType, _, _)) .WillOnce(Return(kErrorStatus)); } void MediaKeysModuleServiceTests::cdmServiceWillGenerateRequest() { expectRequestSuccess(); - EXPECT_CALL(m_cdmServiceMock, generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)) + EXPECT_CALL(m_cdmServiceMock, + generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)) .WillOnce(Return(firebolt::rialto::MediaKeyErrorStatus::OK)); } void MediaKeysModuleServiceTests::cdmServiceWillFailToGenerateRequest() { expectRequestSuccess(); - EXPECT_CALL(m_cdmServiceMock, generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)) + EXPECT_CALL(m_cdmServiceMock, + generateRequest(kHardcodedMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)) .WillOnce(Return(kErrorStatus)); } @@ -483,7 +485,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestAndReceiveResponse( request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_controllerMock.get(), &request, &response, m_closureMock.get()); EXPECT_GE(response.key_session_id(), -1); @@ -497,7 +498,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestAndReceiveErrorResp request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_controllerMock.get(), &request, &response, m_closureMock.get()); EXPECT_GE(response.key_session_id(), -1); @@ -511,7 +511,6 @@ void MediaKeysModuleServiceTests::sendCreateKeySessionRequestWithInvalidIpcAndRe request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_session_type(convertKeySessionType(kKeySessionType)); - request.set_is_ldl(kIsLDL); m_service->createKeySession(m_invalidControllerMock.get(), &request, &response, m_closureMock.get()); } @@ -524,6 +523,7 @@ void MediaKeysModuleServiceTests::sendGenerateRequestRequestAndReceiveResponse() request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_key_session_id(kKeySessionId); request.set_init_data_type(convertInitDataType(kInitDataType)); + request.set_ldl_state(convertLimitedDurationLicense(kLdlState)); for (auto it = kInitData.begin(); it != kInitData.end(); it++) { @@ -542,6 +542,7 @@ void MediaKeysModuleServiceTests::sendGenerateRequestRequestAndReceiveErrorRespo request.set_media_keys_handle(kHardcodedMediaKeysHandle); request.set_key_session_id(kKeySessionId); request.set_init_data_type(convertInitDataType(kInitDataType)); + request.set_ldl_state(convertLimitedDurationLicense(kLdlState)); for (auto it = kInitData.begin(); it != kInitData.end(); it++) { diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp index dcad505bb..4ff1da748 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp @@ -249,6 +249,22 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetPosition) sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); } +TEST_F(MediaPipelineModuleServiceTests, shouldGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillGetDuration(); + sendGetDurationRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToGetDuration(); + sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldSetImmediateOutput) { mediaPipelineServiceWillCreateSession(); @@ -281,6 +297,38 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetImmediateOutput) sendGetImmediateOutputRequestAndReceiveFail(); } +TEST_F(MediaPipelineModuleServiceTests, shouldSetReportDecodeErrors) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillSetReportDecodeErrors(); + sendSetReportDecodeErrorsRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToSetReportDecodeErrors) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToSetReportDecodeErrors(); + sendSetReportDecodeErrorsRequestAndReceiveFail(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldGetQueuedFrames) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillGetQueuedFrames(); + sendGetQueuedFramesRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetQueuedFrames) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToGetQueuedFrames(); + sendGetQueuedFramesRequestAndReceiveFail(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldGetStats) { mediaPipelineServiceWillCreateSession(); @@ -345,6 +393,14 @@ TEST_F(MediaPipelineModuleServiceTests, shouldSendPlaybackErrorEvent) sendPlaybackErrorEvent(); } +TEST_F(MediaPipelineModuleServiceTests, shouldSendFirstFrameReceivedEvent) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaClientWillSendFirstFrameReceivedEvent(); + sendFirstFrameReceivedEvent(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldSendSourceFlushedEvent) { mediaPipelineServiceWillCreateSession(); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index ac3576a74..137d3341d 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp @@ -86,6 +86,8 @@ constexpr uint64_t kDroppedFrames{321}; constexpr uint32_t kDuration{30}; constexpr bool kImmediateOutputVal1{false}; constexpr bool kImmediateOutputVal2{true}; +constexpr bool kReportDecodeErrorsVal{false}; +constexpr uint32_t kQueuedFramesVal{123}; constexpr int64_t kDiscontinuityGap{1}; constexpr bool kIsAudioAac{false}; constexpr uint32_t kBufferingLimit{12341}; @@ -93,6 +95,7 @@ constexpr bool kUseBuffering{true}; constexpr uint64_t kStopPosition{2423}; constexpr bool kFramed{true}; constexpr bool kIsVideoMaster{true}; +constexpr bool kIsLive{true}; } // namespace MATCHER_P(AttachedSourceMatcher, source, "") @@ -203,6 +206,13 @@ MATCHER_P2(PlaybackErrorEventMatcher, kExpectedSourceId, kExpectedPlaybackError, return ((kExpectedSourceId == event->source_id()) && (kExpectedPlaybackError == event->error())); } +MATCHER_P(FirstFrameReceivedEventMatcher, kSourceId, "") +{ + std::shared_ptr event = + std::dynamic_pointer_cast(arg); + return (kSourceId == event->source_id()); +} + MATCHER_P(PlaybackStateChangeEventMatcher, kPlaybackState, "") { std::shared_ptr event = @@ -284,13 +294,15 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToDestroySessi void MediaPipelineModuleServiceTests::mediaPipelineServiceWillLoadSession() { expectRequestSuccess(); - EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl, kIsLive)) + .WillOnce(Return(true)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToLoadSession() { expectRequestFailure(); - EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineServiceMock, load(kHardcodedSessionId, kMediaType, kMimeType, kUrl, kIsLive)) + .WillOnce(Return(false)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillAttachSource() @@ -388,13 +400,13 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailAllSourcesAtta void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPlay() { expectRequestSuccess(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(true)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToPlay() { expectRequestFailure(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(false)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPause() @@ -491,6 +503,24 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetPosition( EXPECT_CALL(m_mediaPipelineServiceMock, getPosition(kHardcodedSessionId, _)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetDuration() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)) + .WillOnce(Invoke( + [&](int, std::int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetDuration() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)).WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetImmediateOutput() { expectRequestSuccess(); @@ -518,6 +548,33 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetImmediate EXPECT_CALL(m_mediaPipelineServiceMock, getImmediateOutput(kHardcodedSessionId, _, _)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetReportDecodeErrors() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, setReportDecodeErrors(kHardcodedSessionId, _, kReportDecodeErrorsVal)) + .WillOnce(Return(true)); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToSetReportDecodeErrors() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, setReportDecodeErrors(kHardcodedSessionId, _, kReportDecodeErrorsVal)) + .WillOnce(Return(false)); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetQueuedFrames() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, getQueuedFrames(kHardcodedSessionId, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kQueuedFramesVal), Return(true))); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetQueuedFrames() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, getQueuedFrames(kHardcodedSessionId, _, _)).WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetStats() { expectRequestSuccess(); @@ -852,6 +909,11 @@ void MediaPipelineModuleServiceTests::mediaClientWillSendPlaybackErrorEvent() EXPECT_CALL(*m_clientMock, sendEvent(PlaybackErrorEventMatcher(kSourceId, convertPlaybackError(kPlaybackError)))); } +void MediaPipelineModuleServiceTests::mediaClientWillSendFirstFrameReceivedEvent() +{ + EXPECT_CALL(*m_clientMock, sendEvent(FirstFrameReceivedEventMatcher(kSourceId))); +} + void MediaPipelineModuleServiceTests::mediaClientWillSendSourceFlushedEvent() { EXPECT_CALL(*m_clientMock, sendEvent(SourceFlushedEventMatcher(kSourceId))); @@ -919,6 +981,7 @@ void MediaPipelineModuleServiceTests::sendLoadRequestAndReceiveResponse() request.set_type(convertMediaType(kMediaType)); request.set_mime_type(kMimeType); request.set_url(kUrl); + request.set_is_live(kIsLive); m_service->load(m_controllerMock.get(), &request, &response, m_closureMock.get()); } @@ -1104,6 +1167,28 @@ void MediaPipelineModuleServiceTests::sendGetPositionRequestAndReceiveResponseWi m_service->getPosition(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponse() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); + + EXPECT_EQ(response.duration(), kDuration); +} + +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponseWithoutDurationMatch() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendSetImmediateOutputRequestAndReceiveResponse() { firebolt::rialto::SetImmediateOutputRequest request; @@ -1148,6 +1233,50 @@ void MediaPipelineModuleServiceTests::sendGetImmediateOutputRequestAndReceiveFai m_service->getImmediateOutput(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendSetReportDecodeErrorsRequestAndReceiveResponse() +{ + firebolt::rialto::SetReportDecodeErrorsRequest request; + firebolt::rialto::SetReportDecodeErrorsResponse response; + + request.set_session_id(kHardcodedSessionId); + request.set_report_decode_errors(kReportDecodeErrorsVal); + + m_service->setReportDecodeErrors(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + +void MediaPipelineModuleServiceTests::sendSetReportDecodeErrorsRequestAndReceiveFail() +{ + firebolt::rialto::SetReportDecodeErrorsRequest request; + firebolt::rialto::SetReportDecodeErrorsResponse response; + + request.set_session_id(kHardcodedSessionId); + request.set_report_decode_errors(kReportDecodeErrorsVal); + + m_service->setReportDecodeErrors(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + +void MediaPipelineModuleServiceTests::sendGetQueuedFramesRequestAndReceiveResponse() +{ + firebolt::rialto::GetQueuedFramesRequest request; + firebolt::rialto::GetQueuedFramesResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getQueuedFrames(m_controllerMock.get(), &request, &response, m_closureMock.get()); + + EXPECT_EQ(response.queued_frames(), kQueuedFramesVal); +} + +void MediaPipelineModuleServiceTests::sendGetQueuedFramesRequestAndReceiveFail() +{ + firebolt::rialto::GetQueuedFramesRequest request; + firebolt::rialto::GetQueuedFramesResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getQueuedFrames(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendGetStatsRequestAndReceiveResponse() { firebolt::rialto::GetStatsRequest request; @@ -1547,6 +1676,12 @@ void MediaPipelineModuleServiceTests::sendPlaybackErrorEvent() m_mediaPipelineClient->notifyPlaybackError(kSourceId, kPlaybackError); } +void MediaPipelineModuleServiceTests::sendFirstFrameReceivedEvent() +{ + ASSERT_TRUE(m_mediaPipelineClient); + m_mediaPipelineClient->notifyFirstFrameReceived(kSourceId); +} + void MediaPipelineModuleServiceTests::sendSourceFlushedEvent() { ASSERT_TRUE(m_mediaPipelineClient); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h index 90f02fb4a..7df32bedf 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h @@ -74,10 +74,16 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaPipelineServiceWillFailToSetPlaybackRate(); void mediaPipelineServiceWillGetPosition(); void mediaPipelineServiceWillFailToGetPosition(); + void mediaPipelineServiceWillGetDuration(); + void mediaPipelineServiceWillFailToGetDuration(); void mediaPipelineServiceWillSetImmediateOutput(); void mediaPipelineServiceWillFailToSetImmediateOutput(); void mediaPipelineServiceWillGetImmediateOutput(); void mediaPipelineServiceWillFailToGetImmediateOutput(); + void mediaPipelineServiceWillSetReportDecodeErrors(); + void mediaPipelineServiceWillFailToSetReportDecodeErrors(); + void mediaPipelineServiceWillGetQueuedFrames(); + void mediaPipelineServiceWillFailToGetQueuedFrames(); void mediaPipelineServiceWillGetStats(); void mediaPipelineServiceWillFailToGetStats(); void mediaPipelineServiceWillRenderFrame(); @@ -127,6 +133,7 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaClientWillSendPostionChangeEvent(); void mediaClientWillSendQosEvent(); void mediaClientWillSendPlaybackErrorEvent(); + void mediaClientWillSendFirstFrameReceivedEvent(); void mediaClientWillSendSourceFlushedEvent(); void mediaClientWillSendPlaybackInfoEvent(); @@ -150,10 +157,16 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendSetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); + void sendGetDurationRequestAndReceiveResponse(); + void sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); void sendSetImmediateOutputRequestAndReceiveResponse(); void sendSetImmediateOutputRequestAndReceiveFail(); void sendGetImmediateOutputRequestAndReceiveResponse(); void sendGetImmediateOutputRequestAndReceiveFail(); + void sendSetReportDecodeErrorsRequestAndReceiveResponse(); + void sendSetReportDecodeErrorsRequestAndReceiveFail(); + void sendGetQueuedFramesRequestAndReceiveResponse(); + void sendGetQueuedFramesRequestAndReceiveFail(); void sendGetStatsRequestAndReceiveResponse(); void sendGetStatsRequestAndReceiveResponseWithoutStatsMatch(); void sendHaveDataRequestAndReceiveResponse(); @@ -192,6 +205,7 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendPostionChangeEvent(); void sendQosEvent(); void sendPlaybackErrorEvent(); + void sendFirstFrameReceivedEvent(); void sendSourceFlushedEvent(); void sendPlaybackInfoEvent(); void sendRenderFrameRequestAndReceiveResponse(); diff --git a/tests/unittests/media/server/main/CMakeLists.txt b/tests/unittests/media/server/main/CMakeLists.txt index 59d2ded8e..646d2ff4d 100644 --- a/tests/unittests/media/server/main/CMakeLists.txt +++ b/tests/unittests/media/server/main/CMakeLists.txt @@ -68,7 +68,6 @@ add_gtests ( mediaKeys/GetDrmTimeTest.cpp mediaKeys/GetLastDrmErrorTest.cpp mediaKeys/SelectKeyIdTest.cpp - mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp mediaKeys/PingTest.cpp mediaKeys/ReleaseKeySessionTest.cpp mediaKeys/GetMetricSystemDataTest.cpp @@ -76,6 +75,7 @@ add_gtests ( mediaKeysCapabilities/CreateTest.cpp mediaKeysCapabilities/KeySystemsTest.cpp mediaKeysCapabilities/CertificateTest.cpp + mediaKeysCapabilities/RobustnessTest.cpp mediaKeySession/base/MediaKeySessionTestBase.cpp mediaKeySession/CreateTest.cpp @@ -91,11 +91,12 @@ add_gtests ( mediaKeySession/SetDrmHeaderTest.cpp mediaKeySession/GetLastDrmErrorTest.cpp mediaKeySession/SelectKeyIdTest.cpp - mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp sharedMemoryBuffer/SharedMemoryBufferTestsFixture.cpp sharedMemoryBuffer/SharedMemoryBufferTests.cpp + needDataDelayCalculator/NeedDataDelayCalculatorTest.cpp + needMediaData/NeedMediaDataTestsFixture.cpp needMediaData/NeedMediaDataTests.cpp diff --git a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp index 171480c5c..268cc6387 100644 --- a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp +++ b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp @@ -20,6 +20,12 @@ #include "ActiveRequests.h" #include +namespace +{ +constexpr std::uint32_t kMaxFrames{10}; +constexpr std::uint32_t kDefaultMaxFrames{24}; +} // namespace + class ActiveRequestsTests : public testing::Test { protected: @@ -45,7 +51,8 @@ TEST_F(ActiveRequestsTests, addSegmentShouldReturnErrorForInvalidData) { std::unique_ptr segment = std::make_unique(); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(m_sut.addSegment(0, segment), firebolt::rialto::AddSegmentStatus::ERROR); } @@ -60,7 +67,7 @@ TEST_F(ActiveRequestsTests, addSegmentShouldReturnErrorForInvalidId) TEST_F(ActiveRequestsTests, addSegmentsOverLimitShouldReturnNoSpace) { - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 5)); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 5, kMaxFrames)); std::vector data{'T', 'E', 'S', 'T'}; std::unique_ptr segment = std::make_unique(); @@ -76,23 +83,32 @@ TEST_F(ActiveRequestsTests, addSegmentsOverLimitShouldReturnNoSpace) TEST_F(ActiveRequestsTests, shouldGenerateGetAndEraseIds) { EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(0)); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); + EXPECT_EQ(kDefaultMaxFrames, m_sut.getMaxFrames(0)); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + EXPECT_EQ(kMaxFrames, m_sut.getMaxFrames(0)); m_sut.erase(0); EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(0)); EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(1)); - EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); + EXPECT_EQ(kDefaultMaxFrames, m_sut.getMaxFrames(1)); + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); + EXPECT_EQ(kMaxFrames, m_sut.getMaxFrames(1)); m_sut.erase(1); EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(1)); + EXPECT_EQ(kDefaultMaxFrames, m_sut.getMaxFrames(1)); } TEST_F(ActiveRequestsTests, shouldClearIds) { EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(0)); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); - EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); m_sut.clear(); @@ -103,10 +119,14 @@ TEST_F(ActiveRequestsTests, shouldClearIds) TEST_F(ActiveRequestsTests, shouldEraseAudioIds) { EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(0)); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); - EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); - EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); - EXPECT_EQ(3, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(3, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); @@ -121,10 +141,14 @@ TEST_F(ActiveRequestsTests, shouldEraseAudioIds) TEST_F(ActiveRequestsTests, shouldEraseVideoIds) { EXPECT_EQ(firebolt::rialto::MediaSourceType::UNKNOWN, m_sut.getType(0)); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); - EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); - EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); - EXPECT_EQ(3, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); + EXPECT_EQ(3, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(2)); @@ -142,20 +166,45 @@ TEST_F(ActiveRequestsTests, shouldAddAndGetSegments) std::unique_ptr segment = std::make_unique(); segment->setData(data.size(), data.data()); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(m_sut.addSegment(0, segment), firebolt::rialto::AddSegmentStatus::OK); const firebolt::rialto::IMediaPipeline::MediaSegmentVector &kSegments = m_sut.getSegments(0); ASSERT_EQ(1, kSegments.size()); EXPECT_EQ(kSegments[0]->getType(), firebolt::rialto::MediaSourceType::AUDIO); } +TEST_F(ActiveRequestsTests, insertShouldHandleDuplicateId) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100, kMaxFrames)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100, kMaxFrames)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100, kMaxFrames)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + +TEST_F(ActiveRequestsTests, insertShouldResolveDuplicateIdCollision) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100, kMaxFrames)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100, kMaxFrames); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100, kMaxFrames)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + TEST_F(ActiveRequestsTests, shouldAddAndRemoveSegments) { std::vector data{'T', 'E', 'S', 'T'}; std::unique_ptr segment = std::make_unique(); segment->setData(data.size(), data.data()); - EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max())); + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, std::numeric_limits::max(), + kMaxFrames)); EXPECT_EQ(m_sut.addSegment(0, segment), firebolt::rialto::AddSegmentStatus::OK); m_sut.clear(); EXPECT_THROW(m_sut.getSegments(0), std::runtime_error); diff --git a/tests/unittests/media/server/main/dataReader/DataReaderFactoryTests.cpp b/tests/unittests/media/server/main/dataReader/DataReaderFactoryTests.cpp index 9ff930fa2..b25abf4d5 100644 --- a/tests/unittests/media/server/main/dataReader/DataReaderFactoryTests.cpp +++ b/tests/unittests/media/server/main/dataReader/DataReaderFactoryTests.cpp @@ -32,9 +32,10 @@ TEST_F(DataReaderFactoryTests, shouldFailToCreateDataReaderForUnknownVersion) { constexpr auto kMediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; constexpr std::uint32_t kNumFrames{1}; + constexpr bool kIsBufferFull{true}; std::uint32_t version{23}; std::uint8_t *data{reinterpret_cast(&version)}; - auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames); + auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames, kIsBufferFull); ASSERT_EQ(nullptr, reader); } @@ -42,9 +43,10 @@ TEST_F(DataReaderFactoryTests, shouldCreateDataReaderV1) { constexpr auto kMediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; constexpr std::uint32_t kNumFrames{1}; + constexpr bool kIsBufferFull{true}; std::uint32_t version{1}; std::uint8_t *data{reinterpret_cast(&version)}; - auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames); + auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames, kIsBufferFull); ASSERT_NE(nullptr, reader); const firebolt::rialto::server::DataReaderV1 *v1Reader = dynamic_cast(reader.get()); @@ -55,9 +57,10 @@ TEST_F(DataReaderFactoryTests, shouldCreateDataReaderV2) { constexpr auto kMediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; constexpr std::uint32_t kNumFrames{1}; + constexpr bool kIsBufferFull{true}; std::uint32_t version{2}; std::uint8_t *data{reinterpret_cast(&version)}; - auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames); + auto reader = m_sut.createDataReader(kMediaSourceType, data, 0, kNumFrames, kIsBufferFull); ASSERT_NE(nullptr, reader); const firebolt::rialto::server::DataReaderV2 *v2Reader = dynamic_cast(reader.get()); diff --git a/tests/unittests/media/server/main/dataReader/DataReaderV1Tests.cpp b/tests/unittests/media/server/main/dataReader/DataReaderV1Tests.cpp index 931161a53..8e0d7b52d 100644 --- a/tests/unittests/media/server/main/dataReader/DataReaderV1Tests.cpp +++ b/tests/unittests/media/server/main/dataReader/DataReaderV1Tests.cpp @@ -56,6 +56,7 @@ const std::unique_ptr kVideoSegment{ const std::unique_ptr kAudioSegment{ std::make_unique(kAudioSourceId, kTimeStamp, kDuration, kSampleRate, kNumberOfChannels)}; +constexpr bool kIsBufferFull{true}; } // namespace class DataReaderV1Tests : public testing::Test @@ -98,8 +99,9 @@ class DataReaderV1Tests : public testing::Test { ASSERT_TRUE(m_shm); std::uint8_t *buffer = m_shm->getBuffer(); - m_sut = std::make_unique(kVideoMediaSourceType, buffer, 4, kNumFrames); + m_sut = std::make_unique(kVideoMediaSourceType, buffer, 4, kNumFrames, kIsBufferFull); auto result = m_sut->readData(); + EXPECT_EQ(kIsBufferFull, m_sut->isBufferFull()); ASSERT_EQ(1, result.size()); IMediaPipeline::MediaSegmentVideo *resultSegment = dynamic_cast(result.front().get()); @@ -140,9 +142,11 @@ class DataReaderV1Tests : public testing::Test std::uint8_t *buffer = m_shm->getBuffer(); std::uint32_t regionOffset = m_shm->getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, kSessionId, kAudioMediaSourceType); - m_sut = std::make_unique(kAudioMediaSourceType, buffer, regionOffset + 4, kNumFrames); + m_sut = + std::make_unique(kAudioMediaSourceType, buffer, regionOffset + 4, kNumFrames, kIsBufferFull); auto result = m_sut->readData(); ASSERT_EQ(1, result.size()); + EXPECT_EQ(kIsBufferFull, m_sut->isBufferFull()); IMediaPipeline::MediaSegmentAudio *resultSegment = dynamic_cast(result.front().get()); ASSERT_NE(nullptr, resultSegment); diff --git a/tests/unittests/media/server/main/dataReader/DataReaderV2Tests.cpp b/tests/unittests/media/server/main/dataReader/DataReaderV2Tests.cpp index c2824b425..fac217e9a 100644 --- a/tests/unittests/media/server/main/dataReader/DataReaderV2Tests.cpp +++ b/tests/unittests/media/server/main/dataReader/DataReaderV2Tests.cpp @@ -65,6 +65,7 @@ constexpr SegmentAlignment kSegmentAlignment{SegmentAlignment::AU}; constexpr uint32_t kCryptBlocks{131}; constexpr uint32_t kSkipBlocks{242}; constexpr uint64_t kDisplayOffset{35}; +constexpr bool kIsBufferFull{true}; class Check { @@ -296,7 +297,8 @@ class DataReaderV2Tests : public testing::Test std::unique_ptr readData(const firebolt::rialto::MediaSourceType &sourceType) { - m_sut = std::make_unique(sourceType, m_shm, kMetaDataSize, kNumFrames); + m_sut = std::make_unique(sourceType, m_shm, kMetaDataSize, kNumFrames, kIsBufferFull); + EXPECT_EQ(m_sut->isBufferFull(), kIsBufferFull); auto result = m_sut->readData(); if (result.size() != 1) return nullptr; diff --git a/tests/unittests/media/server/main/main.cpp b/tests/unittests/media/server/main/main.cpp index 5c3249a66..3857bb482 100644 --- a/tests/unittests/media/server/main/main.cpp +++ b/tests/unittests/media/server/main/main.cpp @@ -17,6 +17,8 @@ * limitations under the License. */ +#include "GlibWrapperFactoryMock.h" +#include "GlibWrapperMock.h" #include "GstWrapperFactoryMock.h" #include "GstWrapperMock.h" #include "IFactoryAccessor.h" @@ -36,13 +38,22 @@ void initialiseGstreamer() EXPECT_CALL(*gstWrapperFactoryMock, getGstWrapper()).WillOnce(Return(gstWrapperMock)); IFactoryAccessor::instance().gstWrapperFactory() = gstWrapperFactoryMock; + std::shared_ptr> glibWrapperFactoryMock{ + std::make_shared>()}; + std::shared_ptr> glibWrapperMock{std::make_shared>()}; + EXPECT_CALL(*glibWrapperFactoryMock, getGlibWrapper()).WillOnce(Return(glibWrapperMock)); + IFactoryAccessor::instance().glibWrapperFactory() = glibWrapperFactoryMock; + EXPECT_CALL(*gstWrapperMock, gstInit(nullptr, nullptr)); EXPECT_CALL(*gstWrapperMock, gstRegistryGet()).WillOnce(Return(nullptr)); EXPECT_CALL(*gstWrapperMock, gstRegistryFindPlugin(nullptr, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(*glibWrapperMock, gThreadPoolSetMaxUnusedThreads(2)); + EXPECT_CALL(*glibWrapperMock, gThreadPoolSetMaxIdleTime(5 * 1000)); firebolt::rialto::server::IGstInitialiser::instance().initialise(nullptr, nullptr); firebolt::rialto::server::IGstInitialiser::instance().waitForInitialisation(); IFactoryAccessor::instance().gstWrapperFactory() = nullptr; + IFactoryAccessor::instance().glibWrapperFactory() = nullptr; } int main(int argc, char **argv) // NOLINT(build/filename_format) diff --git a/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp index 948ce3c90..03a4f2d2b 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CallbacksTest.cpp @@ -60,7 +60,7 @@ TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeNoGenerateReque /** * Test that onProcessChallenge after a generateRequest for none Netflix key system notifies licenseRequest. */ -TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeGenerateRequestNoneNetflix) +TEST_F(RialtoServerMediaKeySessionCallbacksTest, ProcessChallengeGenerateRequest) { generateRequest(); mainThreadWillEnqueueTask(); diff --git a/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp index 8552b6741..569ee760e 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CloseKeySessionTest.cpp @@ -31,11 +31,14 @@ class RialtoServerMediaKeySessionCloseKeySessionTest : public MediaKeySessionTes TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** @@ -56,11 +59,14 @@ TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, SuccessNoneNetflix) TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCancelChallengeDataFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::INVALID_STATE)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** @@ -69,11 +75,14 @@ TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCancelChalleng TEST_F(RialtoServerMediaKeySessionCloseKeySessionTest, OcdmSessionCleanDecryptContextFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, cancelChallengeData()).WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_CALL(*m_ocdmSessionMock, cleanDecryptContext()).WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->closeKeySession()); + + EXPECT_CALL(*m_ocdmSessionMock, destructSession()).WillOnce(Return(MediaKeyErrorStatus::OK)); } /** diff --git a/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp b/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp index 0e70388a2..a0b6c8da8 100644 --- a/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/CreateTest.cpp @@ -34,8 +34,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, Create) EXPECT_NO_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock)); + m_mediaKeysClientMock, m_mainThreadFactoryMock)); EXPECT_NE(m_mediaKeySession, nullptr); destroyKeySession(); @@ -52,7 +51,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, FactoryCreatesObject) EXPECT_CALL(*m_ocdmSystemMock, createSession(_)).WillOnce(Return(ByMove(std::move(m_ocdmSession)))); EXPECT_NE(factory->createMediaKeySession(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL), + m_mediaKeysClientMock), nullptr); } @@ -66,8 +65,7 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, CreateMainThreadFailure) EXPECT_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock), + m_mediaKeysClientMock, m_mainThreadFactoryMock), std::runtime_error); } @@ -83,7 +81,6 @@ TEST_F(RialtoServerCreateMediaKeySessionTest, CreateOcdmSessionFailure) EXPECT_THROW(m_mediaKeySession = std::make_unique(kNetflixKeySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, - m_mediaKeysClientMock, m_isLDL, - m_mainThreadFactoryMock), + m_mediaKeysClientMock, m_mainThreadFactoryMock), std::runtime_error); } diff --git a/tests/unittests/media/server/main/mediaKeySession/DecryptBufferTest.cpp b/tests/unittests/media/server/main/mediaKeySession/DecryptBufferTest.cpp index 00597dc46..55c8181b3 100644 --- a/tests/unittests/media/server/main/mediaKeySession/DecryptBufferTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/DecryptBufferTest.cpp @@ -19,6 +19,9 @@ #include "MediaKeySessionTestBase.h" +using testing::DoAll; +using testing::SetArgReferee; + class RialtoServerMediaKeySessionDecryptBufferTest : public MediaKeySessionTestBase { protected: @@ -48,8 +51,72 @@ TEST_F(RialtoServerMediaKeySessionDecryptBufferTest, OcdmSessionFailure) createKeySession(kWidevineKeySystem); EXPECT_CALL(*m_ocdmSessionMock, decryptBuffer(&m_encrypted, &m_caps)).WillOnce(Return(MediaKeyErrorStatus::FAIL)); + EXPECT_CALL(*m_ocdmSessionMock, getLastDrmError(_)).WillOnce(Return(MediaKeyErrorStatus::OK)); + + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); +} + +/** + * Test that repeated decryption failures keep returning failure. + */ +TEST_F(RialtoServerMediaKeySessionDecryptBufferTest, RepeatedOcdmSessionFailure) +{ + createKeySession(kWidevineKeySystem); + + EXPECT_CALL(*m_ocdmSessionMock, decryptBuffer(&m_encrypted, &m_caps)) + .Times(2) + .WillRepeatedly(Return(MediaKeyErrorStatus::FAIL)); + EXPECT_CALL(*m_ocdmSessionMock, getLastDrmError(_)).Times(2).WillRepeatedly(Return(MediaKeyErrorStatus::OK)); + + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); +} + +/** + * Test that successful decryption resets the repeated failure handling. + */ +TEST_F(RialtoServerMediaKeySessionDecryptBufferTest, OcdmSessionFailureAfterSuccess) +{ + createKeySession(kWidevineKeySystem); + + EXPECT_CALL(*m_ocdmSessionMock, decryptBuffer(&m_encrypted, &m_caps)) + .WillOnce(Return(MediaKeyErrorStatus::FAIL)) + .WillOnce(Return(MediaKeyErrorStatus::OK)) + .WillOnce(Return(MediaKeyErrorStatus::FAIL)); + EXPECT_CALL(*m_ocdmSessionMock, getLastDrmError(_)).Times(2).WillRepeatedly(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); +} + +/** + * Test that an HDCP output protection DRM error is translated to OUTPUT_RESTRICTED. + */ +TEST_F(RialtoServerMediaKeySessionDecryptBufferTest, OcdmSessionOutputRestricted) +{ + constexpr uint32_t kHdcpOutputProtectionFailure{4427}; + + createKeySession(kWidevineKeySystem); + + EXPECT_CALL(*m_ocdmSessionMock, decryptBuffer(&m_encrypted, &m_caps)).WillOnce(Return(MediaKeyErrorStatus::FAIL)); + EXPECT_CALL(*m_ocdmSessionMock, getLastDrmError(_)) + .WillOnce(DoAll(SetArgReferee<0>(kHdcpOutputProtectionFailure), Return(MediaKeyErrorStatus::OK))); + + EXPECT_EQ(MediaKeyErrorStatus::OUTPUT_RESTRICTED, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); +} + +/** + * Test that OUTPUT_RESTRICTED is propagated when returned directly by decryptBuffer. + */ +TEST_F(RialtoServerMediaKeySessionDecryptBufferTest, OcdmSessionDirectOutputRestricted) +{ + createKeySession(kWidevineKeySystem); + + EXPECT_CALL(*m_ocdmSessionMock, decryptBuffer(&m_encrypted, &m_caps)) + .WillOnce(Return(MediaKeyErrorStatus::OUTPUT_RESTRICTED)); + + EXPECT_EQ(MediaKeyErrorStatus::OUTPUT_RESTRICTED, m_mediaKeySession->decrypt(&m_encrypted, &m_caps)); } /** diff --git a/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp b/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp index 2e0cf179d..d967ac7de 100644 --- a/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/GenerateRequestTest.cpp @@ -22,28 +22,9 @@ using ::testing::DoAll; using ::testing::SetArgPointee; -MATCHER(nullptrMatcher, "") -{ - return arg == nullptr; -} - -MATCHER(notNullptrMatcher, "") -{ - return arg != nullptr; -} - -ACTION_P(memcpyChallenge, vec) -{ - memcpy(arg1, &vec[0], vec.size()); -} - class RialtoServerMediaKeySessionGenerateRequestTest : public MediaKeySessionTestBase { protected: - const InitDataType m_kInitDataType = InitDataType::CENC; - const std::vector m_kInitData{1, 2, 3}; - const std::vector m_kChallenge{'d', 'e', 'f'}; - ~RialtoServerMediaKeySessionGenerateRequestTest() { destroyKeySession(); } }; @@ -58,7 +39,7 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNoneNetflix) constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // Close ocdm before destroying expectCloseKeySession(kWidevineKeySystem); @@ -71,18 +52,20 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); - EXPECT_CALL(*m_ocdmSessionMock, - constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) - .WillOnce(Return(MediaKeyErrorStatus::OK)); - mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) - .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); - EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) - .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); - mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + generateRequestPlayready(); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); +} + +/** + * Test that GenerateRequest can generate request successfully for a netflix keysystem. + */ +TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SuccessNetflixWithTwoGenerateChallengeCalls) +{ + createKeySession(kNetflixKeySystem); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + generateRequestPlayreadyWithTwoCalls(); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -102,7 +85,9 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, FailNetflixWhenChallengeD EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -124,7 +109,9 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, FailNetflixWhenGettingCha EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::FAIL))); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); // Close ocdm before destroying expectCloseKeySession(kNetflixKeySystem); @@ -140,10 +127,10 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, SessionAlreadyConstructed EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // Generate request again should just return OK - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // OcdmSession will be closed on destruction expectCloseKeySession(kWidevineKeySystem); @@ -158,7 +145,8 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, OcdmSessionFailure) EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) .WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); - EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); } /** @@ -176,7 +164,7 @@ TEST_F(RialtoServerMediaKeySessionGenerateRequestTest, OnErrorFailure) return MediaKeyErrorStatus::OK; })); - EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData)); + EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, m_kLdlState)); // OcdmSession will be closed on destruction expectCloseKeySession(kWidevineKeySystem); diff --git a/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp b/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp deleted file mode 100644 index 989a4ea2a..000000000 --- a/tests/unittests/media/server/main/mediaKeySession/IsNetflixPlayreadyKeySystemTest.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * 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 "MediaKeySessionTestBase.h" - -class RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest : public MediaKeySessionTestBase -{ -protected: - ~RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest() { destroyKeySession(); } -}; - -/** - * Test that isNetflixPlayreadyKeySystem returns false for microsoft playready key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnFalseForMsPlayready) -{ - createKeySession(kPlayreadyKeySystem); - - EXPECT_FALSE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns true for netflix key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnTrueForNetflix) -{ - createKeySession(kNetflixKeySystem); - - EXPECT_TRUE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns false for widevine key system - */ -TEST_F(RialtoServerMediaKeySessionIsNetflixPlayreadyKeySystemTest, ReturnFalseForWidevine) -{ - createKeySession(kWidevineKeySystem); - - EXPECT_FALSE(m_mediaKeySession->isNetflixPlayreadyKeySystem()); -} diff --git a/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp b/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp index 18b027a32..7969dac84 100644 --- a/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/SetDrmHeaderTest.cpp @@ -19,6 +19,18 @@ #include "MediaKeySessionTestBase.h" +MATCHER_P(drmHeaderMatcher, header, "") +{ + for (size_t i = 0; i < header.size(); ++i) + { + if (arg[i] != header[i]) + { + return false; + } + } + return true; +} + class RialtoServerMediaKeySessionSetDrmHeaderTest : public MediaKeySessionTestBase { protected: @@ -33,10 +45,15 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, Success) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); + EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -46,10 +63,15 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OcdmSessionFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); + EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Return(MediaKeyErrorStatus::FAIL)); EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -57,7 +79,9 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OcdmSessionFailure) */ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OnErrorFailure) { - createKeySession(kWidevineKeySystem); + createKeySession(kNetflixKeySystem); + + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, setDrmHeader(&m_kDrmHeader[0], m_kDrmHeader.size())) .WillOnce(Invoke( @@ -68,4 +92,7 @@ TEST_F(RialtoServerMediaKeySessionSetDrmHeaderTest, OnErrorFailure) })); EXPECT_EQ(MediaKeyErrorStatus::FAIL, m_mediaKeySession->setDrmHeader(m_kDrmHeader)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } diff --git a/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp b/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp index d6ae82c0e..7c826a14f 100644 --- a/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/UpdateSessionTest.cpp @@ -33,11 +33,15 @@ class RialtoServerMediaKeySessionUpdateSessionTest : public MediaKeySessionTestB TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, SuccessNetflix) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, storeLicenseData(&m_kResponseData[0], m_kResponseData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->updateSession(m_kResponseData)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** @@ -59,11 +63,15 @@ TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, SuccessNoneNetflix) TEST_F(RialtoServerMediaKeySessionUpdateSessionTest, OcdmSessionStoreLicenseDataFailure) { createKeySession(kNetflixKeySystem); + generateRequestPlayready(); EXPECT_CALL(*m_ocdmSessionMock, storeLicenseData(&m_kResponseData[0], m_kResponseData.size())) .WillOnce(Return(MediaKeyErrorStatus::INVALID_STATE)); EXPECT_EQ(MediaKeyErrorStatus::INVALID_STATE, m_mediaKeySession->updateSession(m_kResponseData)); + + // Close ocdm before destroying + expectCloseKeySession(kNetflixKeySystem); } /** diff --git a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp index 05f9184cf..12eb74230 100644 --- a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp +++ b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.cpp @@ -42,7 +42,7 @@ void MediaKeySessionTestBase::createKeySession(const std::string &keySystem) EXPECT_NO_THROW(m_mediaKeySession = std::make_unique(keySystem, m_kKeySessionId, *m_ocdmSystemMock, m_keySessionType, m_mediaKeysClientMock, - m_isLDL, m_mainThreadFactoryMock)); + m_mainThreadFactoryMock)); EXPECT_NE(m_mediaKeySession, nullptr); } @@ -74,7 +74,47 @@ void MediaKeySessionTestBase::generateRequest() EXPECT_CALL(*m_ocdmSessionMock, constructSession(m_keySessionType, m_initDataType, &m_initData[0], m_initData.size())) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_initDataType, m_initData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeySession->generateRequest(m_initDataType, m_initData, m_kLdlState)); +} + +void MediaKeySessionTestBase::generateRequestPlayready() +{ + EXPECT_CALL(*m_ocdmSessionMock, + constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) + .WillOnce(Return(MediaKeyErrorStatus::OK)); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) + .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) + .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); +} + +void MediaKeySessionTestBase::generateRequestPlayreadyWithTwoCalls() +{ + EXPECT_CALL(*m_ocdmSessionMock, + constructSession(m_keySessionType, m_kInitDataType, &m_kInitData[0], m_kInitData.size())) + .WillOnce(Return(MediaKeyErrorStatus::OK)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED)); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, nullptrMatcher(), _)) + .WillOnce(DoAll(SetArgPointee<2>(m_kChallenge.size()), Return(MediaKeyErrorStatus::OK))); + EXPECT_CALL(*m_ocdmSessionMock, getChallengeData(m_isLDL, notNullptrMatcher(), _)) + .WillOnce(DoAll(memcpyChallenge(m_kChallenge), Return(MediaKeyErrorStatus::OK))); + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_mediaKeysClientMock, onLicenseRequest(m_kKeySessionId, m_kChallenge, _)); + + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeySession->generateRequest(m_kInitDataType, m_kInitData, + firebolt::rialto::LimitedDurationLicense::DISABLED)); } void MediaKeySessionTestBase::mainThreadWillEnqueueTask() diff --git a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h index b13514d76..5bf10e5c8 100644 --- a/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h +++ b/tests/unittests/media/server/main/mediaKeySession/base/MediaKeySessionTestBase.h @@ -30,6 +30,7 @@ #include #include #include +#include using namespace firebolt::rialto; using namespace firebolt::rialto::server; @@ -38,10 +39,27 @@ using namespace firebolt::rialto::server::mock; using ::testing::_; using ::testing::ByMove; +using ::testing::DoAll; using ::testing::Invoke; using ::testing::Return; +using ::testing::SetArgPointee; using ::testing::StrictMock; +MATCHER(nullptrMatcher, "") +{ + return arg == nullptr; +} + +MATCHER(notNullptrMatcher, "") +{ + return arg != nullptr; +} + +ACTION_P(memcpyChallenge, vec) +{ + memcpy(arg1, &vec[0], vec.size()); +} + class MediaKeySessionTestBase : public ::testing::Test { public: @@ -65,11 +83,17 @@ class MediaKeySessionTestBase : public ::testing::Test const int32_t m_kMainThreadClientId = {5}; KeySessionType m_keySessionType = KeySessionType::PERSISTENT_RELEASE_MESSAGE; bool m_isLDL = false; + const InitDataType m_kInitDataType{InitDataType::CENC}; + const std::vector m_kInitData{1, 2, 3}; + const std::vector m_kChallenge{'d', 'e', 'f'}; + const LimitedDurationLicense m_kLdlState{LimitedDurationLicense::NOT_SPECIFIED}; void createKeySession(const std::string &keySystem); void destroyKeySession(); void expectCloseKeySession(const std::string &keySystem); void generateRequest(); + void generateRequestPlayready(); + void generateRequestPlayreadyWithTwoCalls(); void mainThreadWillEnqueueTask(); }; diff --git a/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp b/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp index 40ea6c374..4ab26341c 100644 --- a/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp +++ b/tests/unittests/media/server/main/mediaKeys/CreateKeySessionTest.cpp @@ -34,12 +34,11 @@ TEST_F(RialtoServerMediaKeysCreateKeySessionTest, Success) int32_t returnKeySessionId = -1; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, - createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(std::move(m_mediaKeySession)))); EXPECT_EQ(MediaKeyErrorStatus::OK, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, returnKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionId)); EXPECT_GE(returnKeySessionId, -1); } @@ -51,10 +50,9 @@ TEST_F(RialtoServerMediaKeysCreateKeySessionTest, OcdmSystemFailure) int32_t returnKeySessionId = -1; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, - createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(kNetflixKeySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(nullptr))); EXPECT_EQ(MediaKeyErrorStatus::FAIL, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, returnKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, returnKeySessionId)); } diff --git a/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp b/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp index d69ac3cf8..50becd378 100644 --- a/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp +++ b/tests/unittests/media/server/main/mediaKeys/GenerateRequestTest.cpp @@ -24,6 +24,7 @@ class RialtoServerMediaKeysGenerateRequestTest : public MediaKeysTestBase protected: InitDataType m_initDataType = InitDataType::CENC; std::vector m_initData{1, 2, 3}; + LimitedDurationLicense m_ldlState{LimitedDurationLicense::NOT_SPECIFIED}; RialtoServerMediaKeysGenerateRequestTest() { @@ -39,10 +40,11 @@ class RialtoServerMediaKeysGenerateRequestTest : public MediaKeysTestBase TEST_F(RialtoServerMediaKeysGenerateRequestTest, Success) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData)) + EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData, m_ldlState)) .WillOnce(Return(MediaKeyErrorStatus::OK)); - EXPECT_EQ(MediaKeyErrorStatus::OK, m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData)); + EXPECT_EQ(MediaKeyErrorStatus::OK, + m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData, m_ldlState)); } /** @@ -52,7 +54,7 @@ TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionDoesNotExistFailure) { mainThreadWillEnqueueTaskAndWait(); EXPECT_EQ(MediaKeyErrorStatus::BAD_SESSION_ID, - m_mediaKeys->generateRequest(m_kKeySessionId + 1, m_initDataType, m_initData)); + m_mediaKeys->generateRequest(m_kKeySessionId + 1, m_initDataType, m_initData, m_ldlState)); } /** @@ -61,9 +63,9 @@ TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionDoesNotExistFailure) TEST_F(RialtoServerMediaKeysGenerateRequestTest, SessionFailure) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData)) + EXPECT_CALL(*m_mediaKeySessionMock, generateRequest(m_initDataType, m_initData, m_ldlState)) .WillOnce(Return(MediaKeyErrorStatus::NOT_SUPPORTED)); EXPECT_EQ(MediaKeyErrorStatus::NOT_SUPPORTED, - m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData)); + m_mediaKeys->generateRequest(m_kKeySessionId, m_initDataType, m_initData, m_ldlState)); } diff --git a/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp b/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp deleted file mode 100644 index f9d1fcbad..000000000 --- a/tests/unittests/media/server/main/mediaKeys/IsNetflixPlayreadyKeySystemTest.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2022 Sky UK - * - * 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 "MediaKeysTestBase.h" - -class RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest : public MediaKeysTestBase -{ -protected: - RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest() = default; - ~RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest() { destroyMediaKeys(); } -}; - -/** - * Test that isNetflixPlayreadyKeySystem returns true. - */ -TEST_F(RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest, ReturnTrue) -{ - createMediaKeys(kNetflixKeySystem); - EXPECT_TRUE(m_mediaKeys->isNetflixPlayreadyKeySystem()); -} - -/** - * Test that isNetflixPlayreadyKeySystem returns false - */ -TEST_F(RialtoServerMediaKeysIsNetflixPlayreadyKeySystemTest, ReturnFalse) -{ - createMediaKeys(kWidevineKeySystem); - EXPECT_FALSE(m_mediaKeys->isNetflixPlayreadyKeySystem()); -} diff --git a/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp b/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp index d6a9f91f7..d12682c95 100644 --- a/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp +++ b/tests/unittests/media/server/main/mediaKeys/base/MediaKeysTestBase.cpp @@ -61,11 +61,11 @@ void MediaKeysTestBase::destroyMediaKeys() void MediaKeysTestBase::createKeySession(std::string keySystem) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(keySystem, _, _, m_keySessionType, _, m_isLDL)) + EXPECT_CALL(*m_mediaKeySessionFactoryMock, createMediaKeySession(keySystem, _, _, m_keySessionType, _)) .WillOnce(Return(ByMove(std::move(m_mediaKeySession)))); EXPECT_EQ(MediaKeyErrorStatus::OK, - m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_isLDL, m_kKeySessionId)); + m_mediaKeys->createKeySession(m_keySessionType, m_mediaKeysClientMock, m_kKeySessionId)); } void MediaKeysTestBase::mainThreadWillEnqueueTaskAndWait() diff --git a/tests/unittests/media/server/main/mediaKeysCapabilities/RobustnessTest.cpp b/tests/unittests/media/server/main/mediaKeysCapabilities/RobustnessTest.cpp new file mode 100644 index 000000000..1bca787e9 --- /dev/null +++ b/tests/unittests/media/server/main/mediaKeysCapabilities/RobustnessTest.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 2024 Sky UK + * + * 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 "MediaKeysCapabilities.h" +#include "OcdmFactoryMock.h" +#include "OcdmMock.h" +#include "OcdmSystemFactoryMock.h" +#include "OcdmSystemMock.h" +#include + +using namespace firebolt::rialto; +using namespace firebolt::rialto::server; +using namespace firebolt::rialto::wrappers; + +using ::testing::_; +using ::testing::ByMove; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgReferee; +using ::testing::StrictMock; + +namespace +{ +const std::string kKeySystem{"com.netflix.playready"}; +const std::vector kRobustnessLevels{"HW_SECURE_ALL", "SW_SECURE_CRYPTO"}; +} // namespace + +class RialtoServerMediaKeysCapabilitiesRobustnessTest : public ::testing::Test +{ +protected: + std::shared_ptr> m_ocdmFactoryMock; + std::shared_ptr> m_ocdmMock; + std::shared_ptr> m_ocdmSystemFactoryMock; + std::shared_ptr> m_ocdmSystem; + StrictMock *m_ocdmSystemMock; + std::shared_ptr m_mediaKeysCapabilities; + + RialtoServerMediaKeysCapabilitiesRobustnessTest() + : m_ocdmFactoryMock{std::make_shared>()}, + m_ocdmMock{std::make_shared>()}, + m_ocdmSystemFactoryMock{std::make_shared>()}, + m_ocdmSystem{std::make_shared>()}, m_ocdmSystemMock{m_ocdmSystem.get()} + { + EXPECT_CALL(*m_ocdmFactoryMock, getOcdm()).WillOnce(Return(m_ocdmMock)); + + EXPECT_NO_THROW(m_mediaKeysCapabilities = + std::make_shared(m_ocdmFactoryMock, m_ocdmSystemFactoryMock)); + EXPECT_NE(m_mediaKeysCapabilities, nullptr); + } +}; + +/** + * Test that getSupportedRobustnessLevels returns the levels from the ocdm system. + */ +TEST_F(RialtoServerMediaKeysCapabilitiesRobustnessTest, GetSupportedRobustnessLevelsSuccess) +{ + std::vector levels; + EXPECT_CALL(*m_ocdmSystemFactoryMock, createOcdmSystem(kKeySystem)).WillOnce(Return(ByMove(std::move(m_ocdmSystem)))); + EXPECT_CALL(*m_ocdmSystemMock, getSupportedRobustnessLevels(_)) + .WillOnce(DoAll(SetArgReferee<0>(kRobustnessLevels), Return(true))); + + EXPECT_TRUE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystem, levels)); + EXPECT_EQ(levels, kRobustnessLevels); +} + +/** + * Test that getSupportedRobustnessLevels returns false when the ocdm system returns false. + */ +TEST_F(RialtoServerMediaKeysCapabilitiesRobustnessTest, GetSupportedRobustnessLevelsFailure) +{ + std::vector levels; + EXPECT_CALL(*m_ocdmSystemFactoryMock, createOcdmSystem(kKeySystem)).WillOnce(Return(ByMove(std::move(m_ocdmSystem)))); + EXPECT_CALL(*m_ocdmSystemMock, getSupportedRobustnessLevels(_)).WillOnce(Return(false)); + + EXPECT_FALSE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystem, levels)); +} + +/** + * Test that getSupportedRobustnessLevels returns false if failure to create the ocdm system object. + */ +TEST_F(RialtoServerMediaKeysCapabilitiesRobustnessTest, OcdmSystemFailure) +{ + std::vector levels; + EXPECT_CALL(*m_ocdmSystemFactoryMock, createOcdmSystem(kKeySystem)).WillOnce(Return(ByMove(nullptr))); + EXPECT_FALSE(m_mediaKeysCapabilities->getSupportedRobustnessLevels(kKeySystem, levels)); +} diff --git a/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp b/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp index 3abe1efe1..15beecf9b 100644 --- a/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/CallbackTest.cpp @@ -88,7 +88,7 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifyNeedMediaDataInPrerollingSta { auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; int sourceId = attachSource(mediaSourceType, "video/h264"); - constexpr int kNumFrames{24}; + constexpr int kNumFrames{3}; expectNotifyNeedData(mediaSourceType, sourceId, kNumFrames); @@ -111,6 +111,29 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifyNeedMediaDataInPlayingState) m_gstPlayerCallback->notifyNeedMediaData(mediaSourceType); } +/** + * Test a notification of the need media data is scheduled + */ +TEST_F(RialtoServerMediaPipelineCallbackTest, notifyDelayedNeedMediaData) +{ + auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; + attachSource(mediaSourceType, "video/h264"); + + setPlaybackStatePlaying(); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_sharedMemoryBufferMock, + clearData(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, mediaSourceType)) + .WillOnce(Return(true)); + const std::chrono::milliseconds m_kDefaultNeedMediaDataResendTimeout{15}; + EXPECT_CALL(*m_timerMock, isActive()).WillOnce(Return(true)); + EXPECT_CALL(*m_timerMock, cancel()); + EXPECT_CALL(*m_timerFactoryMock, createTimer(m_kDefaultNeedMediaDataResendTimeout, _, _)) + .WillOnce(Return(ByMove(std::move(m_timerMock)))); + + m_gstPlayerCallback->notifyNeedMediaDataWithDelay(mediaSourceType); +} + /** * Test a notification of the need media data is not forwarded when source id is not present */ @@ -127,6 +150,22 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifyNeedMediaDataFailureDueToSou m_gstPlayerCallback->notifyNeedMediaData(mediaSourceType); } +/** + * Test a notification of the delayed need media data is not forwarded when source id is not present + */ +TEST_F(RialtoServerMediaPipelineCallbackTest, notifyDelayedNeedMediaDataFailureDueToSourceIdNotPresent) +{ + auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; + mainThreadWillEnqueueTaskAndWait(); + ASSERT_TRUE(m_sharedMemoryBufferMock); + ASSERT_TRUE(m_activeRequestsMock); + EXPECT_CALL(*m_sharedMemoryBufferMock, + clearData(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, mediaSourceType)) + .WillOnce(Return(true)); + + m_gstPlayerCallback->notifyNeedMediaDataWithDelay(mediaSourceType); +} + /** * Test a notification of the need media data for audio is ignored if EOS. */ @@ -143,6 +182,22 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifyNeedMediaDataAudioInEos) m_gstPlayerCallback->notifyNeedMediaData(mediaSourceType); } +/** + * Test a notification of the delayed need media data for audio is ignored if EOS. + */ +TEST_F(RialtoServerMediaPipelineCallbackTest, notifyDelayedNeedMediaDataAudioInEos) +{ + auto mediaSourceType = firebolt::rialto::MediaSourceType::AUDIO; + attachSource(mediaSourceType, "audio/x-opus"); + + setPlaybackStatePlaying(); + setEos(mediaSourceType); + + expectNotifyNeedDataEos(mediaSourceType); + + m_gstPlayerCallback->notifyNeedMediaDataWithDelay(mediaSourceType); +} + /** * Test a notification of the need media data for video is ignored if EOS. */ @@ -269,3 +324,29 @@ TEST_F(RialtoServerMediaPipelineCallbackTest, notifySourceFlushedFailureSourceId m_gstPlayerCallback->notifySourceFlushed(mediaSourceType); } + +/** + * Test a notification of first frame received is forwarded to the registered client. + */ +TEST_F(RialtoServerMediaPipelineCallbackTest, notifyFirstFrameReceived) +{ + auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; + int sourceId = attachSource(mediaSourceType, "video/mp4"); + + mainThreadWillEnqueueTask(); + EXPECT_CALL(*m_mediaPipelineClientMock, notifyFirstFrameReceived(sourceId)); + + m_gstPlayerCallback->notifyFirstFrameReceived(mediaSourceType); +} + +/** + * Test a notification of first frame received fails when sourceid cannot be found. + */ +TEST_F(RialtoServerMediaPipelineCallbackTest, notifyFirstFrameReceivedFailureSourceIdNotFound) +{ + auto mediaSourceType = firebolt::rialto::MediaSourceType::VIDEO; + + mainThreadWillEnqueueTask(); + + m_gstPlayerCallback->notifyFirstFrameReceived(mediaSourceType); +} diff --git a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp index c4ef4c947..a9ca62986 100644 --- a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp @@ -100,6 +100,6 @@ TEST_F(RialtoServerMediaPipelineFlushTest, FlushResetEos) EXPECT_TRUE(m_mediaPipeline->flush(sourceId, m_kResetTime, async)); // Expect need data notified to client - expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 24); + expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 3); m_gstPlayerCallback->notifyNeedMediaData(firebolt::rialto::MediaSourceType::VIDEO); } diff --git a/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp b/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp index b1980c220..188bb8f43 100644 --- a/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/HaveDataTest.cpp @@ -28,8 +28,9 @@ using ::testing::Throw; class RialtoServerMediaPipelineHaveDataTest : public MediaPipelineTestBase { protected: - const uint32_t m_kNumFrames{24}; + const uint32_t m_kNumFrames{3}; const uint32_t m_kNeedDataRequestId{0}; + const bool m_kIsBufferFull{true}; const std::chrono::milliseconds m_kDefaultNeedMediaDataResendTimeout{15}; const std::chrono::milliseconds m_kLowLatencyNeedMediaDataResendTimeout{5}; @@ -221,6 +222,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithS mainThreadWillEnqueueTaskAndWait(); ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)).WillOnce(Return(mediaSourceType)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_timerMock, isActive()).WillOnce(Return(true)); EXPECT_CALL(*m_timerMock, cancel()); @@ -238,6 +240,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithS mainThreadWillEnqueueTaskAndWait(); ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)).WillOnce(Return(mediaSourceType)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_timerMock, isActive()).Times(2).WillRepeatedly(Return(true)); EXPECT_CALL(*m_timerMock, cancel()); @@ -246,6 +249,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithS EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); mainThreadWillEnqueueTaskAndWait(); EXPECT_CALL(*m_activeRequestsMock, getType(kNextNeedDataRequestId)).WillOnce(Return(mediaSourceType)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(kNextNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(kNextNeedDataRequestId)); EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, kNextNeedDataRequestId)); } @@ -269,6 +273,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithR mainThreadWillEnqueueTaskAndWait(); ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)).WillOnce(Return(mediaSourceType)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_timerFactoryMock, createTimer(m_kDefaultNeedMediaDataResendTimeout, _, _)) .WillOnce(Invoke( @@ -291,7 +296,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithR EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, mediaSourceType)) .WillOnce(Return(0)); - EXPECT_CALL(*m_activeRequestsMock, insert(mediaSourceType, _)).WillOnce(Return(0)); + EXPECT_CALL(*m_activeRequestsMock, insert(mediaSourceType, _, m_kNumFrames)).WillOnce(Return(0)); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNeedMediaData(sourceId, m_kNumFrames, 0, _)); // params tested in NeedMediaDataTests mainThreadWillEnqueueTask(); @@ -307,6 +312,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataFailureDueTo ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(nullptr)); EXPECT_CALL(*m_mediaPipelineClientMock, notifyPlaybackState(PlaybackState::FAILURE)); @@ -323,6 +329,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataFailureDueTo ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, @@ -344,13 +351,14 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataFailureDueTo ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, firebolt::rialto::MediaSourceType::VIDEO)) .WillOnce(Return(offset)); EXPECT_CALL(*m_dataReaderFactoryMock, - createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames)) + createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames, m_kIsBufferFull)) .WillOnce(Return(dataReader)); EXPECT_CALL(*m_mediaPipelineClientMock, notifyPlaybackState(PlaybackState::FAILURE)); EXPECT_FALSE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); @@ -367,13 +375,64 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccess) ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, firebolt::rialto::MediaSourceType::VIDEO)) .WillOnce(Return(offset)); EXPECT_CALL(*m_dataReaderFactoryMock, - createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames)) + createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames, m_kIsBufferFull)) + .WillOnce(Return(dataReader)); + EXPECT_CALL(*m_gstPlayerMock, attachSamples(dataReader)); + EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); +} + +TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithSmallerNumberOfFramesAndBufferFull) +{ + auto status = firebolt::rialto::MediaSourceStatus::NO_SPACE_FOR_SAMPLES; + std::uint8_t data{123}; + int offset = 0; + std::shared_ptr dataReader{std::make_shared()}; + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + ASSERT_TRUE(m_activeRequestsMock); + EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) + .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames + 10)); + EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); + EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); + EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, + firebolt::rialto::MediaSourceType::VIDEO)) + .WillOnce(Return(offset)); + EXPECT_CALL(*m_dataReaderFactoryMock, + createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames, m_kIsBufferFull)) + .WillOnce(Return(dataReader)); + EXPECT_CALL(*m_gstPlayerMock, attachSamples(dataReader)); + EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); +} + +TEST_F(RialtoServerMediaPipelineHaveDataTest, + ServerInternalHaveDataSuccessWithSmallerNumberOfFramesAndPartiallyFilledBuffer) +{ + auto status = firebolt::rialto::MediaSourceStatus::OK; + std::uint8_t data{123}; + int offset = 0; + constexpr bool kBufferIsNotFull{false}; + std::shared_ptr dataReader{std::make_shared()}; + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + ASSERT_TRUE(m_activeRequestsMock); + EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) + .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames + 10)); + EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); + EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); + EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, + firebolt::rialto::MediaSourceType::VIDEO)) + .WillOnce(Return(offset)); + EXPECT_CALL(*m_dataReaderFactoryMock, createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, + m_kNumFrames, kBufferIsNotFull)) .WillOnce(Return(dataReader)); EXPECT_CALL(*m_gstPlayerMock, attachSamples(dataReader)); EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); @@ -390,13 +449,14 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataAudioSuccess ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::AUDIO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, firebolt::rialto::MediaSourceType::AUDIO)) .WillOnce(Return(offset)); EXPECT_CALL(*m_dataReaderFactoryMock, - createDataReader(firebolt::rialto::MediaSourceType::AUDIO, &data, offset, m_kNumFrames)) + createDataReader(firebolt::rialto::MediaSourceType::AUDIO, &data, offset, m_kNumFrames, m_kIsBufferFull)) .WillOnce(Return(dataReader)); EXPECT_CALL(*m_gstPlayerMock, attachSamples(dataReader)); EXPECT_TRUE(m_mediaPipeline->haveData(status, m_kNumFrames, m_kNeedDataRequestId)); @@ -413,13 +473,14 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessWithE ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, firebolt::rialto::MediaSourceType::VIDEO)) .WillOnce(Return(offset)); EXPECT_CALL(*m_dataReaderFactoryMock, - createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames)) + createDataReader(firebolt::rialto::MediaSourceType::VIDEO, &data, offset, m_kNumFrames, m_kIsBufferFull)) .WillOnce(Return(dataReader)); EXPECT_CALL(*m_gstPlayerMock, attachSamples(dataReader)); EXPECT_CALL(*m_gstPlayerMock, setEos(firebolt::rialto::MediaSourceType::VIDEO)); @@ -436,6 +497,7 @@ TEST_F(RialtoServerMediaPipelineHaveDataTest, ServerInternalHaveDataSuccessEosWi ASSERT_TRUE(m_activeRequestsMock); EXPECT_CALL(*m_activeRequestsMock, getType(m_kNeedDataRequestId)) .WillOnce(Return(firebolt::rialto::MediaSourceType::VIDEO)); + EXPECT_CALL(*m_activeRequestsMock, getMaxFrames(m_kNeedDataRequestId)).WillOnce(Return(m_kNumFrames)); EXPECT_CALL(*m_activeRequestsMock, erase(m_kNeedDataRequestId)); EXPECT_CALL(*m_sharedMemoryBufferMock, getBuffer()).WillOnce(Return(&data)); EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, diff --git a/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp b/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp index 131a647f1..0e961781b 100644 --- a/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/LoadTest.cpp @@ -32,6 +32,7 @@ class RialtoServerMediaPipelineLoadTest : public MediaPipelineTestBase MediaType m_type = MediaType::MSE; const std::string m_kMimeType = "mime"; const std::string m_kUrl = "mse://1"; + const bool m_kIsLive = true; RialtoServerMediaPipelineLoadTest() { createMediaPipeline(); } @@ -45,11 +46,12 @@ TEST_F(RialtoServerMediaPipelineLoadTest, Success) { mainThreadWillEnqueueTaskAndWait(); mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, + createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), m_kIsLive, _)) .WillOnce(Return(ByMove(std::move(m_gstPlayer)))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(NetworkState::BUFFERING)); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), true); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), true); } /** @@ -59,9 +61,10 @@ TEST_F(RialtoServerMediaPipelineLoadTest, Success) TEST_F(RialtoServerMediaPipelineLoadTest, CreateGstPlayerFailure) { mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, + createGstGenericPlayer(_, _, m_type, VideoRequirementsMatcher(m_videoReq), m_kIsLive, _)) .WillOnce(Return(ByMove(nullptr))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(_)).Times(0); - EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl), false); + EXPECT_EQ(m_mediaPipeline->load(m_type, m_kMimeType, m_kUrl, m_kIsLive), false); } diff --git a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp index ce00bdee8..f888936e8 100644 --- a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp @@ -28,6 +28,7 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline { protected: const int64_t m_kPosition{4028596027}; + const int64_t m_kDuration{8057192054}; const double m_kPlaybackRate{1.5}; uint64_t m_kRenderedFrames{3141}; uint64_t m_kDroppedFrames{95}; @@ -43,11 +44,12 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) { + bool async{false}; loadGstPlayer(); mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, play()); - EXPECT_TRUE(m_mediaPipeline->play()); + EXPECT_CALL(*m_gstPlayerMock, play(_)); + EXPECT_TRUE(m_mediaPipeline->play(async)); } /** @@ -55,8 +57,9 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlayFailureDueToUninitializedPlayer) { + bool async{false}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_FALSE(m_mediaPipeline->play()); + EXPECT_FALSE(m_mediaPipeline->play(async)); } /** @@ -219,6 +222,44 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetPositionSuccess) EXPECT_EQ(targetPosition, m_kPosition); } +/** + * Test that GetDuration returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailureDueToUninitializedPlayer) +{ + int64_t targetDuration{}; + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailure) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns success if the gstreamer API succeeds and gets the duration + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationSuccess) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = m_kDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(targetDuration)); + EXPECT_EQ(targetDuration, m_kDuration); +} + /** * Test that SetImmediateOutput returns failure if the gstreamer player is not initialized */ @@ -325,6 +366,112 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetImmediateOutputSu EXPECT_EQ(immediateOutputState, false); } +/** + * Test that SetReportDecodeErrors returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsFailureDueToUninitializedPlayer) +{ + mainThreadWillEnqueueTaskAndWait(); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kDummySourceId, true)); +} + +/** + * Test that SetReportDecodeErrors returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsFailure) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, _)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, true)); +} + +/** + * Test that SetReportDecodeErrors fails if source is not present. + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsNoSourcePresent) +{ + loadGstPlayer(); + // No attachment of source + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kDummySourceId, true)); +} + +/** + * Test that SetReportDecodeErrors returns success if the gstreamer API succeeds + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsSuccess) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, true)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, true)); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, false)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, false)); +} + +/** + * Test that GetQueuedFrames returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesFailureDueToUninitializedPlayer) +{ + mainThreadWillEnqueueTaskAndWait(); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kDummySourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesFailure) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(Return(false)); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames fails if source is not present. + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesNoSourcePresent) +{ + loadGstPlayer(); + // No attachment of source + mainThreadWillEnqueueTaskAndWait(); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kDummySourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames returns success if the gstreamer API succeeds + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesSuccess) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + + uint32_t queuedFrames; + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(DoAll(SetArgReferee<0>(123), Return(true))); + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); + EXPECT_EQ(queuedFrames, 123); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(DoAll(SetArgReferee<0>(456), Return(true))); + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); + EXPECT_EQ(queuedFrames, 456); +} + /** * Test that GetStats returns failure if the gstreamer player is not initialized */ @@ -434,6 +581,7 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetVolumeSuccess) TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetVolumeFailureDueToUninitializedPlayer) { double resultVolume{}; + mainThreadWillEnqueueTaskAndWait(); EXPECT_FALSE(m_mediaPipeline->getVolume(resultVolume)); } @@ -443,6 +591,7 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetVolumeFailureDueT TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetVolumeFailure) { loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); double resultVolume{}; EXPECT_CALL(*m_gstPlayerMock, getVolume(_)).WillOnce(Return(false)); EXPECT_FALSE(m_mediaPipeline->getVolume(resultVolume)); @@ -457,6 +606,7 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetVolumeSuccess) double resultVolume{}; loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); EXPECT_CALL(*m_gstPlayerMock, getVolume(_)) .WillOnce(Invoke( [&](double &vol) @@ -468,56 +618,6 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetVolumeSuccess) EXPECT_EQ(resultVolume, kCurrentVolume); } -/** - * Test that GetVolume is enqueued, when volume setting is ongoing - */ -TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetAndGetVolumeSuccess) -{ - constexpr double kVolume{0.7}; - const uint32_t kVolumeDuration{0}; - const firebolt::rialto::EaseType kEaseType{firebolt::rialto::EaseType::EASE_LINEAR}; - double resultVolume{}; - std::mutex mutex; - std::condition_variable cv; - bool setVolumeQueued{false}; - bool getVolumeQueued{false}; - - loadGstPlayer(); - EXPECT_CALL(*m_mainThreadMock, enqueueTaskAndWait(m_kMainThreadClientId, _)) - .WillOnce(Invoke( - [&](uint32_t clientId, firebolt::rialto::server::IMainThread::Task task) - { - std::unique_lock lock{mutex}; - setVolumeQueued = true; - cv.notify_one(); - cv.wait(lock, [&]() { return getVolumeQueued; }); - EXPECT_CALL(*m_gstPlayerMock, setVolume(kVolume, kVolumeDuration, kEaseType)); - task(); - })) - .WillOnce(Invoke( - [&](uint32_t clientId, firebolt::rialto::server::IMainThread::Task task) - { - std::unique_lock lock{mutex}; - getVolumeQueued = true; - cv.notify_one(); - EXPECT_CALL(*m_gstPlayerMock, getVolume(_)) - .WillOnce(Invoke( - [&](double &vol) - { - vol = kVolume; - return true; - })); - task(); - })); - std::thread setVolumeThread([&]() { EXPECT_TRUE(m_mediaPipeline->setVolume(kVolume, kVolumeDuration, kEaseType)); }); - { - std::unique_lock lock{mutex}; - cv.wait(lock, [&]() { return setVolumeQueued; }); - } - EXPECT_TRUE(m_mediaPipeline->getVolume(resultVolume)); - setVolumeThread.join(); -} - /** * Test that SetMute returns failure if the gstreamer player is not initialized */ diff --git a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp index 491aec0e1..abfe2537e 100644 --- a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp @@ -86,11 +86,11 @@ void MediaPipelineTestBase::loadGstPlayer() { mainThreadWillEnqueueTaskAndWait(); mainThreadWillEnqueueTask(); - EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, _, _, _)) + EXPECT_CALL(*m_gstPlayerFactoryMock, createGstGenericPlayer(_, _, _, _, _, _)) .WillOnce(DoAll(SaveArg<0>(&m_gstPlayerCallback), Return(ByMove(std::move(m_gstPlayer))))); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNetworkState(NetworkState::BUFFERING)); - EXPECT_EQ(m_mediaPipeline->load(MediaType::MSE, "mime", "mse://1"), true); + EXPECT_EQ(m_mediaPipeline->load(MediaType::MSE, "mime", "mse://1", false), true); ASSERT_NE(m_gstPlayerCallback, nullptr); } @@ -143,7 +143,7 @@ void MediaPipelineTestBase::expectNotifyNeedData(MediaSourceType sourceType, int EXPECT_CALL(*m_sharedMemoryBufferMock, getDataOffset(ISharedMemoryBuffer::MediaPlaybackType::GENERIC, m_kSessionId, sourceType)) .WillOnce(Return(0)); - EXPECT_CALL(*m_activeRequestsMock, insert(sourceType, _)).WillOnce(Return(0)); + EXPECT_CALL(*m_activeRequestsMock, insert(sourceType, _, numFrames)).WillOnce(Return(0)); EXPECT_CALL(*m_mediaPipelineClientMock, notifyNeedMediaData(sourceId, numFrames, 0, _)); // params tested in NeedMediaDataTests } diff --git a/tests/unittests/media/server/main/needDataDelayCalculator/NeedDataDelayCalculatorTest.cpp b/tests/unittests/media/server/main/needDataDelayCalculator/NeedDataDelayCalculatorTest.cpp new file mode 100644 index 000000000..8f617f320 --- /dev/null +++ b/tests/unittests/media/server/main/needDataDelayCalculator/NeedDataDelayCalculatorTest.cpp @@ -0,0 +1,76 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 "NeedDataDelayCalculator.h" +#include + +namespace +{ +constexpr std::chrono::milliseconds kDefaultNeedMediaDataResendTimeMs{15}; +constexpr std::chrono::milliseconds kIncreasedNeedMediaDataResendTimeMs{30}; +} // namespace + +TEST(NeedDataDelayCalculatorTest, ShouldReturnDefaultDelayWhenNoDelayIsSet) +{ + firebolt::rialto::server::NeedDataDelayCalculator delayCalculator; + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); +} + +TEST(NeedDataDelayCalculatorTest, ShouldIncreaseAndDecreaseDelay) +{ + firebolt::rialto::server::NeedDataDelayCalculator delayCalculator; + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kIncreasedNeedMediaDataResendTimeMs); + delayCalculator.decreaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); +} + +TEST(NeedDataDelayCalculatorTest, ShouldIncreaseAndResetDelay) +{ + firebolt::rialto::server::NeedDataDelayCalculator delayCalculator; + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kIncreasedNeedMediaDataResendTimeMs); + delayCalculator.resetMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); +} + +TEST(NeedDataDelayCalculatorTest, ShouldIncreaseAndResetAllDelays) +{ + firebolt::rialto::server::NeedDataDelayCalculator delayCalculator; + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); + delayCalculator.increaseNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kIncreasedNeedMediaDataResendTimeMs); + delayCalculator.resetMediaDataDelay(); + EXPECT_EQ(delayCalculator.getNeedMediaDataDelay(firebolt::rialto::MediaSourceType::VIDEO), + kDefaultNeedMediaDataResendTimeMs); +} diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp index 746d78012..462f45d73 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTests.cpp @@ -30,3 +30,9 @@ TEST_F(NeedMediaDataTests, shouldSendMessageInPlayingState) initialize(firebolt::rialto::PlaybackState::PLAYING); needMediaDataWillBeSentInPlayingState(); } + +TEST_F(NeedMediaDataTests, shouldSendMessageInPrerollingState) +{ + initialize(firebolt::rialto::PlaybackState::PAUSED); + needMediaDataWillBeSentBelowPlayingState(); +} diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp index ac5fc88f6..ab3601ca1 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.cpp @@ -30,6 +30,7 @@ constexpr int kSourceId{1}; constexpr std::uint32_t kBufferLen{7 * 1024 * 1024}; constexpr std::uint32_t kMetadataOffset{1024}; constexpr int kRequestId{0}; +constexpr int kPrerollingNumFrames{3}; constexpr int kMaxFrames{24}; constexpr int kMaxMetadataBytes{2500}; } // namespace @@ -83,11 +84,24 @@ void NeedMediaDataTests::needMediaDataWillBeSentInPlayingState() expectedShmInfo->metadataOffset = kMetadataOffset; expectedShmInfo->mediaDataOffset = kMetadataOffset + kMaxMetadataBytes; ASSERT_TRUE(m_sut); - EXPECT_CALL(activeRequestsMock, insert(kValidMediaSourceType, _)).WillOnce(Return(kRequestId)); + EXPECT_CALL(activeRequestsMock, insert(kValidMediaSourceType, _, kMaxFrames)).WillOnce(Return(kRequestId)); EXPECT_CALL(*m_clientMock, notifyNeedMediaData(kSourceId, kMaxFrames, kRequestId, expectedShmInfo)); EXPECT_TRUE(m_sut->send()); } +void NeedMediaDataTests::needMediaDataWillBeSentBelowPlayingState() +{ + std::shared_ptr expectedShmInfo{ + std::make_shared()}; + expectedShmInfo->maxMetadataBytes = kMaxMetadataBytes; + expectedShmInfo->metadataOffset = kMetadataOffset; + expectedShmInfo->mediaDataOffset = kMetadataOffset + kMaxMetadataBytes; + ASSERT_TRUE(m_sut); + EXPECT_CALL(activeRequestsMock, insert(kValidMediaSourceType, _, kPrerollingNumFrames)).WillOnce(Return(kRequestId)); + EXPECT_CALL(*m_clientMock, notifyNeedMediaData(kSourceId, kPrerollingNumFrames, kRequestId, expectedShmInfo)); + EXPECT_TRUE(m_sut->send()); +} + void NeedMediaDataTests::needMediaDataWillNotBeSent() { ASSERT_TRUE(m_sut); diff --git a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h index 00dd9a1c1..6f725cf8c 100644 --- a/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h +++ b/tests/unittests/media/server/main/needMediaData/NeedMediaDataTestsFixture.h @@ -41,6 +41,7 @@ class NeedMediaDataTests : public testing::Test void needMediaDataWillBeSentInPlayingState(); void needMediaDataWillNotBeSent(); + void needMediaDataWillBeSentBelowPlayingState(); private: std::unique_ptr m_sut; diff --git a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h index 0a4d3fff1..b3daeec0e 100644 --- a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h @@ -28,9 +28,11 @@ namespace firebolt::rialto::server class FlushOnPrerollControllerMock : public IFlushOnPrerollController { public: - MOCK_METHOD(bool, shouldPostponeFlush, (const MediaSourceType &type), (const, override)); - MOCK_METHOD(void, setFlushing, (const MediaSourceType &type, const GstState ¤tPipelineState), (override)); + MOCK_METHOD(void, waitIfRequired, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setFlushing, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setPrerolling, (), (override)); MOCK_METHOD(void, stateReached, (const GstState &newPipelineState), (override)); + MOCK_METHOD(void, setTargetState, (const GstState &state), (override)); MOCK_METHOD(void, reset, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 902309e50..b7005db6e 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -47,6 +47,10 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type), (const, override)); + MOCK_METHOD(std::unique_ptr, createFirstFrameReceived, + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction), + (const, override)); MOCK_METHOD(std::unique_ptr, createFinishSetupSource, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player), (const, override)); MOCK_METHOD(std::unique_ptr, createHandleBusMessage, @@ -63,7 +67,7 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory const std::shared_ptr &dataReader), (const, override)); MOCK_METHOD(std::unique_ptr, createRemoveSource, - (GenericPlayerContext & context, (IGstGenericPlayerPrivate & player), + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type), (const, override)); MOCK_METHOD(std::unique_ptr, createReportPosition, @@ -110,8 +114,7 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory MediaSourceType sourceType), (const, override)); MOCK_METHOD(std::unique_ptr, createUpdatePlaybackGroup, - (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, GstElement *typefind, - const GstCaps *caps), + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, GstElement *typefind, GstCaps *caps), (const, override)); MOCK_METHOD(std::unique_ptr, createRenderFrame, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player), (const, override)); @@ -119,7 +122,7 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (const, override)); MOCK_METHOD(std::unique_ptr, createFlush, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, bool resetTime), + const firebolt::rialto::MediaSourceType &type, bool resetTime, bool isAsync), (const, override)); MOCK_METHOD(std::unique_ptr, createSetSourcePosition, (GenericPlayerContext & context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, @@ -137,6 +140,10 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, bool immediateOutput), (const, override)); + MOCK_METHOD(std::unique_ptr, createSetReportDecodeErrors, + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, bool reportDecodeErrors), + (const, override)); MOCK_METHOD(std::unique_ptr, createSetBufferingLimit, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, std::uint32_t limit), (const, override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h index 95b1d947a..49b6675d0 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h @@ -31,6 +31,7 @@ class GstDispatcherThreadFactoryMock : public IGstDispatcherThreadFactory public: MOCK_METHOD(std::unique_ptr, createGstDispatcherThread, (IGstDispatcherThreadClient & client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper), (const, override)); }; diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerClientMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerClientMock.h index a40fac73a..81f8cf0a1 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerClientMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerClientMock.h @@ -33,12 +33,14 @@ class GstGenericPlayerClientMock : public IGstGenericPlayerClient MOCK_METHOD(void, notifyPlaybackState, (PlaybackState state), (override)); MOCK_METHOD(bool, notifyNeedMediaData, (MediaSourceType mediaSourceType), (override)); + MOCK_METHOD(bool, notifyNeedMediaDataWithDelay, (MediaSourceType mediaSourceType), (override)); MOCK_METHOD(void, notifyPosition, (std::int64_t position), (override)); MOCK_METHOD(void, notifyNetworkState, (NetworkState state), (override)); MOCK_METHOD(void, clearActiveRequestsCache, (), (override)); MOCK_METHOD(void, invalidateActiveRequests, (const MediaSourceType &type), (override)); MOCK_METHOD(void, notifyQos, (MediaSourceType mediaSourceType, const QosInfo &qosInfo), (override)); MOCK_METHOD(void, notifyBufferUnderflow, (MediaSourceType mediaSourceType), (override)); + MOCK_METHOD(void, notifyFirstFrameReceived, (MediaSourceType mediaSourceType), (override)); MOCK_METHOD(void, notifyPlaybackError, (MediaSourceType mediaSourceType, PlaybackError error), (override)); MOCK_METHOD(void, notifySourceFlushed, (MediaSourceType mediaSourceType), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (const PlaybackInfo &playbackInfo), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h index 08a82a373..0139f54da 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerFactoryMock.h @@ -34,7 +34,7 @@ class GstGenericPlayerFactoryMock : public IGstGenericPlayerFactory MOCK_METHOD(std::unique_ptr, createGstGenericPlayer, (IGstGenericPlayerClient * client, IDecryptionService &decryptionService, MediaType type, - const VideoRequirements &videoRequirements, + const VideoRequirements &videoRequirements, bool isLive, const std::shared_ptr &rdkGstreamerUtilsWrapperFactory), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index de4697646..e751de7f2 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -36,15 +36,19 @@ class GstGenericPlayerMock : public IGstGenericPlayer MOCK_METHOD(void, attachSource, (const std::unique_ptr &mediaSource), (override)); MOCK_METHOD(void, removeSource, (const MediaSourceType &mediaSourceType), (override)); MOCK_METHOD(void, allSourcesAttached, (), (override)); - MOCK_METHOD(void, play, (), (override)); + MOCK_METHOD(void, play, (bool &async), (override)); MOCK_METHOD(void, pause, (), (override)); MOCK_METHOD(void, stop, (), (override)); MOCK_METHOD(void, attachSamples, (const IMediaPipeline::MediaSegmentVector &mediaSegments), (override)); MOCK_METHOD(void, attachSamples, (const std::shared_ptr &dataReader), (override)); MOCK_METHOD(void, setPosition, (std::int64_t position), (override)); MOCK_METHOD(bool, getPosition, (std::int64_t & position), (override)); + MOCK_METHOD(bool, getDuration, (std::int64_t & duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (const MediaSourceType &mediaSourceType, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (const MediaSourceType &mediaSourceType, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (const MediaSourceType &mediaSourceType, bool reportDecodeErrors), + (override)); + MOCK_METHOD(bool, getQueuedFrames, (uint32_t & queuedFrames), (override)); MOCK_METHOD(bool, getStats, (const MediaSourceType &mediaSourceType, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(void, setVideoGeometry, (int x, int y, int width, int height), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index ceab057ff..c7011f8cc 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -36,9 +36,15 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, scheduleEnoughData, (GstAppSrc * src), (override)); MOCK_METHOD(void, scheduleAudioUnderflow, (), (override)); MOCK_METHOD(void, scheduleVideoUnderflow, (), (override)); + MOCK_METHOD(void, scheduleFirstVideoFrameReceived, (), (override)); + MOCK_METHOD(void, scheduleFirstAudioFrameReceived, (AudioFirstFrameAction audioAction), (override)); + MOCK_METHOD(void, setAudioFirstFrameFallbackProbe, (GstPad * pad, gulong id), (override)); + MOCK_METHOD(void, clearAudioFirstFrameFallbackProbe, (), (override)); + MOCK_METHOD(void, clearAudioFirstFrameFallbackProbeState, (), (override)); MOCK_METHOD(void, scheduleAllSourcesAttached, (), (override)); MOCK_METHOD(bool, setVideoSinkRectangle, (), (override)); MOCK_METHOD(bool, setImmediateOutput, (), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (), (override)); MOCK_METHOD(bool, setLowLatency, (), (override)); MOCK_METHOD(bool, setSync, (), (override)); MOCK_METHOD(bool, setSyncOff, (), (override)); @@ -48,6 +54,7 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(bool, setUseBuffering, (), (override)); MOCK_METHOD(bool, setShowVideoWindow, (), (override)); MOCK_METHOD(void, notifyNeedMediaData, (const MediaSourceType mediaSource), (override)); + MOCK_METHOD(void, notifyNeedMediaDataWithDelay, (const MediaSourceType mediaSource), (override)); MOCK_METHOD(GstBuffer *, createBuffer, (const IMediaPipeline::MediaSegment &mediaSegment), (const, override)); MOCK_METHOD(void, attachData, (const firebolt::rialto::MediaSourceType mediaType), (override)); MOCK_METHOD(void, updateAudioCaps, (int32_t rate, int32_t channels, const std::shared_ptr &codecData), @@ -55,10 +62,12 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, updateVideoCaps, (int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData), (override)); - MOCK_METHOD(bool, changePipelineState, (GstState newState), (override)); + MOCK_METHOD(GstStateChangeReturn, changePipelineState, (GstState newState), (override)); MOCK_METHOD(int64_t, getPosition, (GstElement * element), (override)); MOCK_METHOD(void, startPositionReportingAndCheckAudioUnderflowTimer, (), (override)); MOCK_METHOD(void, stopPositionReportingAndCheckAudioUnderflowTimer, (), (override)); + MOCK_METHOD(void, startNotifyPlaybackInfoTimer, (), (override)); + MOCK_METHOD(void, stopNotifyPlaybackInfoTimer, (), (override)); MOCK_METHOD(void, stopWorkerThread, (), (override)); MOCK_METHOD(void, cancelUnderflow, (firebolt::rialto::MediaSourceType mediaSource), (override)); MOCK_METHOD(void, setPendingPlaybackRate, (), (override)); @@ -68,18 +77,13 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, removeAutoVideoSinkChild, (GObject * object), (override)); MOCK_METHOD(void, removeAutoAudioSinkChild, (GObject * object), (override)); MOCK_METHOD(GstElement *, getSink, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, setPlaybinFlags, (bool enableAudio), (override)); - MOCK_METHOD(void, addAudioClippingToBuffer, (GstBuffer * buffer, uint64_t clippingStart, uint64_t clippingEnd), (const, override)); - MOCK_METHOD(void, pushSampleIfRequired, (GstElement * source, const std::string &typeStr), (override)); MOCK_METHOD(bool, reattachSource, (const std::unique_ptr &source), (override)); MOCK_METHOD(void, setSourceFlushed, (const MediaSourceType &mediaSourceType), (override)); MOCK_METHOD(void, startSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(void, stopSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(bool, hasSourceType, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, postponeFlush, (const MediaSourceType &mediaSourceType, bool resetTime), (override)); - MOCK_METHOD(void, executePostponedFlushes, (), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h new file mode 100644 index 000000000..f8357fd55 --- /dev/null +++ b/tests/unittests/media/server/mocks/gstplayer/GstProfilerFactoryMock.h @@ -0,0 +1,46 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ + +#include "IGstProfiler.h" + +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerFactoryMock : public IGstProfilerFactory +{ +public: + using IGstWrapper = firebolt::rialto::wrappers::IGstWrapper; + using IGlibWrapper = firebolt::rialto::wrappers::IGlibWrapper; + + GstProfilerFactoryMock() = default; + ~GstProfilerFactoryMock() override = default; + + MOCK_METHOD(std::unique_ptr, createGstProfiler, + (GstElement * pipeline, const std::shared_ptr &gstWrapper, + const std::shared_ptr &glibWrapper), + (const, override)); +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_FACTORY_MOCK_H_ diff --git a/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h new file mode 100644 index 000000000..ef549dbc2 --- /dev/null +++ b/tests/unittests/media/server/mocks/gstplayer/GstProfilerMock.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * 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 FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ + +#include "IGstProfiler.h" + +#include +#include +#include +#include + +namespace firebolt::rialto::server +{ +class GstProfilerMock : public IGstProfiler +{ +public: + GstProfilerMock() = default; + ~GstProfilerMock() override = default; + + MOCK_METHOD(std::optional, createRecord, (const std::string &stage), (override)); + MOCK_METHOD(std::optional, createRecord, (const std::string &stage, const std::string &info), (override)); + + MOCK_METHOD(void, scheduleGstElementRecord, (GstElement * element), (override)); + MOCK_METHOD(std::vector, getRecords, (), (const, override)); + + MOCK_METHOD(void, logRecord, (RecordId id), (override)); + MOCK_METHOD(void, dumpToFile, (), (const, override)); + MOCK_METHOD(void, logPipelineSummary, (), (const, override)); +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PROFILER_MOCK_H_ diff --git a/tests/unittests/media/server/mocks/ipc/MediaKeysCapabilitiesModuleServiceMock.h b/tests/unittests/media/server/mocks/ipc/MediaKeysCapabilitiesModuleServiceMock.h index ecf1ed4b1..0cfb06db8 100644 --- a/tests/unittests/media/server/mocks/ipc/MediaKeysCapabilitiesModuleServiceMock.h +++ b/tests/unittests/media/server/mocks/ipc/MediaKeysCapabilitiesModuleServiceMock.h @@ -50,6 +50,11 @@ class MediaKeysCapabilitiesModuleServiceMock : public IMediaKeysCapabilitiesModu const ::firebolt::rialto::GetSupportedKeySystemVersionRequest *request, ::firebolt::rialto::GetSupportedKeySystemVersionResponse *response, ::google::protobuf::Closure *done), (override)); + MOCK_METHOD(void, getSupportedRobustnessLevels, + (::google::protobuf::RpcController * controller, + const ::firebolt::rialto::GetSupportedRobustnessLevelsRequest *request, + ::firebolt::rialto::GetSupportedRobustnessLevelsResponse *response, ::google::protobuf::Closure *done), + (override)); }; } // namespace firebolt::rialto::server::ipc diff --git a/tests/unittests/media/server/mocks/main/ActiveRequestsMock.h b/tests/unittests/media/server/mocks/main/ActiveRequestsMock.h index 8d73ac0ad..c59bf206b 100644 --- a/tests/unittests/media/server/mocks/main/ActiveRequestsMock.h +++ b/tests/unittests/media/server/mocks/main/ActiveRequestsMock.h @@ -29,8 +29,11 @@ namespace firebolt::rialto::server class ActiveRequestsMock : public IActiveRequests { public: - MOCK_METHOD(std::uint32_t, insert, (const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes), (override)); + MOCK_METHOD(std::uint32_t, insert, + (const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes, std::uint32_t maxFrames), + (override)); MOCK_METHOD(MediaSourceType, getType, (std::uint32_t requestId), (const, override)); + MOCK_METHOD(std::uint32_t, getMaxFrames, (std::uint32_t requestId), (const, override)); MOCK_METHOD(void, erase, (std::uint32_t requestId), (override)); MOCK_METHOD(void, erase, (const MediaSourceType &mediaSourceType), (override)); MOCK_METHOD(void, clear, (), (override)); diff --git a/tests/unittests/media/server/mocks/main/DataReaderFactoryMock.h b/tests/unittests/media/server/mocks/main/DataReaderFactoryMock.h index f775c3190..e198cba0a 100644 --- a/tests/unittests/media/server/mocks/main/DataReaderFactoryMock.h +++ b/tests/unittests/media/server/mocks/main/DataReaderFactoryMock.h @@ -31,7 +31,7 @@ class DataReaderFactoryMock : public IDataReaderFactory { public: MOCK_METHOD(std::shared_ptr, createDataReader, - (const MediaSourceType &, std::uint8_t *, std::uint32_t, std::uint32_t), (const, override)); + (const MediaSourceType &, std::uint8_t *, std::uint32_t, std::uint32_t, bool), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/DataReaderMock.h b/tests/unittests/media/server/mocks/main/DataReaderMock.h index 32efa298b..e7bcec5d7 100644 --- a/tests/unittests/media/server/mocks/main/DataReaderMock.h +++ b/tests/unittests/media/server/mocks/main/DataReaderMock.h @@ -29,6 +29,7 @@ class DataReaderMock : public IDataReader { public: MOCK_METHOD(IMediaPipeline::MediaSegmentVector, readData, (), (const, override)); + MOCK_METHOD(bool, isBufferFull, (), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h b/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h index 64f57c40c..c6a600510 100644 --- a/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h +++ b/tests/unittests/media/server/mocks/main/DecryptionServiceMock.h @@ -30,7 +30,7 @@ class DecryptionServiceMock : public IDecryptionService { public: MOCK_METHOD(MediaKeyErrorStatus, decrypt, (int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (int32_t keySessionId), (override)); + MOCK_METHOD(bool, isExtendedInterfaceUsed, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(void, incrementSessionIdUsageCounter, (int32_t keySessionId), (override)); MOCK_METHOD(void, decrementSessionIdUsageCounter, (int32_t keySessionId), (override)); diff --git a/tests/unittests/media/server/mocks/main/MainThreadMock.h b/tests/unittests/media/server/mocks/main/MainThreadMock.h index acf9dc5e2..d8359bfde 100644 --- a/tests/unittests/media/server/mocks/main/MainThreadMock.h +++ b/tests/unittests/media/server/mocks/main/MainThreadMock.h @@ -32,9 +32,9 @@ class MainThreadMock : public IMainThread public: MOCK_METHOD(int32_t, registerClient, (), (override)); MOCK_METHOD(void, unregisterClient, (uint32_t clientId), (override)); - MOCK_METHOD(void, enqueueTask, (uint32_t clientId, Task task), (override)); - MOCK_METHOD(void, enqueueTaskAndWait, (uint32_t clientId, Task task), (override)); - MOCK_METHOD(void, enqueuePriorityTaskAndWait, (uint32_t clientId, Task task), (override)); + MOCK_METHOD(void, enqueueTask, (uint32_t clientId, const Task &task), (override)); + MOCK_METHOD(void, enqueueTaskAndWait, (uint32_t clientId, const Task &task), (override)); + MOCK_METHOD(void, enqueuePriorityTaskAndWait, (uint32_t clientId, const Task &task), (override)); }; } // namespace firebolt::rialto::server::mock diff --git a/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h b/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h index d7ada3a1c..fc2835f40 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeySessionFactoryMock.h @@ -33,7 +33,7 @@ class MediaKeySessionFactoryMock : public IMediaKeySessionFactory MOCK_METHOD(std::unique_ptr, createMediaKeySession, (const std::string &keySystem, int32_t keySessionId, const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType, - std::weak_ptr client, bool isLDL), + std::weak_ptr client), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h b/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h index b813d80eb..8bbdc8284 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeySessionMock.h @@ -30,7 +30,8 @@ namespace firebolt::rialto::server class MediaKeySessionMock : public IMediaKeySession { public: - MOCK_METHOD(MediaKeyErrorStatus, generateRequest, (InitDataType initDataType, const std::vector &initData), + MOCK_METHOD(MediaKeyErrorStatus, generateRequest, + (InitDataType initDataType, const std::vector &initData, const LimitedDurationLicense &ldlState), (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (const std::vector &responseData), (override)); @@ -42,7 +43,6 @@ class MediaKeySessionMock : public IMediaKeySession MOCK_METHOD(MediaKeyErrorStatus, setDrmHeader, (const std::vector &requestData), (override)); MOCK_METHOD(MediaKeyErrorStatus, getLastDrmError, (uint32_t & errorCode), (override)); MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (const std::vector &keyId), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h index 7333feede..4ee5df08f 100644 --- a/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaKeysServerInternalMock.h @@ -37,10 +37,11 @@ class MediaKeysServerInternalMock : public IMediaKeysServerInternal MOCK_METHOD(MediaKeyErrorStatus, selectKeyId, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(bool, containsKey, (int32_t keySessionId, const std::vector &keyId), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, - (KeySessionType sessionType, std::weak_ptr client, bool isLDL, int32_t &keySessionId), - (override)); + (KeySessionType sessionType, std::weak_ptr client, int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, - (int32_t keySessionId, InitDataType initDataType, const std::vector &initData), (override)); + (int32_t keySessionId, InitDataType initDataType, const std::vector &initData, + const LimitedDurationLicense &ldlState), + (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, (int32_t keySessionId, const std::vector &responseData), (override)); @@ -58,7 +59,6 @@ class MediaKeysServerInternalMock : public IMediaKeysServerInternal MOCK_METHOD(MediaKeyErrorStatus, getCdmKeySessionId, (int32_t keySessionId, std::string &cdmKeySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, decrypt, (int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps), (override)); - MOCK_METHOD(bool, isNetflixPlayreadyKeySystem, (), (const, override)); MOCK_METHOD(void, ping, (std::unique_ptr && heartbeatHandler), (override)); MOCK_METHOD(MediaKeyErrorStatus, releaseKeySession, (int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, getMetricSystemData, (std::vector & buffer), (override)); diff --git a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h index 61ee32b61..8e7db9fe2 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -32,11 +32,12 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal { public: MOCK_METHOD(std::weak_ptr, getClient, (), (override)); - MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); + MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url, bool isLive), + (override)); MOCK_METHOD(bool, attachSource, (const std::unique_ptr &source), (override)); MOCK_METHOD(bool, removeSource, (int32_t id), (override)); MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, setPlaybackRate, (double rate), (override)); @@ -44,6 +45,8 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, getPosition, (std::int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); MOCK_METHOD(bool, haveData, (MediaSourceStatus status, uint32_t numFrames, uint32_t needDataRequestId), (override)); @@ -75,6 +78,7 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); MOCK_METHOD(bool, setSubtitleOffset, (int32_t sourceId, int64_t position), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/service/CdmServiceMock.h b/tests/unittests/media/server/mocks/service/CdmServiceMock.h index 4c0699c46..27cce73a8 100644 --- a/tests/unittests/media/server/mocks/service/CdmServiceMock.h +++ b/tests/unittests/media/server/mocks/service/CdmServiceMock.h @@ -37,11 +37,11 @@ class CdmServiceMock : public ICdmService MOCK_METHOD(bool, destroyMediaKeys, (int mediaKeysHandle), (override)); MOCK_METHOD(MediaKeyErrorStatus, createKeySession, (int mediaKeysHandle, KeySessionType sessionType, const std::shared_ptr &client, - bool isLDL, int32_t &keySessionId), + int32_t &keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, generateRequest, (int mediaKeysHandle, int32_t keySessionId, InitDataType initDataType, - const std::vector &initData), + const std::vector &initData, const LimitedDurationLicense &ldlState), (override)); MOCK_METHOD(MediaKeyErrorStatus, loadSession, (int mediaKeysHandle, int32_t keySessionId), (override)); MOCK_METHOD(MediaKeyErrorStatus, updateSession, @@ -69,6 +69,8 @@ class CdmServiceMock : public ICdmService MOCK_METHOD(bool, supportsKeySystem, (const std::string &keySystem), (override)); MOCK_METHOD(bool, getSupportedKeySystemVersion, (const std::string &keySystem, std::string &version), (override)); MOCK_METHOD(bool, isServerCertificateSupported, (const std::string &keySystem), (override)); + MOCK_METHOD(bool, getSupportedRobustnessLevels, + (const std::string &keySystem, std::vector &robustnessLevels), (override)); MOCK_METHOD(void, ping, (const std::shared_ptr &heartbeatProcedure), (override)); MOCK_METHOD(MediaKeyErrorStatus, getMetricSystemData, (int mediaKeysHandle, std::vector &buffer), (override)); diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index 69812124d..7c9179031 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -34,18 +34,21 @@ class MediaPipelineServiceMock : public IMediaPipelineService MOCK_METHOD(bool, createSession, (int, const std::shared_ptr &, std::uint32_t, std::uint32_t), (override)); MOCK_METHOD(bool, destroySession, (int), (override)); - MOCK_METHOD(bool, load, (int, MediaType, const std::string &, const std::string &), (override)); + MOCK_METHOD(bool, load, (int, MediaType, const std::string &, const std::string &, bool), (override)); MOCK_METHOD(bool, attachSource, (int, const std::unique_ptr &), (override)); MOCK_METHOD(bool, removeSource, (int, std::int32_t), (override)); MOCK_METHOD(bool, allSourcesAttached, (int), (override)); - MOCK_METHOD(bool, play, (int), (override)); + MOCK_METHOD(bool, play, (int, bool &), (override)); MOCK_METHOD(bool, pause, (int), (override)); MOCK_METHOD(bool, stop, (int), (override)); MOCK_METHOD(bool, setPlaybackRate, (int, double), (override)); MOCK_METHOD(bool, setPosition, (int, int64_t), (override)); MOCK_METHOD(bool, getPosition, (int sessionId, int64_t &position), (override)); + MOCK_METHOD(bool, getDuration, (int sessionId, int64_t &duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (int sessionId, int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int sessionId, int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int sessionId, int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int sessionId, int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t), (override)); diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp b/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp index 2c27cac62..534442631 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTests.cpp @@ -81,7 +81,6 @@ TEST_F(CdmServiceTests, shouldCreateKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); destroyMediaKeysShouldSucceed(); @@ -108,7 +107,6 @@ TEST_F(CdmServiceTests, shouldFailToCreateKeySessionWhenMediaKeysClientExists) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -123,6 +121,20 @@ TEST_F(CdmServiceTests, shouldGenerateRequest) createMediaKeysShouldSucceed(); mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(false); + destroyMediaKeysShouldSucceed(); +} + +TEST_F(CdmServiceTests, shouldGenerateRequestWithLdlEnabled) +{ + triggerSwitchToActiveSuccess(); + mediaKeysFactoryWillCreateMediaKeys(); + createMediaKeysShouldSucceed(); + mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + createKeySessionShouldSucceed(); + mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -199,7 +211,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -212,7 +223,6 @@ TEST_F(CdmServiceTests, incrementSessionUsage) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -224,7 +234,6 @@ TEST_F(CdmServiceTests, deccrementSessionUsage) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); decrementSessionIdUsageCounter(); @@ -260,7 +269,6 @@ TEST_F(CdmServiceTests, shouldFailToCloseKeySessionWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -273,7 +281,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySessionDeferred) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -288,7 +295,6 @@ TEST_F(CdmServiceTests, shouldCloseKeySessionDeferredWithFailurePrint) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -355,7 +361,6 @@ TEST_F(CdmServiceTests, shouldDecrypt) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -374,7 +379,6 @@ TEST_F(CdmServiceTests, shouldFailToDecryptWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -396,11 +400,11 @@ TEST_F(CdmServiceTests, shouldSelectKeyId) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); selectKeyIdShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -415,7 +419,6 @@ TEST_F(CdmServiceTests, shouldFailToSelectKeyIdWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -463,8 +466,11 @@ TEST_F(CdmServiceTests, shouldSetDrmHeader) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); + mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + createKeySessionShouldSucceed(); mediaKeysWillSetDrmHeaderWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); setDrmHeaderShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + isExtendedInterfaceUsedShouldReturn(true); destroyMediaKeysShouldSucceed(); } @@ -671,7 +677,6 @@ TEST_F(CdmServiceTests, shouldReleaseKeySession) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); @@ -684,7 +689,6 @@ TEST_F(CdmServiceTests, shouldReleaseKeySessionDeferred) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); incrementSessionIdUsageCounter(); @@ -705,7 +709,6 @@ TEST_F(CdmServiceTests, shouldFailToReleaseKeySessionWhenMediaKeysFails) triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE); @@ -713,6 +716,17 @@ TEST_F(CdmServiceTests, shouldFailToReleaseKeySessionWhenMediaKeysFails) destroyMediaKeysShouldSucceed(); } +TEST_F(CdmServiceTests, shouldFailToReleaseKeySessionAfterSwitchToInactive) +{ + triggerSwitchToActiveSuccess(); + mediaKeysFactoryWillCreateMediaKeys(); + createMediaKeysShouldSucceed(); + mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); + createKeySessionShouldSucceed(); + triggerSwitchToInactive(); + releaseKeySessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus::FAIL); +} + TEST_F(CdmServiceTests, shouldGetNoKeySystemsFromGetSupportedKeySystemsInInactiveState) { getSupportedKeySystemsReturnNon(); @@ -801,42 +815,57 @@ TEST_F(CdmServiceTests, shouldGetServerCertificateSupportedIfSupportedInActiveSt supportsServerCertificateReturnTrue(); } -TEST_F(CdmServiceTests, shouldCheckThatKeySystemIsPlayready) +TEST_F(CdmServiceTests, shouldFailToGetSupportedRobustnessLevelsInInactiveState) +{ + getSupportedRobustnessLevelsShouldFail(); +} + +TEST_F(CdmServiceTests, shouldFailToGetSupportedRobustnessLevelsIfCreationFailureInActiveState) { triggerSwitchToActiveSuccess(); - mediaKeysFactoryWillCreateMediaKeys(); - createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(true); - mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); - createKeySessionShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(true); - destroyMediaKeysShouldSucceed(); + mediaKeysCapabilitiesFactoryWillReturnNullptr(); + getSupportedRobustnessLevelsShouldFail(); +} + +TEST_F(CdmServiceTests, shouldFailToGetSupportedRobustnessLevelsIfApiFailureInActiveState) +{ + triggerSwitchToActiveSuccess(); + mediaKeysCapabilitiesFactoryWillCreateMediaKeysCapabilities(); + getSupportedRobustnessLevelsWillFail(); + getSupportedRobustnessLevelsShouldFail(); +} + +TEST_F(CdmServiceTests, shouldGetSupportedRobustnessLevelsInActiveState) +{ + triggerSwitchToActiveSuccess(); + mediaKeysCapabilitiesFactoryWillCreateMediaKeysCapabilities(); + getSupportedRobustnessLevelsWillSucceed(); + getSupportedRobustnessLevelsShouldSucceed(); } -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenNoMediaKeys) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenNoMediaKeys) { triggerSwitchToActiveSuccess(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); } -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenMediaKeysFails) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenMediaKeysFails) { triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - mediaKeysWillCheckIfKeySystemIsPlayready(false); mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus::OK); createKeySessionShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); destroyMediaKeysShouldSucceed(); } -TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingPlayreadyKeySystemWhenMediaKeysIsNotFoundForSession) +TEST_F(CdmServiceTests, shouldReturnFalseWhenCheckingExtendedInterfaceWhenMediaKeysIsNotFoundForSession) { triggerSwitchToActiveSuccess(); mediaKeysFactoryWillCreateMediaKeys(); createMediaKeysShouldSucceed(); - isNetflixPlayreadyKeySystemShouldReturn(false); + isExtendedInterfaceUsedShouldReturn(false); destroyMediaKeysShouldSucceed(); } diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp index 73e0a7a43..c912689c5 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.cpp @@ -35,7 +35,6 @@ const std::vector kKeySystems{"expectedKeySystem1", "expectedKeySys const std::string kVersion{"123"}; constexpr int kMediaKeysHandle{2}; constexpr firebolt::rialto::KeySessionType kKeySessionType{firebolt::rialto::KeySessionType::TEMPORARY}; -constexpr bool kIsLDL{false}; constexpr int kKeySessionId{3}; constexpr firebolt::rialto::InitDataType kInitDataType{firebolt::rialto::InitDataType::CENC}; const std::vector kInitData{6, 7, 2}; @@ -44,6 +43,9 @@ const std::vector keyId{1, 4, 7}; const std::vector kDrmHeader{4, 9, 3}; const uint32_t kSubSampleCount{2}; constexpr uint32_t kInitWithLast15{1}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlState{firebolt::rialto::LimitedDurationLicense::NOT_SPECIFIED}; +constexpr firebolt::rialto::LimitedDurationLicense kLdlStateEnabled{firebolt::rialto::LimitedDurationLicense::ENABLED}; +const std::vector kRobustnessLevels{"HW_SECURE_ALL", "SW_SECURE_CRYPTO"}; } // namespace CdmServiceTests::CdmServiceTests() @@ -98,6 +100,17 @@ void CdmServiceTests::supportsServerCertificateWillReturnTrue() EXPECT_CALL(m_mediaKeysCapabilitiesMock, isServerCertificateSupported(kKeySystems[0])).WillOnce(Return(true)); } +void CdmServiceTests::getSupportedRobustnessLevelsWillSucceed() +{ + EXPECT_CALL(m_mediaKeysCapabilitiesMock, getSupportedRobustnessLevels(kKeySystems[0], _)) + .WillOnce(DoAll(SetArgReferee<1>(kRobustnessLevels), Return(true))); +} + +void CdmServiceTests::getSupportedRobustnessLevelsWillFail() +{ + EXPECT_CALL(m_mediaKeysCapabilitiesMock, getSupportedRobustnessLevels(kKeySystems[0], _)).WillOnce(Return(false)); +} + void CdmServiceTests::triggerSwitchToActiveSuccess() { EXPECT_TRUE(m_sut.switchToActive()); @@ -126,13 +139,20 @@ void CdmServiceTests::mediaKeysFactoryWillReturnNullptr() void CdmServiceTests::mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_CALL(m_mediaKeysMock, createKeySession(kKeySessionType, _, kIsLDL, _)) - .WillOnce(DoAll(SetArgReferee<3>(kKeySessionId), Return(status))); + EXPECT_CALL(m_mediaKeysMock, createKeySession(kKeySessionType, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kKeySessionId), Return(status))); } void CdmServiceTests::mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData)).WillOnce(Return(status)); + EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData, kLdlState)) + .WillOnce(Return(status)); +} + +void CdmServiceTests::mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus status) +{ + EXPECT_CALL(m_mediaKeysMock, generateRequest(kKeySessionId, kInitDataType, kInitData, kLdlStateEnabled)) + .WillOnce(Return(status)); } void CdmServiceTests::mediaKeysWillLoadSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status) @@ -220,11 +240,6 @@ void CdmServiceTests::mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::Media EXPECT_CALL(m_mediaKeysMock, selectKeyId(kKeySessionId, keyId)).WillOnce(Return(status)); } -void CdmServiceTests::mediaKeysWillCheckIfKeySystemIsPlayready(bool result) -{ - EXPECT_CALL(m_mediaKeysMock, isNetflixPlayreadyKeySystem()).WillOnce(Return(result)); -} - void CdmServiceTests::mediaKeysWillPing() { EXPECT_CALL(*m_heartbeatProcedureMock, createHandler()); @@ -260,21 +275,25 @@ void CdmServiceTests::createKeySessionShouldSucceed() { int32_t returnKeySessionId = -1; EXPECT_EQ(firebolt::rialto::MediaKeyErrorStatus::OK, - m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, kIsLDL, - returnKeySessionId)); + m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, returnKeySessionId)); EXPECT_GE(returnKeySessionId, -1); } void CdmServiceTests::createKeySessionShouldFailWithReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) { int32_t returnKeySessionId = -1; - EXPECT_EQ(status, m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, kIsLDL, - returnKeySessionId)); + EXPECT_EQ(status, + m_sut.createKeySession(kMediaKeysHandle, kKeySessionType, m_mediaKeysClientMock, returnKeySessionId)); } void CdmServiceTests::generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) { - EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData)); + EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlState)); +} + +void CdmServiceTests::generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) +{ + EXPECT_EQ(status, m_sut.generateRequest(kMediaKeysHandle, kKeySessionId, kInitDataType, kInitData, kLdlStateEnabled)); } void CdmServiceTests::loadSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status) @@ -370,9 +389,9 @@ void CdmServiceTests::releaseKeySessionShouldReturnStatus(firebolt::rialto::Medi EXPECT_EQ(status, m_sut.releaseKeySession(kMediaKeysHandle, kKeySessionId)); } -void CdmServiceTests::isNetflixPlayreadyKeySystemShouldReturn(bool result) +void CdmServiceTests::isExtendedInterfaceUsedShouldReturn(bool result) { - EXPECT_EQ(result, m_sut.isNetflixPlayreadyKeySystem(kKeySessionId)); + EXPECT_EQ(result, m_sut.isExtendedInterfaceUsed(kKeySessionId)); } void CdmServiceTests::getSupportedKeySystemsShouldSucceed() @@ -418,6 +437,19 @@ void CdmServiceTests::supportsServerCertificateReturnFalse() EXPECT_FALSE(m_sut.isServerCertificateSupported(kKeySystems[0])); } +void CdmServiceTests::getSupportedRobustnessLevelsShouldSucceed() +{ + std::vector levels; + EXPECT_TRUE(m_sut.getSupportedRobustnessLevels(kKeySystems[0], levels)); + EXPECT_EQ(levels, kRobustnessLevels); +} + +void CdmServiceTests::getSupportedRobustnessLevelsShouldFail() +{ + std::vector levels; + EXPECT_FALSE(m_sut.getSupportedRobustnessLevels(kKeySystems[0], levels)); +} + void CdmServiceTests::incrementSessionIdUsageCounter() { m_sut.incrementSessionIdUsageCounter(kKeySessionId); diff --git a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h index 77939fd0b..2fbeab0cd 100644 --- a/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h +++ b/tests/unittests/media/server/service/cdmService/CdmServiceTestsFixture.h @@ -45,6 +45,7 @@ class CdmServiceTests : public testing::Test void mediaKeysFactoryWillReturnNullptr(); void mediaKeysWillCreateKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillGenerateRequestWithStatus(firebolt::rialto::MediaKeyErrorStatus status); + void mediaKeysWillGenerateRequestLdlEnabledWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillLoadSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillUpdateSessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillCloseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -62,7 +63,6 @@ class CdmServiceTests : public testing::Test void mediaKeysWillReleaseKeySessionWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillDecryptWithStatus(firebolt::rialto::MediaKeyErrorStatus status); void mediaKeysWillSelectKeyIdWithStatus(firebolt::rialto::MediaKeyErrorStatus status); - void mediaKeysWillCheckIfKeySystemIsPlayready(bool result); void mediaKeysWillPing(); void mediaKeysWillGetMetricSystemDataWithStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -73,6 +73,8 @@ class CdmServiceTests : public testing::Test void getSupportedKeySystemVersionWillSucceed(); void getSupportedKeySystemVersionWillFail(); void supportsServerCertificateWillReturnTrue(); + void getSupportedRobustnessLevelsWillSucceed(); + void getSupportedRobustnessLevelsWillFail(); void triggerSwitchToActiveSuccess(); void triggerSwitchToInactive(); @@ -85,6 +87,7 @@ class CdmServiceTests : public testing::Test void createKeySessionShouldSucceed(); void createKeySessionShouldFailWithReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void generateRequestShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); + void generateRequestWithLdlEnabledShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void loadSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void updateSessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void closeKeySessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -102,7 +105,7 @@ class CdmServiceTests : public testing::Test void getLastDrmErrorShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void getDrmTimeShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); void releaseKeySessionShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); - void isNetflixPlayreadyKeySystemShouldReturn(bool result); + void isExtendedInterfaceUsedShouldReturn(bool result); void incrementSessionIdUsageCounter(); void decrementSessionIdUsageCounter(); void getMetricSystemDataShouldReturnStatus(firebolt::rialto::MediaKeyErrorStatus status); @@ -115,6 +118,8 @@ class CdmServiceTests : public testing::Test void getSupportedKeySystemVersionShouldFail(); void supportsServerCertificateReturnTrue(); void supportsServerCertificateReturnFalse(); + void getSupportedRobustnessLevelsShouldSucceed(); + void getSupportedRobustnessLevelsShouldFail(); private: std::shared_ptr> m_mediaKeysFactoryMock; diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp index 4cd1f95d1..838928736 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp @@ -322,6 +322,26 @@ TEST_F(MediaPipelineServiceTests, shouldGetPosition) getPositionShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToGetDurationForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetDuration) +{ + initSession(); + mediaPipelineWillFailToGetDuration(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldGetDuration) +{ + initSession(); + mediaPipelineWillGetDuration(); + getDurationShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldFailToSetImmediateOutputForNotExistingSession) { createMediaPipelineShouldSuccess(); @@ -362,6 +382,46 @@ TEST_F(MediaPipelineServiceTests, shouldGetImmediateOutput) getImmediateOutputShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToSetReportDecodeErrorsForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + setReportDecodeErrorsShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetQueuedFramesForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + getQueuedFramesShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToSetReportDecodeErrors) +{ + initSession(); + mediaPipelineWillFailToSetReportDecodeErrors(); + setReportDecodeErrorsShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetQueuedFrames) +{ + initSession(); + mediaPipelineWillFailToGetQueuedFrames(); + getQueuedFramesShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldSetReportDecodeErrors) +{ + initSession(); + mediaPipelineWillSetReportDecodeErrors(); + setReportDecodeErrorsShouldSucceed(); +} + +TEST_F(MediaPipelineServiceTests, shouldGetQueuedFrames) +{ + initSession(); + mediaPipelineWillGetQueuedFrames(); + getQueuedFramesShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldFailToGetStatsForNotExistingSession) { createMediaPipelineShouldSuccess(); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp index 9b9e441dd..0c43860d9 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -69,6 +69,8 @@ const std::string kTextTrackIdentifier{"TextTrackIdentifier"}; constexpr uint32_t kBufferingLimit{4324}; constexpr bool kUseBuffering{true}; constexpr uint64_t kStopPosition{23412}; +constexpr bool kIsLive{false}; +constexpr uint32_t kQueuedFrames{123}; } // namespace namespace firebolt::rialto @@ -98,12 +100,12 @@ MediaPipelineServiceTests::MediaPipelineServiceTests() void MediaPipelineServiceTests::mediaPipelineWillLoad() { - EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl, kIsLive)).WillOnce(Return(true)); } void MediaPipelineServiceTests::mediaPipelineWillFailToLoad() { - EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineMock, load(kType, kMimeType, kUrl, kIsLive)).WillOnce(Return(false)); } void MediaPipelineServiceTests::mediaPipelineWillAttachSource() @@ -138,12 +140,12 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToAllSourcesAttached() void MediaPipelineServiceTests::mediaPipelineWillPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(true)); } void MediaPipelineServiceTests::mediaPipelineWillFailToPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(false)); } void MediaPipelineServiceTests::mediaPipelineWillPause() @@ -222,6 +224,22 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToGetPosition() EXPECT_CALL(m_mediaPipelineMock, getPosition(_)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillSetImmediateOutput() { EXPECT_CALL(m_mediaPipelineMock, setImmediateOutput(_, _)).WillOnce(Return(true)); @@ -242,6 +260,26 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToGetImmediateOutput() EXPECT_CALL(m_mediaPipelineMock, getImmediateOutput(_, _)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillSetReportDecodeErrors() +{ + EXPECT_CALL(m_mediaPipelineMock, setReportDecodeErrors(_, _)).WillOnce(Return(true)); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToSetReportDecodeErrors() +{ + EXPECT_CALL(m_mediaPipelineMock, setReportDecodeErrors(_, _)).WillOnce(Return(false)); +} + +void MediaPipelineServiceTests::mediaPipelineWillGetQueuedFrames() +{ + EXPECT_CALL(m_mediaPipelineMock, getQueuedFrames(_, _)).WillOnce(DoAll(SetArgReferee<1>(123), Return(true))); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToGetQueuedFrames() +{ + EXPECT_CALL(m_mediaPipelineMock, getQueuedFrames(_, _)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillGetStats() { EXPECT_CALL(m_mediaPipelineMock, getStats(_, _, _)) @@ -580,12 +618,12 @@ void MediaPipelineServiceTests::destroySessionShouldFail() void MediaPipelineServiceTests::loadShouldSucceed() { - EXPECT_TRUE(m_sut->load(kSessionId, kType, kMimeType, kUrl)); + EXPECT_TRUE(m_sut->load(kSessionId, kType, kMimeType, kUrl, kIsLive)); } void MediaPipelineServiceTests::loadShouldFail() { - EXPECT_FALSE(m_sut->load(kSessionId, kType, kMimeType, kUrl)); + EXPECT_FALSE(m_sut->load(kSessionId, kType, kMimeType, kUrl, kIsLive)); } void MediaPipelineServiceTests::attachSourceShouldSucceed() @@ -624,12 +662,14 @@ void MediaPipelineServiceTests::allSourcesAttachedShouldFail() void MediaPipelineServiceTests::playShouldSucceed() { - EXPECT_TRUE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_TRUE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::playShouldFail() { - EXPECT_FALSE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_FALSE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::pauseShouldSucceed() @@ -705,6 +745,19 @@ void MediaPipelineServiceTests::getPositionShouldFail() EXPECT_FALSE(m_sut->getPosition(kSessionId, targetPosition)); } +void MediaPipelineServiceTests::getDurationShouldSucceed() +{ + std::int64_t targetDuration{}; + EXPECT_TRUE(m_sut->getDuration(kSessionId, targetDuration)); + EXPECT_EQ(targetDuration, kDuration); +} + +void MediaPipelineServiceTests::getDurationShouldFail() +{ + std::int64_t targetDuration{}; + EXPECT_FALSE(m_sut->getDuration(kSessionId, targetDuration)); +} + void MediaPipelineServiceTests::getStatsShouldSucceed() { std::uint64_t renderedFrames; @@ -744,6 +797,29 @@ void MediaPipelineServiceTests::getImmediateOutputShouldFail() EXPECT_FALSE(m_sut->getImmediateOutput(kSessionId, kSourceId, immOp)); } +void MediaPipelineServiceTests::setReportDecodeErrorsShouldSucceed() +{ + EXPECT_TRUE(m_sut->setReportDecodeErrors(kSessionId, kSourceId, true)); +} + +void MediaPipelineServiceTests::setReportDecodeErrorsShouldFail() +{ + EXPECT_FALSE(m_sut->setReportDecodeErrors(kSessionId, kSourceId, true)); +} + +void MediaPipelineServiceTests::getQueuedFramesShouldSucceed() +{ + uint32_t queuedFr; + EXPECT_TRUE(m_sut->getQueuedFrames(kSessionId, kSourceId, queuedFr)); + EXPECT_EQ(queuedFr, kQueuedFrames); +} + +void MediaPipelineServiceTests::getQueuedFramesShouldFail() +{ + uint32_t queuedFr; + EXPECT_FALSE(m_sut->getQueuedFrames(kSessionId, kSourceId, queuedFr)); +} + void MediaPipelineServiceTests::getSupportedMimeTypesSucceed() { MediaSourceType type = MediaSourceType::VIDEO; diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h index ead87a642..d76ca55a3 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h @@ -64,10 +64,16 @@ class MediaPipelineServiceTests : public testing::Test void mediaPipelineWillFailToHaveData(); void mediaPipelineWillGetPosition(); void mediaPipelineWillFailToGetPosition(); + void mediaPipelineWillGetDuration(); + void mediaPipelineWillFailToGetDuration(); void mediaPipelineWillSetImmediateOutput(); void mediaPipelineWillFailToSetImmediateOutput(); + void mediaPipelineWillSetReportDecodeErrors(); + void mediaPipelineWillFailToSetReportDecodeErrors(); void mediaPipelineWillGetImmediateOutput(); void mediaPipelineWillFailToGetImmediateOutput(); + void mediaPipelineWillGetQueuedFrames(); + void mediaPipelineWillFailToGetQueuedFrames(); void mediaPipelineWillGetStats(); void mediaPipelineWillFailToGetStats(); void mediaPipelineWillRenderFrame(); @@ -156,10 +162,16 @@ class MediaPipelineServiceTests : public testing::Test void haveDataShouldFail(); void getPositionShouldSucceed(); void getPositionShouldFail(); + void getDurationShouldSucceed(); + void getDurationShouldFail(); + void setReportDecodeErrorsShouldSucceed(); + void setReportDecodeErrorsShouldFail(); void setImmediateOutputShouldSucceed(); void setImmediateOutputShouldFail(); void getImmediateOutputShouldSucceed(); void getImmediateOutputShouldFail(); + void getQueuedFramesShouldSucceed(); + void getQueuedFramesShouldFail(); void getStatsShouldSucceed(); void getStatsShouldFail(); void getSupportedMimeTypesSucceed(); diff --git a/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h b/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h index 4b500d482..deba36825 100644 --- a/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h +++ b/tests/unittests/serverManager/mocks/SessionServerAppFactoryMock.h @@ -33,12 +33,12 @@ class SessionServerAppFactoryMock : public ISessionServerAppFactory SessionServerAppFactoryMock() = default; virtual ~SessionServerAppFactoryMock() = default; - MOCK_METHOD(std::unique_ptr, create, + MOCK_METHOD(std::shared_ptr, create, (const std::string &appId, const firebolt::rialto::common::SessionServerState &initialState, const firebolt::rialto::common::AppConfig &appConfig, SessionServerAppManager &sessionServerAppManager, std::unique_ptr &&namedSocket), (const, override)); - MOCK_METHOD(std::unique_ptr, create, + MOCK_METHOD(std::shared_ptr, create, (SessionServerAppManager & sessionServerAppManager, std::unique_ptr &&namedSocket), (const, override)); diff --git a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp index 4289aa15b..70d6509c8 100644 --- a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp +++ b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp @@ -285,3 +285,22 @@ TEST_F(HealthcheckServiceTests, WillFailToFailPingWithWrongId) // There should be no error indication here. triggerOnPingFailed(pingId + 1); } + +TEST_F(HealthcheckServiceTests, WillSkipRestartingServerWhenAcksAreReceivedLater) +{ + int pingId{-1}; + timerWillBeCreated(); + createSut(); + pingWillBeSent(pingId); + triggerPingTimeout(); + const int firstPingId{pingId}; + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); + triggerOnAckReceived(kServerId, firstPingId, kSuccess); + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); +} diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp index 3998a8a5e..83994a266 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.cpp @@ -81,15 +81,13 @@ MATCHER_P(SmartPtrMatcher, expectedPtr, "") SessionServerAppManagerTests::SessionServerAppManagerTests() : m_controller{std::make_unique>()}, m_stateObserver{std::make_shared>()}, - m_sessionServerApp{std::make_unique>()}, + m_sessionServerAppMock{std::make_shared>()}, m_sessionServerAppFactory{ std::make_unique>()}, m_healthcheckServiceFactory{ std::make_unique>()}, m_healthcheckService{std::make_unique>()}, m_controllerMock{dynamic_cast &>(*m_controller)}, - m_sessionServerAppMock{ - dynamic_cast &>(*m_sessionServerApp)}, m_sessionServerAppFactoryMock{dynamic_cast &>( *m_sessionServerAppFactory)}, m_healthcheckServiceFactoryMock{ @@ -119,16 +117,16 @@ void SessionServerAppManagerTests::sessionServerLaunchWillFail(const firebolt::r EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(false)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerLaunchWillFail() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(false)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerConnectWillFail(const firebolt::rialto::common::SessionServerState &state) @@ -136,13 +134,13 @@ void SessionServerAppManagerTests::sessionServerConnectWillFail(const firebolt:: EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); } @@ -150,19 +148,23 @@ void SessionServerAppManagerTests::preloadedSessionServerConnectWillFail() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); } void SessionServerAppManagerTests::sessionServerChangeStateWillFail(const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, setExpectedState(state)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, setExpectedState(state)); + if (firebolt::rialto::common::SessionServerState::NOT_RUNNING == state) + { + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); + } EXPECT_CALL(m_controllerMock, performSetState(kServerId, state)).WillOnce(Return(false)); } @@ -171,11 +173,11 @@ void SessionServerAppManagerTests::sessionServerWillLaunch(const firebolt::rialt EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(kAppName, state, kAppConfig, _, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(true)); } @@ -183,62 +185,60 @@ void SessionServerAppManagerTests::preloadedSessionServerWillLaunch() { EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, SmartPtrMatcher(&m_namedSocketMock))) - .WillOnce(Return(ByMove(std::move(m_sessionServerApp)))); - EXPECT_CALL(m_sessionServerAppMock, launch()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); + .WillOnce(Return(m_sessionServerAppMock)); + EXPECT_CALL(*m_sessionServerAppMock, launch()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getServerId()).WillRepeatedly(Return(kServerId)); + EXPECT_CALL(*m_sessionServerAppMock, getAppManagementSocketName()).WillOnce(Return(kAppMgmtSocket)); EXPECT_CALL(m_controllerMock, createClient(kServerId, kAppMgmtSocket)).WillOnce(Return(true)); } void SessionServerAppManagerTests::preloadedSessionServerWillFailToConfigure( const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerWillBeConfigured( const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, configure(kAppName, state, kAppConfig)).WillOnce(Return(true)); } void SessionServerAppManagerTests::preloadedSessionServerWillCloseWithError() { - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kEmptyAppName)).RetiresOnSaturation(); + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kEmptyAppName)).RetiresOnSaturation(); EXPECT_CALL(m_controllerMock, removeClient(kServerId)); - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillChangeState(const firebolt::rialto::common::SessionServerState &state) { - EXPECT_CALL(m_sessionServerAppMock, setExpectedState(state)); + EXPECT_CALL(*m_sessionServerAppMock, setExpectedState(state)); EXPECT_CALL(m_controllerMock, performSetState(kServerId, state)).WillOnce(Return(true)); } void SessionServerAppManagerTests::sessionServerWillChangeStateToUninitialized() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -250,9 +250,9 @@ void SessionServerAppManagerTests::sessionServerWillChangeStateToUninitialized() void SessionServerAppManagerTests::preloadedSessionServerWillChangeStateToUninitialized() { - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); } void SessionServerAppManagerTests::sessionServerWillChangeStateToInactive() @@ -262,22 +262,20 @@ void SessionServerAppManagerTests::sessionServerWillChangeStateToInactive() void SessionServerAppManagerTests::preloadedSessionServerWillSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -285,48 +283,46 @@ void SessionServerAppManagerTests::preloadedSessionServerWillSetConfiguration() kSocketOwner, kSocketGroup, kEmptyAppName)) .WillOnce(Return(true)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::preloadedSessionServerWillSetConfigurationWithFd() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + m_secondSessionServerAppMock = std::make_shared>(); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, isConnected()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillRepeatedly(ReturnRef(kEmptyAppName)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketFd, kClientDisplayName, MaxResourceMatcher(kMaxSessions, kMaxWebAudioPlayers), kEmptyAppName)) .WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillFailToSetConfigurationWithFd() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(true)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketFd()).WillOnce(Return(kSessionServerSocketFd)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketFd, kClientDisplayName, @@ -337,19 +333,19 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetConfigurationWithFd void SessionServerAppManagerTests::sessionServerWillFailToSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, cancelStartupTimer()); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, cancelStartupTimer()); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -361,17 +357,17 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetConfiguration() void SessionServerAppManagerTests::preloadedSessionServerWillFailToSetConfiguration() { - EXPECT_CALL(m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); - EXPECT_CALL(m_sessionServerAppMock, getInitialState()) + EXPECT_CALL(*m_sessionServerAppMock, isNamedSocketInitialized()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, getInitialState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::INACTIVE)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); - EXPECT_CALL(m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); - EXPECT_CALL(m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillRepeatedly(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, getAppName()).WillOnce(ReturnRef(kAppName)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketPermissions()).WillOnce(Return(kSocketPermissions)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketOwner()).WillOnce(Return(kSocketOwner)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketGroup()).WillOnce(Return(kSocketGroup)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxPlaybackSessions()).WillOnce(Return(kMaxSessions)); + EXPECT_CALL(*m_sessionServerAppMock, getMaxWebAudioPlayers()).WillOnce(Return(kMaxWebAudioPlayers)); EXPECT_CALL(m_controllerMock, performSetConfiguration(kServerId, firebolt::rialto::common::SessionServerState::INACTIVE, kSessionServerSocketName, kClientDisplayName, @@ -392,7 +388,7 @@ void SessionServerAppManagerTests::sessionServerWillFailToSetLogLevels() void SessionServerAppManagerTests::clientWillBeRemoved() { - EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); EXPECT_CALL(m_controllerMock, removeClient(kServerId)); } @@ -404,12 +400,12 @@ void SessionServerAppManagerTests::sessionServerWillIndicateStateChange( void SessionServerAppManagerTests::sessionServerWillKillRunningApplication() { - EXPECT_CALL(m_sessionServerAppMock, kill()); + EXPECT_CALL(*m_sessionServerAppMock, kill()); } void SessionServerAppManagerTests::sessionServerWontBePreloaded() { - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(false)); } void SessionServerAppManagerTests::healthcheckServiceWillHandleAck(bool success) @@ -431,27 +427,26 @@ void SessionServerAppManagerTests::pingSendToRunningAppsWillFail() void SessionServerAppManagerTests::newSessionServerWillBeLaunched() { - m_secondSessionServerApp = std::make_unique>(); - auto &secondSessionServerAppMock{ - dynamic_cast &>(*m_secondSessionServerApp)}; + m_secondSessionServerAppMock = std::make_shared>(); EXPECT_CALL(m_namedSocketFactoryMock, createNamedSocket()).WillOnce(Return(ByMove(std::move(m_namedSocket)))); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); - EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(ByMove(std::move(m_secondSessionServerApp)))); - EXPECT_CALL(secondSessionServerAppMock, launch()).WillOnce(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillOnce(Return(true)); + EXPECT_CALL(m_sessionServerAppFactoryMock, create(_, _)).WillOnce(Return(m_secondSessionServerAppMock)); + EXPECT_CALL(*m_secondSessionServerAppMock, launch()).WillOnce(Return(false)); } void SessionServerAppManagerTests::sessionServerWillReturnAppSocketName(const std::string &socketName) { - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(socketName)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(socketName)); } void SessionServerAppManagerTests::sessionServerWillBeRestarted(const firebolt::rialto::common::SessionServerState &state) { m_namedSocket = std::make_unique>(); - EXPECT_CALL(m_sessionServerAppMock, getExpectedState()).WillOnce(Return(state)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getExpectedState()).WillOnce(Return(state)); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); sessionServerWillKillRunningApplication(); sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::NOT_RUNNING); clientWillBeRemoved(); @@ -468,17 +463,19 @@ void SessionServerAppManagerTests::sessionServerWillBeRestarted(const firebolt:: void SessionServerAppManagerTests::sessionServerWillRestartWillBeSkipped() { - EXPECT_CALL(m_sessionServerAppMock, getExpectedState()) + EXPECT_CALL(*m_sessionServerAppMock, getExpectedState()) .WillOnce(Return(firebolt::rialto::common::SessionServerState::UNINITIALIZED)); - EXPECT_CALL(m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); - EXPECT_CALL(m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); - EXPECT_CALL(m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); + EXPECT_CALL(*m_sessionServerAppMock, getSessionManagementSocketName()).WillOnce(Return(kSessionServerSocketName)); + EXPECT_CALL(*m_sessionServerAppMock, getClientDisplayName()).WillOnce(Return(kClientDisplayName)); + EXPECT_CALL(*m_sessionServerAppMock, releaseNamedSocketRef()).WillOnce(ReturnRef(m_namedSocket)); } void SessionServerAppManagerTests::sessionServerWillHandleServerStartupTimeout() { sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::ERROR); - EXPECT_CALL(m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(*m_sessionServerAppMock, isPreloaded()).WillRepeatedly(Return(false)); + EXPECT_CALL(m_healthcheckServiceMock, onServerRemoved(kServerId)).RetiresOnSaturation(); sessionServerWillKillRunningApplication(); sessionServerWillIndicateStateChange(firebolt::rialto::common::SessionServerState::NOT_RUNNING); clientWillBeRemoved(); diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h index 1bac26634..5dfa7f42a 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppManagerTestsFixture.h @@ -92,13 +92,12 @@ class SessionServerAppManagerTests : public testing::Test private: std::unique_ptr m_controller; std::shared_ptr> m_stateObserver; - std::unique_ptr m_sessionServerApp; - std::unique_ptr m_secondSessionServerApp; + std::shared_ptr> m_sessionServerAppMock; + std::shared_ptr> m_secondSessionServerAppMock; std::unique_ptr m_sessionServerAppFactory; std::unique_ptr m_healthcheckServiceFactory; std::unique_ptr m_healthcheckService; StrictMock &m_controllerMock; - StrictMock &m_sessionServerAppMock; StrictMock &m_sessionServerAppFactoryMock; StrictMock &m_healthcheckServiceFactoryMock; StrictMock &m_healthcheckServiceMock; diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp index 0d5a1c653..fd275bbbf 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.cpp @@ -56,7 +56,7 @@ using testing::StrictMock; void SessionServerAppTests::createPreloadedAppSut() { - m_sut = std::make_unique(std::move(m_linuxWrapper), + m_sut = std::make_shared(std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, kEnvironmentVariables, kSessionServerPath, @@ -79,7 +79,7 @@ void SessionServerAppTests::createPreloadedAppSut() void SessionServerAppTests::createAppSut(const firebolt::rialto::common::AppConfig &appConfig) { EXPECT_CALL(m_namedSocketMock, bind(_)).WillOnce(Return(true)); - m_sut = std::make_unique(kAppName, kInitialState, appConfig, + m_sut = std::make_shared(kAppName, kInitialState, appConfig, std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, @@ -104,7 +104,7 @@ void SessionServerAppTests::createAppSut(const firebolt::rialto::common::AppConf void SessionServerAppTests::createAppSutWithDisabledTimer(const firebolt::rialto::common::AppConfig &appConfig) { EXPECT_CALL(m_namedSocketMock, bind(_)).WillOnce(Return(true)); - m_sut = std::make_unique(kAppName, kInitialState, appConfig, + m_sut = std::make_shared(kAppName, kInitialState, appConfig, std::move(m_linuxWrapper), m_timerFactoryMock, m_sessionServerAppManagerMock, @@ -177,7 +177,7 @@ void SessionServerAppTests::willKillAppOnDestruction() const EXPECT_CALL(*killTimer, cancel()); EXPECT_CALL(*m_timerFactoryMock, createTimer(kKillTimeout, _, firebolt::rialto::common::TimerType::ONE_SHOT)) .WillOnce(DoAll(InvokeArgument<1>(), Return(ByMove(std::move(killTimer))))); - EXPECT_CALL(m_linuxWrapperMock, waitpid(-1, nullptr, 0)) + EXPECT_CALL(m_linuxWrapperMock, waitpid(kPid, nullptr, 0)) .WillOnce(Return(-1)); // -1 here as pid, because we invoked timer with kill earlier. EXPECT_CALL(m_linuxWrapperMock, close(kSocketPair[0])).WillOnce(Return(0)); } diff --git a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h index 00a5ae59b..30d15a0ea 100644 --- a/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h +++ b/tests/unittests/serverManager/unittests/common/SessionServerAppTestsFixture.h @@ -70,7 +70,7 @@ class SessionServerAppTests : public testing::Test testing::StrictMock &m_namedSocketMock{*m_namedSocket}; protected: - std::unique_ptr m_sut; + std::shared_ptr m_sut; }; #endif // SESSION_SERVER_APP_TESTS_FIXTURE_H_ diff --git a/wrappers/include/GlibWrapper.h b/wrappers/include/GlibWrapper.h index 2e4f86c3f..63522c419 100644 --- a/wrappers/include/GlibWrapper.h +++ b/wrappers/include/GlibWrapper.h @@ -76,6 +76,8 @@ class GlibWrapper : public IGlibWrapper gpointer gTypeClassRef(GType type) override { return g_type_class_ref(type); } + void gTypeClassUnref(gpointer g_class) override { g_type_class_unref(g_class); } + GType gTypeFromName(const gchar *name) override { return g_type_from_name(name); } GFlagsValue *gFlagsGetValueByNick(GFlagsClass *flags_class, const gchar *nick) override @@ -137,6 +139,15 @@ class GlibWrapper : public IGlibWrapper } GValue *gValueInit(GValue *value, GType type) const override { return g_value_init(value, type); } + + void gThreadPoolStopUnusedThreads() const override { g_thread_pool_stop_unused_threads(); } + + void gThreadPoolSetMaxUnusedThreads(gint maxThreads) const override + { + g_thread_pool_set_max_unused_threads(maxThreads); + } + + void gThreadPoolSetMaxIdleTime(guint interval) const override { g_thread_pool_set_max_idle_time(interval); } }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 055484c43..83df1926d 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -101,6 +101,11 @@ class GstWrapper : public IGstWrapper return gst_element_factory_make(factoryname, name); } + GType gstElementFactoryGetElementType(GstElementFactory *factory) override + { + return gst_element_factory_get_element_type(factory); + } + GstElement *gstBinGetByName(GstBin *bin, const gchar *name) override { return gst_bin_get_by_name(bin, name); } gpointer gstObjectRef(gpointer object) override { return gst_object_ref(object); } @@ -188,6 +193,11 @@ class GstWrapper : public IGstWrapper return gst_element_query_position(element, format, cur); } + gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) override + { + return gst_element_query_duration(element, format, duration); + } + GstPad *gstGhostPadNew(const gchar *name, GstPad *target) override { return gst_ghost_pad_new(name, target); } void gstPadSetQueryFunction(GstPad *pad, GstPadQueryFunction query) override @@ -508,6 +518,11 @@ class GstWrapper : public IGstWrapper return gst_message_new_warning(src, error, debug); } + GstMessage *gstMessageNewApplication(GstObject *src, GstStructure *structure) const override + { + return gst_message_new_application(src, structure); + } + void gstMessageParseWarning(GstMessage *message, GError **gerror, gchar **debug) const override { gst_message_parse_warning(message, gerror, debug); @@ -595,6 +610,30 @@ class GstWrapper : public IGstWrapper { gst_base_parse_set_pts_interpolation(parse, ptsInterpolate); } + + GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) override + { + return gst_element_get_state(element, state, pending, timeout); + } + + GstPad *gstPadGetPeer(GstPad *pad) override { return gst_pad_get_peer(pad); } + + gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_unlink(srcpad, sinkpad); } + + GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_link(srcpad, sinkpad); } + + gboolean gstBinRemove(GstBin *bin, GstElement *element) override { return gst_bin_remove(bin, element); } + + GstObject *gstPadGetParent(GstPad *pad) override { return gst_pad_get_parent(pad); } + + gulong gstPadAddProbe(GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) override + { + return gst_pad_add_probe(pad, mask, callback, userData, destroyData); + } + + void gstPadRemoveProbe(GstPad *pad, gulong id) override { gst_pad_remove_probe(pad, id); } }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/include/OcdmSession.h b/wrappers/include/OcdmSession.h index 51841f701..928f6140c 100644 --- a/wrappers/include/OcdmSession.h +++ b/wrappers/include/OcdmSession.h @@ -94,6 +94,7 @@ class OcdmSession : public IOcdmSession private: using OcdmGstSessionDecryptExFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstBuffer *, const uint32_t, GstBuffer *, GstBuffer *, uint32_t, GstCaps *); + using OcdmGstSessionDecryptBufferOnceFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstCaps *); /** * @brief The System handle. */ @@ -115,7 +116,7 @@ class OcdmSession : public IOcdmSession struct OpenCDMSession *m_session; static OcdmGstSessionDecryptExFn m_ocdmGstSessionDecryptEx; - + static OcdmGstSessionDecryptBufferOnceFn m_ocdmGstSessionDecryptBufferOnce; /** * @brief Requests the processing of the challenge data. * diff --git a/wrappers/include/OcdmSystem.h b/wrappers/include/OcdmSystem.h index 48a9c2a1e..acf6e5682 100644 --- a/wrappers/include/OcdmSystem.h +++ b/wrappers/include/OcdmSystem.h @@ -63,6 +63,7 @@ class OcdmSystem : public IOcdmSystem MediaKeyErrorStatus getMetricSystemData(uint32_t &bufferLength, std::vector &buffer) override; std::unique_ptr createSession(IOcdmSessionClient *client) const override; bool supportsServerCertificate() const override; + bool getSupportedRobustnessLevels(std::vector &robustnessLevels) override; private: /** diff --git a/wrappers/interface/IGlibWrapper.h b/wrappers/interface/IGlibWrapper.h index 2c3ef1fcb..daf8bdc67 100644 --- a/wrappers/interface/IGlibWrapper.h +++ b/wrappers/interface/IGlibWrapper.h @@ -112,6 +112,13 @@ class IGlibWrapper */ virtual gpointer gTypeClassRef(GType type) = 0; + /** + * @brief Decrements the reference count of the class structure being passed in. + * + * @param[in] g_class : Pointer to the class structure to decrement. + */ + virtual void gTypeClassUnref(gpointer g_class) = 0; + /** * @brief Gets the type from the name. * @@ -323,6 +330,29 @@ class IGlibWrapper * @retval The GValue structure that has been passed in. */ virtual GValue *gValueInit(GValue *value, GType type) const = 0; + + /** + * @brief Stops all currently unused threads. This does not change the maximal number of unused threads. + */ + virtual void gThreadPoolStopUnusedThreads() const = 0; + + /** + * @brief Sets the maximal number of unused threads to max_threads. If max_threads is -1, no limit is imposed on the + * number of unused threads. + * + * @param[in] maxThreads : Maximal number of unused threads. + * + */ + virtual void gThreadPoolSetMaxUnusedThreads(gint maxThreads) const = 0; + + /** + * @brief This function will set the maximum interval that a thread waiting in the pool for new tasks can be idle + * for before being stopped. + * + * @param[in] interval : The maximum interval (in milliseconds) a thread can be idle. + * + */ + virtual void gThreadPoolSetMaxIdleTime(guint interval) const = 0; }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index 0eca162f7..b8123b110 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -147,6 +147,15 @@ class IGstWrapper */ virtual GstElement *gstElementFactoryMake(const gchar *factoryname, const gchar *name) = 0; + /** + * @brief Get the element type returned by the requested factory. + * + * @param[in] factory : Factory + * + * @retval GType that the factory produces + */ + virtual GType gstElementFactoryGetElementType(GstElementFactory *factory) = 0; + /** * @brief Increment reference count on object. * @@ -412,6 +421,17 @@ class IGstWrapper */ virtual gboolean gstElementQueryPosition(GstElement *element, GstFormat format, gint64 *cur) = 0; + /** + * @brief Queries an element (usually top-level pipeline or playbin element) for the total stream duration in nanoseconds. + * + * @param[in] element : a GstElement to invoke the duration query on. + * @param[in] format : the GstFormat requested + * @param[out] duration : A location in which to store the total duration, or NULL. + * + * @retval TRUE on success, FALSE otherwise. + */ + virtual gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) = 0; + /** * @brief Create a new ghost pad. * @@ -1201,6 +1221,16 @@ class IGstWrapper */ virtual GstMessage *gstMessageNewWarning(GstObject *src, GError *error, const gchar *debug) const = 0; + /** + * @brief Creates a new application-typed message. + * + * @param[in] src : the origin of the message. + * @param[in] structure : the structure for the message (takes ownership). + * + * @retval New application message. + */ + virtual GstMessage *gstMessageNewApplication(GstObject *src, GstStructure *structure) const = 0; + /** * @brief Get the GError and error string from the message. * @@ -1402,6 +1432,91 @@ class IGstWrapper * @param ptsInterpolate : TRUE if parser should interpolate PTS timestamps */ virtual void gstBaseParseSetPtsInterpolation(GstBaseParse *parse, gboolean ptsInterpolate) const = 0; + + /** + * @brief Gets the state of the element (blocking version with timeout). + * + * @param[in] element : A GstElement to get state of. + * @param[out] state : A pointer to GstState to hold the state, or NULL. + * @param[out] pending : A pointer to GstState to hold the pending state, or NULL. + * @param[in] timeout : A GstClockTime to specify the timeout. + * + * @retval GST_STATE_CHANGE_SUCCESS if the element has no more pending state + * and the last state change succeeded, GST_STATE_CHANGE_ASYNC if the + * element is still performing a state change, or other values on failure. + */ + virtual GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) = 0; + + /** + * @brief Gets the peer pad of the given pad. + * + * @param[in] pad : A GstPad to get the peer of. + * + * @retval The peer GstPad. Unref after usage. NULL if pad has no peer. + */ + virtual GstPad *gstPadGetPeer(GstPad *pad) = 0; + + /** + * @brief Unlinks the source pad from the sink pad. + * + * @param[in] srcpad : The source GstPad to unlink. + * @param[in] sinkpad : The sink GstPad to unlink. + * + * @retval TRUE if the pads were unlinked, FALSE otherwise. + */ + virtual gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Links the source pad to the sink pad. + * + * @param[in] srcpad : The source GstPad to link. + * @param[in] sinkpad : The sink GstPad to link. + * + * @retval A result code indicating if the connection worked or what went wrong. + */ + virtual GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Removes an element from the bin. + * + * @param[in] bin : The bin to remove the element from. + * @param[in] element : The element to remove. + * + * @retval TRUE if the element could be removed, FALSE otherwise. + */ + virtual gboolean gstBinRemove(GstBin *bin, GstElement *element) = 0; + + /** + * @brief Gets the parent of a pad. + * + * @param[in] pad : The pad to get the parent of. + * + * @retval The parent GstObject. Unref after usage. NULL if pad has no parent. + */ + virtual GstObject *gstPadGetParent(GstPad *pad) = 0; + + /** + * @brief Adds probe to the pad. + * + * @param[in] pad : The GstPad to add the probe to. + * @param[in] mask : The probe mask. + * @param[in] callback : GstPadProbeCallback that will be called with notifications of the pad state. + * @param[in] userData : User data passed to the callback. + * @param[in] destroyData : GDestroyNotify for user_data. + * + * @retval An id or 0 if no probe is pending. The id can be used to remove the probe with gst_pad_remove_probe. + */ + virtual gulong gstPadAddProbe(GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) = 0; + + /** + * @brief Removes a probe from the pad. + * + * @param[in] pad : The GstPad to remove the probe from. + * @param[in] id : The probe id returned by gstPadAddProbe. + */ + virtual void gstPadRemoveProbe(GstPad *pad, gulong id) = 0; }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/interface/IOcdmSystem.h b/wrappers/interface/IOcdmSystem.h index 0e8977d6e..927001209 100644 --- a/wrappers/interface/IOcdmSystem.h +++ b/wrappers/interface/IOcdmSystem.h @@ -148,6 +148,15 @@ class IOcdmSystem */ virtual bool supportsServerCertificate() const = 0; + /** + * @brief Gets the robustness levels supported by the underlying CDM for this key system. + * + * @param[out] robustnessLevels : The supported robustness levels. + * + * @retval true if robustness levels were retrieved successfully + */ + virtual bool getSupportedRobustnessLevels(std::vector &robustnessLevels) = 0; + /** * @brief Get metrics for a DRM system * diff --git a/wrappers/source/OcdmSession.cpp b/wrappers/source/OcdmSession.cpp index a236826a9..f9260f30d 100644 --- a/wrappers/source/OcdmSession.cpp +++ b/wrappers/source/OcdmSession.cpp @@ -23,7 +23,7 @@ #include "opencdm/open_cdm_ext.h" #include #include - +#include namespace { LicenseType convertLicenseType(const firebolt::rialto::KeySessionType &sessionType) @@ -70,7 +70,7 @@ const char *convertInitDataType(const firebolt::rialto::InitDataType &initDataTy } } } -const firebolt::rialto::KeyStatus convertKeyStatus(const KeyStatus &ocdmKeyStatus) +firebolt::rialto::KeyStatus convertKeyStatus(const KeyStatus &ocdmKeyStatus) { switch (ocdmKeyStatus) { @@ -104,11 +104,19 @@ const firebolt::rialto::KeyStatus convertKeyStatus(const KeyStatus &ocdmKeyStatu } } } + +void *getOcdmLibraryHandle() +{ + // Keep the library handle for the lifetime of the process so the optional symbols remain valid. + static void *handle = dlopen("libocdm.so", RTLD_LAZY | RTLD_LOCAL); + return handle; +} } // namespace namespace firebolt::rialto::wrappers { OcdmSession::OcdmGstSessionDecryptExFn OcdmSession::m_ocdmGstSessionDecryptEx{nullptr}; +OcdmSession::OcdmGstSessionDecryptBufferOnceFn OcdmSession::m_ocdmGstSessionDecryptBufferOnce{nullptr}; OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient *client) : m_systemHandle(systemHandle), m_ocdmSessionClient(client), m_session(nullptr) @@ -121,6 +129,12 @@ OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient { m_ocdmGstSessionDecryptEx = (OcdmGstSessionDecryptExFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_ex"); + if (void *handle = getOcdmLibraryHandle()) + { + void *decryptBufferOnce = dlsym(handle, "opencdm_gstreamer_session_decrypt_buffer_once"); + m_ocdmGstSessionDecryptBufferOnce = + reinterpret_cast(decryptBufferOnce); + } }); } @@ -195,6 +209,59 @@ MediaKeyErrorStatus OcdmSession::decryptBuffer(GstBuffer *encrypted, GstCaps *ca return MediaKeyErrorStatus::FAIL; } + if (m_ocdmGstSessionDecryptBufferOnce) + { + // Extract key ID from the buffer's protection metadata + std::vector keyId; + GstProtectionMeta *pm = reinterpret_cast(gst_buffer_get_protection_meta(encrypted)); + if (pm) + { + const GValue *kidValue = gst_structure_get_value(pm->info, "kid"); + if (kidValue) + { + GstBuffer *kidBuf = gst_value_get_buffer(kidValue); + if (kidBuf) + { + GstMapInfo kidMap; + if (gst_buffer_map(kidBuf, &kidMap, GST_MAP_READ)) + { + keyId.assign(kidMap.data, kidMap.data + kidMap.size); + gst_buffer_unmap(kidBuf, &kidMap); + } + } + } + } + + // Pre-decrypt key status check: return OUTPUT_RESTRICTED immediately (no sleep) so + // the caller (MediaKeysServerInternal::decrypt) can retry from the GStreamer thread. + if (!keyId.empty()) + { + const ::KeyStatus preStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (preStatus == OutputRestricted || preStatus == OutputRestrictedHDCP22) + { + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + OpenCDMError result = m_ocdmGstSessionDecryptBufferOnce(m_session, encrypted, caps); + + // Post-decrypt status check: a failed decrypt during HDCP reauth may not carry a + // specific error code, so confirm via key status before signalling the caller to retry. + if (result != ERROR_NONE && !keyId.empty()) + { + const ::KeyStatus postStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (postStatus == OutputRestricted || postStatus == OutputRestrictedHDCP22) + { + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + return convertOpenCdmError(result); + } + + // Fallback: adapter without _once handles retries internally. OpenCDMError status = opencdm_gstreamer_session_decrypt_buffer(m_session, encrypted, caps); return convertOpenCdmError(status); } diff --git a/wrappers/source/OcdmSystem.cpp b/wrappers/source/OcdmSystem.cpp index ba7d15946..10f3c70bc 100644 --- a/wrappers/source/OcdmSystem.cpp +++ b/wrappers/source/OcdmSystem.cpp @@ -23,6 +23,9 @@ #include "opencdm/open_cdm_ext.h" #include +extern "C" OpenCDMError opencdm_system_supported_robustness(struct OpenCDMSystem *system, char ***robustness, + uint16_t *count); + namespace firebolt::rialto::wrappers { std::shared_ptr OcdmSystemFactory::createOcdmSystem(const std::string &keySystem) const @@ -118,6 +121,32 @@ bool OcdmSystem::supportsServerCertificate() const return OpenCDMBool::OPENCDM_BOOL_TRUE == opencdm_system_supports_server_certificate(m_systemHandle); } +bool OcdmSystem::getSupportedRobustnessLevels(std::vector &robustnessLevels) +{ + robustnessLevels.clear(); + char **buffer = nullptr; + uint16_t count = 0; + OpenCDMError status = opencdm_system_supported_robustness(m_systemHandle, &buffer, &count); + if (status == ERROR_NONE && buffer != nullptr && count > 0) + { + for (uint16_t i = 0; i < count; ++i) + { + if (buffer[i] != nullptr) + { + robustnessLevels.push_back(std::string(buffer[i])); + free(buffer[i]); + } + } + free(buffer); + return true; + } + if (buffer != nullptr) + { + free(buffer); + } + return false; +} + MediaKeyErrorStatus OcdmSystem::getMetricSystemData(uint32_t &bufferLength, std::vector &buffer) { OpenCDMError status = opencdm_get_metric_system_data(m_systemHandle, &bufferLength, buffer.data());