From dd8788f4d652f3b287abdabd4e9b87dba63985bf Mon Sep 17 00:00:00 2001 From: Koky2701 <90915184+Koky2701@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:37:37 +0200 Subject: [PATCH] Bringing back support for "queued-frames" and "report-decode-errors" properties (#485) Summary: 'report-decode-errors' and 'queued-frames' properties missing from RialtoMSEVideoSink Type: Feature Test Plan: UT/CT, Fullstack Jira: RDKEMW-12692 --- media/client/ipc/include/MediaPipelineIpc.h | 4 + .../client/ipc/interface/IMediaPipelineIpc.h | 24 ++++ media/client/ipc/source/MediaPipelineIpc.cpp | 67 +++++++++++ media/client/main/include/MediaPipeline.h | 4 + .../client/main/include/MediaPipelineProxy.h | 8 ++ media/client/main/source/MediaPipeline.cpp | 10 ++ media/public/include/IMediaPipeline.h | 24 ++++ media/server/gstplayer/CMakeLists.txt | 1 + .../gstplayer/include/GenericPlayerContext.h | 5 + .../gstplayer/include/GstGenericPlayer.h | 3 + .../include/IGstGenericPlayerPrivate.h | 7 ++ .../include/tasks/IGenericPlayerTaskFactory.h | 15 +++ .../tasks/generic/GenericPlayerTaskFactory.h | 4 + .../tasks/generic/SetReportDecodeErrors.h | 49 ++++++++ .../gstplayer/interface/IGstGenericPlayer.h | 19 ++++ .../gstplayer/source/GstGenericPlayer.cpp | 80 +++++++++++++ .../generic/GenericPlayerTaskFactory.cpp | 9 ++ .../tasks/generic/SetReportDecodeErrors.cpp | 52 +++++++++ .../source/tasks/generic/SetupElement.cpp | 5 +- .../ipc/include/MediaPipelineModuleService.h | 8 ++ .../ipc/source/MediaPipelineModuleService.cpp | 34 ++++++ .../include/MediaPipelineServerInternal.h | 28 +++++ .../source/MediaPipelineServerInternal.cpp | 57 ++++++++++ .../service/include/IMediaPipelineService.h | 2 + .../service/source/MediaPipelineService.cpp | 28 +++++ .../service/source/MediaPipelineService.h | 2 + proto/mediapipelinemodule.proto | 50 +++++++++ .../MediaPipelineProtoRequestMatchers.h | 14 +++ .../client/mocks/MediaPipelineModuleMock.h | 20 ++++ .../tests/base/MediaPipelineTestMethods.cpp | 28 +++++ .../tests/base/MediaPipelineTestMethods.h | 4 + .../client/tests/mse/PipelinePropertyTest.cpp | 10 ++ .../mediaPipeline/PipelinePropertyTest.cpp | 12 +- .../unittests/media/client/ipc/CMakeLists.txt | 2 + .../mediaPipelineIpc/GetQueuedFramesTest.cpp | 100 +++++++++++++++++ .../SetReportDecodeErrorsTest.cpp | 96 ++++++++++++++++ .../media/client/main/CMakeLists.txt | 2 + .../mediaPipeline/GetQueuedFramesTest.cpp | 63 +++++++++++ .../mediaPipeline/MediaPipelineProxyTest.cpp | 15 +++ .../SetReportDecodeErrorsTest.cpp | 58 ++++++++++ .../client/mocks/ipc/MediaPipelineIpcMock.h | 2 + .../main/MediaPipelineAndControlClientMock.h | 2 + .../media/server/gstplayer/CMakeLists.txt | 1 + .../GstGenericPlayerPrivateTest.cpp | 30 +++++ .../genericPlayer/GstGenericPlayerTest.cpp | 37 ++++++ .../common/GenericTasksTestsBase.cpp | 15 +++ .../common/GenericTasksTestsBase.h | 4 + .../common/GstGenericPlayerTestCommon.cpp | 14 +++ .../common/GstGenericPlayerTestCommon.h | 1 + .../GenericPlayerTaskFactoryTest.cpp | 8 ++ .../tasksTests/SetReportDecodeErrorsTest.cpp | 30 +++++ .../MediaPipelineModuleServiceTests.cpp | 32 ++++++ ...MediaPipelineModuleServiceTestsFixture.cpp | 73 ++++++++++++ .../MediaPipelineModuleServiceTestsFixture.h | 8 ++ .../MiscellaneousFunctionsTest.cpp | 106 ++++++++++++++++++ .../gstplayer/GenericPlayerTaskFactoryMock.h | 4 + .../mocks/gstplayer/GstGenericPlayerMock.h | 3 + .../gstplayer/GstGenericPlayerPrivateMock.h | 1 + .../main/MediaPipelineServerInternalMock.h | 2 + .../mocks/service/MediaPipelineServiceMock.h | 2 + .../MediaPipelineServiceTests.cpp | 40 +++++++ .../MediaPipelineServiceTestsFixture.cpp | 44 ++++++++ .../MediaPipelineServiceTestsFixture.h | 8 ++ 63 files changed, 1488 insertions(+), 2 deletions(-) create mode 100644 media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h create mode 100644 media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp create mode 100644 tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp create mode 100644 tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp create mode 100644 tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp create mode 100644 tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp create mode 100644 tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index ef5ea0ea6..5b8877688 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -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; diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index 59aea535b..54e6e5c1b 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -189,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. * diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index c2cee81eb..86d52f165 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -606,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()) diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index 6c91b8bfd..12cbb250a 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -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; diff --git a/media/client/main/include/MediaPipelineProxy.h b/media/client/main/include/MediaPipelineProxy.h index 80f019d95..0fbfa3376 100644 --- a/media/client/main/include/MediaPipelineProxy.h +++ b/media/client/main/include/MediaPipelineProxy.h @@ -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); diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index 49ccb3724..d555eecd8 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -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); diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index c1820f302..0f1c23ad2 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -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. * diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 6dcdb9f5f..448f36731 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -59,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 diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index 7f8c38dc1..d65cc12d5 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -156,6 +156,11 @@ struct GenericPlayerContext */ std::optional pendingImmediateOutputForVideo{}; + /** + * @brief Pending report decode errors for MediaSourceType::VIDEO + */ + std::optional pendingReportDecodeErrorsForVideo{}; + /** * @brief Pending low latency */ diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 01f846107..d10b18ed7 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -124,7 +124,9 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva 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; @@ -159,6 +161,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void scheduleAllSourcesAttached() override; bool setVideoSinkRectangle() override; bool setImmediateOutput() override; + bool setReportDecodeErrors() override; bool setShowVideoWindow() override; bool setLowLatency() override; bool setSync() override; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 0e423452d..71c38e53c 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -85,6 +85,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. * diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 227b0486f..7f06195c6 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -508,6 +508,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/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 14e05b9ab..30a81c547 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -124,6 +124,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/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/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index ceb961bf7..6664f4b66 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -214,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/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 85fcef5b3..91d3754b9 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -1178,6 +1178,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}; @@ -1931,6 +1966,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()) diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index be2f3d834..17138a99e 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -43,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" @@ -342,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/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/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index e2e7efd74..6cdf7ce22 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -261,6 +261,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(); + } } } @@ -366,7 +370,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/ipc/include/MediaPipelineModuleService.h b/media/server/ipc/include/MediaPipelineModuleService.h index 92e54e463..91787419a 100644 --- a/media/server/ipc/include/MediaPipelineModuleService.h +++ b/media/server/ipc/include/MediaPipelineModuleService.h @@ -90,6 +90,14 @@ class MediaPipelineModuleService : public IMediaPipelineModuleService 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/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 8976d1c20..ab7e46607 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -703,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/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index 6593e66da..7b7db524e 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -115,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; @@ -405,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. * diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index c72200f97..3aa8b4293 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -559,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:"); diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index 77a27d34a..a70476b19 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -55,6 +55,8 @@ class IMediaPipelineService 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, diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index f3d8136ab..717063564 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -282,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 06eaf320f..7e777c777 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -67,6 +67,8 @@ class MediaPipelineService : public IMediaPipelineService 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/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index 1655d2245..518e6ddc0 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -697,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. @@ -714,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. @@ -1111,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 @@ -1118,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 diff --git a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h index ac34ace4b..f8186d4c0 100644 --- a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h +++ b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h @@ -373,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/componenttests/client/mocks/MediaPipelineModuleMock.h b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h index 4960afae5..6988528f4 100644 --- a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h +++ b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h @@ -75,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)); @@ -241,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/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index 63658d247..281eb5bb4 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -1477,6 +1477,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), _, _)) diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index f73f23f9d..1944df118 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -131,6 +131,8 @@ class MediaPipelineTestMethods 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(); @@ -251,6 +253,8 @@ class MediaPipelineTestMethods 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(); diff --git a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp index 374253730..6a019c9c4 100644 --- a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp +++ b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp @@ -191,5 +191,15 @@ TEST_F(PipelinePropertyTest, setAndGetPipelineProperties) 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/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index e75140fb1..3b04393d2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -160,6 +160,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()), _)) @@ -630,7 +640,7 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) willStop(); stop(); - // Step 19: Destroy media session + // Step 18: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/unittests/media/client/ipc/CMakeLists.txt b/tests/unittests/media/client/ipc/CMakeLists.txt index ba71b08d3..8c4835390 100644 --- a/tests/unittests/media/client/ipc/CMakeLists.txt +++ b/tests/unittests/media/client/ipc/CMakeLists.txt @@ -38,6 +38,8 @@ add_gtests ( mediaPipelineIpc/GetDurationTest.cpp mediaPipelineIpc/SetImmediateOutputTest.cpp mediaPipelineIpc/GetImmediateOutputTest.cpp + mediaPipelineIpc/SetReportDecodeErrorsTest.cpp + mediaPipelineIpc/GetQueuedFramesTest.cpp mediaPipelineIpc/GetStatsTest.cpp mediaPipelineIpc/RenderFrameTest.cpp mediaPipelineIpc/GetVolumeTest.cpp 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/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/main/CMakeLists.txt b/tests/unittests/media/client/main/CMakeLists.txt index 573f5233c..8b0de5571 100644 --- a/tests/unittests/media/client/main/CMakeLists.txt +++ b/tests/unittests/media/client/main/CMakeLists.txt @@ -35,6 +35,8 @@ add_gtests ( mediaPipeline/GetDurationTest.cpp mediaPipeline/SetImmediateOutputTest.cpp mediaPipeline/GetImmediateOutputTest.cpp + mediaPipeline/SetReportDecodeErrorsTest.cpp + mediaPipeline/GetQueuedFramesTest.cpp mediaPipeline/GetStatsTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetVolumeTest.cpp 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/MediaPipelineProxyTest.cpp b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp index ec5e31896..563291b6b 100644 --- a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp @@ -173,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/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/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index b955fce2a..2d90eb922 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -48,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)); diff --git a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h index 54349ba06..c6fd26247 100644 --- a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h +++ b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h @@ -51,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)); diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index ea5b7b2cb..29bc23640 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -51,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 diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 511fe71c7..a28bbdbf6 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"}; @@ -416,6 +417,35 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetImmediateOutput) 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; }); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index 6096a5b3d..51d6333e5 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -438,6 +438,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}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index 83c9685ae..be29af091 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" @@ -3554,6 +3555,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 6fa75065c..d6ec81f74 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -288,6 +288,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(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp index 326c50f9f..861ba60bb 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp @@ -220,6 +220,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)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h index a3415bb7e..6b3c97c2d 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h @@ -126,6 +126,7 @@ 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); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index d1d1b67ec..ceaece581 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -55,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" @@ -360,6 +361,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/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/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp index 45a4ff170..4ff1da748 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp @@ -297,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(); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index 0df902b1d..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}; @@ -546,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(); @@ -1204,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; diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h index 19ebe07a0..7df32bedf 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h @@ -80,6 +80,10 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaPipelineServiceWillFailToSetImmediateOutput(); void mediaPipelineServiceWillGetImmediateOutput(); void mediaPipelineServiceWillFailToGetImmediateOutput(); + void mediaPipelineServiceWillSetReportDecodeErrors(); + void mediaPipelineServiceWillFailToSetReportDecodeErrors(); + void mediaPipelineServiceWillGetQueuedFrames(); + void mediaPipelineServiceWillFailToGetQueuedFrames(); void mediaPipelineServiceWillGetStats(); void mediaPipelineServiceWillFailToGetStats(); void mediaPipelineServiceWillRenderFrame(); @@ -159,6 +163,10 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendSetImmediateOutputRequestAndReceiveFail(); void sendGetImmediateOutputRequestAndReceiveResponse(); void sendGetImmediateOutputRequestAndReceiveFail(); + void sendSetReportDecodeErrorsRequestAndReceiveResponse(); + void sendSetReportDecodeErrorsRequestAndReceiveFail(); + void sendGetQueuedFramesRequestAndReceiveResponse(); + void sendGetQueuedFramesRequestAndReceiveFail(); void sendGetStatsRequestAndReceiveResponse(); void sendGetStatsRequestAndReceiveResponseWithoutStatsMatch(); void sendHaveDataRequestAndReceiveResponse(); diff --git a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp index 34b2b6747..f888936e8 100644 --- a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp @@ -366,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 */ diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 77fac21c3..ab093a63d 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -140,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/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index 1f1fd37de..e751de7f2 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -46,6 +46,9 @@ class GstGenericPlayerMock : public IGstGenericPlayer 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 48f6a33d4..75bfed786 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -40,6 +40,7 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate 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)); diff --git a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h index fccee93d9..8e7db9fe2 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -45,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)); diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index 3273b14a0..7c9179031 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -47,6 +47,8 @@ class MediaPipelineServiceMock : public IMediaPipelineService 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/mediaPipelineService/MediaPipelineServiceTests.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp index 56643a42c..838928736 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp @@ -382,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 ca43df569..0c43860d9 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -70,6 +70,7 @@ 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 @@ -259,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(_, _, _)) @@ -776,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 347d64216..d76ca55a3 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h @@ -68,8 +68,12 @@ class MediaPipelineServiceTests : public testing::Test void mediaPipelineWillFailToGetDuration(); void mediaPipelineWillSetImmediateOutput(); void mediaPipelineWillFailToSetImmediateOutput(); + void mediaPipelineWillSetReportDecodeErrors(); + void mediaPipelineWillFailToSetReportDecodeErrors(); void mediaPipelineWillGetImmediateOutput(); void mediaPipelineWillFailToGetImmediateOutput(); + void mediaPipelineWillGetQueuedFrames(); + void mediaPipelineWillFailToGetQueuedFrames(); void mediaPipelineWillGetStats(); void mediaPipelineWillFailToGetStats(); void mediaPipelineWillRenderFrame(); @@ -160,10 +164,14 @@ class MediaPipelineServiceTests : public testing::Test 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();