From 4d930b26289dd6b5d3e3dfa0b877a9a9d2d38e74 Mon Sep 17 00:00:00 2001 From: Adam Czynszak <92790185+aczs@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:29:29 +0000 Subject: [PATCH 01/21] Changed Wayland Display for TextTrack (#434) Summary: Change Wayland Display for TextTrack Type: Fix Test Plan: UT/ CT, Fullstack Jira: ENTDAI-2218 --- media/server/gstplayer/source/GstTextTrackSink.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/media/server/gstplayer/source/GstTextTrackSink.cpp b/media/server/gstplayer/source/GstTextTrackSink.cpp index b98c5b93f..16e137854 100644 --- a/media/server/gstplayer/source/GstTextTrackSink.cpp +++ b/media/server/gstplayer/source/GstTextTrackSink.cpp @@ -159,19 +159,12 @@ static void gst_rialto_text_track_sink_finalize(GObject *object) // NOLINT(build static gboolean gst_rialto_text_track_sink_start(GstBaseSink *sink) // NOLINT(build/function_format) { - const char *wayland_display = std::getenv("WAYLAND_DISPLAY"); - if (!wayland_display) - { - GST_ERROR_OBJECT(sink, "Failed to get WAYLAND_DISPLAY env variable"); - return false; - } - - std::string display{wayland_display}; + const std::string kDisplay{"westeros-asplayer-subtitles"}; GstRialtoTextTrackSink *self = GST_RIALTO_TEXT_TRACK_SINK(sink); try { self->priv->m_textTrackSession = - firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(display); + firebolt::rialto::server::ITextTrackSessionFactory::getFactory().createTextTrackSession(kDisplay); } catch (const std::exception &e) { From 6a01b68acced6bc28c92dbd267b1a036bcb0b09d Mon Sep 17 00:00:00 2001 From: Adam Czynszak <92790185+aczs@users.noreply.github.com> Date: Fri, 13 Feb 2026 09:08:23 +0000 Subject: [PATCH 02/21] Removed gstreamer interaction during RemoveSource (#445) Summary: Removed gstreamer interaction during RemoveSource Type: Fix Test Plan: UT/CT, Fullstack Jira: LLAMA-18057 --------- Co-authored-by: Marcin Wojciechowski --- media/server/gstplayer/CMakeLists.txt | 1 - .../gstplayer/include/GenericPlayerContext.h | 7 - .../gstplayer/include/GstGenericPlayer.h | 9 +- .../include/IGstGenericPlayerPrivate.h | 7 - .../include/tasks/IGenericPlayerTaskFactory.h | 13 -- .../include/tasks/generic/AttachSource.h | 1 - .../tasks/generic/GenericPlayerTaskFactory.h | 2 - .../include/tasks/generic/RemoveSource.h | 49 ----- .../gstplayer/interface/IGstGenericPlayer.h | 8 - .../gstplayer/source/GstGenericPlayer.cpp | 10 +- .../source/tasks/generic/AttachSource.cpp | 22 --- .../tasks/generic/CheckAudioUnderflow.cpp | 2 +- .../generic/GenericPlayerTaskFactory.cpp | 8 - .../source/tasks/generic/NeedData.cpp | 5 - .../source/tasks/generic/RemoveSource.cpp | 81 -------- .../source/MediaPipelineServerInternal.cpp | 1 - .../server/fixtures/MediaPipelineTest.cpp | 36 ---- .../server/fixtures/MediaPipelineTest.h | 2 - .../server/tests/CMakeLists.txt | 2 +- .../mediaPipeline/AudioOnlyPlaybackTest.cpp | 1 - .../mediaPipeline/AudioSourceSwitchTest.cpp | 1 - .../mediaPipeline/DualVideoPlaybackTest.cpp | 2 - .../mediaPipeline/EncryptedPlaybackTest.cpp | 1 - .../server/tests/mediaPipeline/FlushTest.cpp | 1 - .../mediaPipeline/HaveDataFailureTest.cpp | 1 - .../server/tests/mediaPipeline/MuteTest.cpp | 1 - .../NonFatalPlayerErrorUpdatesTest.cpp | 1 - .../mediaPipeline/PipelinePropertyTest.cpp | 2 - .../tests/mediaPipeline/PlaybackTest.cpp | 1 - .../mediaPipeline/PositionUpdatesTest.cpp | 3 - .../tests/mediaPipeline/QosUpdatesTest.cpp | 2 - .../tests/mediaPipeline/RenderFrameTest.cpp | 2 - .../tests/mediaPipeline/SetPositionTest.cpp | 2 - .../mediaPipeline/SetSourcePositionTest.cpp | 1 - .../mediaPipeline/SetVideoWindowTest.cpp | 1 - .../server/tests/mediaPipeline/SourceTest.cpp | 2 - ...ckTest.cpp => SwitchAudioPlaybackTest.cpp} | 177 ++++++++++-------- .../tests/mediaPipeline/UnderflowTest.cpp | 1 - .../server/tests/mediaPipeline/VolumeTest.cpp | 1 - .../tests/mediaPipeline/WriteSegmentsTest.cpp | 1 - .../media/server/gstplayer/CMakeLists.txt | 1 - .../GstGenericPlayerPrivateTest.cpp | 31 +-- .../genericPlayer/GstGenericPlayerTest.cpp | 10 - .../common/GenericTasksTestsBase.cpp | 67 ------- .../common/GenericTasksTestsBase.h | 12 -- .../tasksTests/AttachSourceTest.cpp | 18 -- .../GenericPlayerTaskFactoryTest.cpp | 8 - .../genericPlayer/tasksTests/NeedDataTest.cpp | 10 - .../tasksTests/RemoveSourceTest.cpp | 72 ------- .../server/main/mediaPipeline/SourceTest.cpp | 2 - .../gstplayer/GenericPlayerTaskFactoryMock.h | 4 - .../mocks/gstplayer/GstGenericPlayerMock.h | 1 - .../gstplayer/GstGenericPlayerPrivateMock.h | 1 - 53 files changed, 107 insertions(+), 601 deletions(-) delete mode 100644 media/server/gstplayer/include/tasks/generic/RemoveSource.h delete mode 100644 media/server/gstplayer/source/tasks/generic/RemoveSource.cpp rename tests/componenttests/server/tests/mediaPipeline/{RemoveAudioPlaybackTest.cpp => SwitchAudioPlaybackTest.cpp} (57%) delete mode 100644 tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 98a77ca4c..6756184da 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -49,7 +49,6 @@ add_library( source/tasks/generic/Play.cpp source/tasks/generic/ProcessAudioGap.cpp source/tasks/generic/ReadShmDataAndAttachSamples.cpp - source/tasks/generic/RemoveSource.cpp source/tasks/generic/RenderFrame.cpp source/tasks/generic/ReportPosition.cpp source/tasks/generic/SetBufferingLimit.cpp diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index a2f214535..29cf8eae1 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -206,13 +206,6 @@ struct GenericPlayerContext */ IDecryptionService *decryptionService{nullptr}; - /** - * @brief Flag used to check, if audio source has been recently removed - * - * Flag can be used only in worker thread - */ - bool audioSourceRemoved{false}; - /** * @brief Audio elements of gst pipeline. * diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 22fd800e0..a07f54f38 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -106,7 +106,6 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva virtual ~GstGenericPlayer(); void attachSource(const std::unique_ptr &mediaSource) override; - void removeSource(const MediaSourceType &mediaSourceType) override; void allSourcesAttached() override; void play() override; void pause() override; @@ -185,7 +184,6 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void addAutoAudioSinkChild(GObject *object) override; void removeAutoVideoSinkChild(GObject *object) override; void removeAutoAudioSinkChild(GObject *object) override; - void setPlaybinFlags(bool enableAudio = true) override; void pushSampleIfRequired(GstElement *source, const std::string &typeStr) override; bool reattachSource(const std::unique_ptr &source) override; bool hasSourceType(const MediaSourceType &mediaSourceType) const override; @@ -340,6 +338,13 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ void pushAdditionalSegmentIfRequired(GstElement *source); + /** + * @brief Sets the audio and video flags on the pipeline based on the input. + * + * @param[in] enableAudio : Whether to enable audio flags. + */ + void setPlaybinFlags(bool enableAudio = true); + private: /** * @brief The player context. diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 1848c8960..ac8ff8d15 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -271,13 +271,6 @@ class IGstGenericPlayerPrivate */ virtual GstElement *getSink(const MediaSourceType &mediaSourceType) const = 0; - /** - * @brief Sets the audio and video flags on the pipeline based on the input. - * - * @param[in] enableAudio : Whether to enable audio flags. - */ - virtual void setPlaybinFlags(bool enableAudio) = 0; - /** * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. * diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 527261652..1026de09a 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -176,19 +176,6 @@ class IGenericPlayerTaskFactory createReadShmDataAndAttachSamples(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader) const = 0; - /** - * @brief Creates a Remove Source task. - * - * @param[in] context : The GstPlayer context - * @param[in] player : The GstGenericPlayer instance - * @param[in] type : The media source type to remove - * - * @retval the new Remove Source task instance. - */ - virtual std::unique_ptr createRemoveSource(GenericPlayerContext &context, - IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const = 0; - /** * @brief Creates a ReportPosition task. * diff --git a/media/server/gstplayer/include/tasks/generic/AttachSource.h b/media/server/gstplayer/include/tasks/generic/AttachSource.h index 0f77545d4..f6d8144fe 100644 --- a/media/server/gstplayer/include/tasks/generic/AttachSource.h +++ b/media/server/gstplayer/include/tasks/generic/AttachSource.h @@ -47,7 +47,6 @@ class AttachSource : public IPlayerTask private: void addSource() const; - void reattachAudioSource() const; GenericPlayerContext &m_context; std::shared_ptr m_gstWrapper; diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 49ec0db1b..8a5c045ef 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -65,8 +65,6 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory std::unique_ptr createReadShmDataAndAttachSamples(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader) const override; - std::unique_ptr createRemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const override; std::unique_ptr createReportPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const override; std::unique_ptr createCheckAudioUnderflow(GenericPlayerContext &context, diff --git a/media/server/gstplayer/include/tasks/generic/RemoveSource.h b/media/server/gstplayer/include/tasks/generic/RemoveSource.h deleted file mode 100644 index 7d1923ff6..000000000 --- a/media/server/gstplayer/include/tasks/generic/RemoveSource.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ -#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ - -#include "GenericPlayerContext.h" -#include "IGstGenericPlayerClient.h" -#include "IGstGenericPlayerPrivate.h" -#include "IGstWrapper.h" -#include "IPlayerTask.h" -#include - -namespace firebolt::rialto::server::tasks::generic -{ -class RemoveSource : public IPlayerTask -{ -public: - RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type); - ~RemoveSource() override; - void execute() const override; - -private: - GenericPlayerContext &m_context; - IGstGenericPlayerPrivate &m_player; - IGstGenericPlayerClient *m_gstPlayerClient; - std::shared_ptr m_gstWrapper; - MediaSourceType m_type; -}; -} // namespace firebolt::rialto::server::tasks::generic - -#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_REMOVE_SOURCE_H_ diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index e01edd364..57be0d51d 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -88,14 +88,6 @@ class IGstGenericPlayer */ virtual void attachSource(const std::unique_ptr &mediaSource) = 0; - /** - * @brief Unattaches a source. - * - * @param[in] mediaSourceType : The media source type. - * - */ - virtual void removeSource(const MediaSourceType &mediaSourceType) = 0; - /** * @brief Handles notification that all sources were attached * diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 1b0976a73..296cf3b70 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -351,14 +351,6 @@ void GstGenericPlayer::attachSource(const std::unique_ptrenqueueTask(m_taskFactory->createRemoveSource(m_context, *this, mediaSourceType)); - } -} - void GstGenericPlayer::allSourcesAttached() { if (m_workerThread) @@ -1149,7 +1141,7 @@ void GstGenericPlayer::scheduleAudioUnderflow() { if (m_workerThread) { - bool underflowEnabled = m_context.isPlaying && !m_context.audioSourceRemoved; + bool underflowEnabled = m_context.isPlaying; m_workerThread->enqueueTask( m_taskFactory->createUnderflow(m_context, *this, underflowEnabled, MediaSourceType::AUDIO)); } diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index f65daa212..86d3e53b8 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -59,10 +59,6 @@ void AttachSource::execute() const { addSource(); } - else if (m_attachedSource->getType() == MediaSourceType::AUDIO && m_context.audioSourceRemoved) - { - reattachAudioSource(); - } else { RIALTO_SERVER_LOG_ERROR("cannot update caps"); @@ -110,22 +106,4 @@ void AttachSource::addSource() const if (caps) m_gstWrapper->gstCapsUnref(caps); } - -void AttachSource::reattachAudioSource() const -{ - if (!m_player.reattachSource(m_attachedSource)) - { - RIALTO_SERVER_LOG_ERROR("Reattaching source failed!"); - return; - } - - // Restart audio sink - m_player.setPlaybinFlags(true); - - m_context.streamInfo[m_attachedSource->getType()].isDataNeeded = true; - m_context.audioSourceRemoved = false; - m_player.notifyNeedMediaData(MediaSourceType::AUDIO); - - RIALTO_SERVER_LOG_MIL("Audio source reattached"); -} } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp index c027c69a2..6cbeec528 100644 --- a/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp +++ b/media/server/gstplayer/source/tasks/generic/CheckAudioUnderflow.cpp @@ -55,7 +55,7 @@ void CheckAudioUnderflow::execute() const { RIALTO_SERVER_LOG_WARN("Audio stream underflow! Position %" PRIu64 ", lastAudioSampleTimestamps: %" PRIu64, position, m_context.lastAudioSampleTimestamps); - bool underflowEnabled = m_context.isPlaying && !m_context.audioSourceRemoved; + bool underflowEnabled = m_context.isPlaying; Underflow task(m_context, m_player, m_gstPlayerClient, underflowEnabled, MediaSourceType::AUDIO); task.execute(); } diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 97b2d3452..2b23743fc 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -33,7 +33,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -147,13 +146,6 @@ std::unique_ptr GenericPlayerTaskFactory::createReadShmDataAndAttac return std::make_unique(context, m_gstWrapper, player, dataReader); } -std::unique_ptr -GenericPlayerTaskFactory::createRemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type) const -{ - return std::make_unique(context, player, m_client, m_gstWrapper, type); -} - std::unique_ptr GenericPlayerTaskFactory::createReportPosition(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const { diff --git a/media/server/gstplayer/source/tasks/generic/NeedData.cpp b/media/server/gstplayer/source/tasks/generic/NeedData.cpp index ca6c1865e..acf7417c5 100644 --- a/media/server/gstplayer/source/tasks/generic/NeedData.cpp +++ b/media/server/gstplayer/source/tasks/generic/NeedData.cpp @@ -59,11 +59,6 @@ void NeedData::execute() const if (m_gstPlayerClient && !elem.second.isNeedDataPending) { - if (sourceType == MediaSourceType::AUDIO && m_context.audioSourceRemoved) - { - RIALTO_SERVER_LOG_DEBUG("Audio source is removed, no need to request data"); - break; - } elem.second.isNeedDataPending = m_gstPlayerClient->notifyNeedMediaData(sourceType); } break; diff --git a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp b/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp deleted file mode 100644 index 29127c08c..000000000 --- a/media/server/gstplayer/source/tasks/generic/RemoveSource.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Sky UK - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "tasks/generic/RemoveSource.h" -#include "RialtoServerLogging.h" -#include "TypeConverters.h" - -namespace firebolt::rialto::server::tasks::generic -{ -RemoveSource::RemoveSource(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, - const MediaSourceType &type) - : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type} -{ - RIALTO_SERVER_LOG_DEBUG("Constructing RemoveSource"); -} - -RemoveSource::~RemoveSource() -{ - RIALTO_SERVER_LOG_DEBUG("RemoveSource finished"); -} - -void RemoveSource::execute() const -{ - RIALTO_SERVER_LOG_DEBUG("Executing RemoveSource for %s source", common::convertMediaSourceType(m_type)); - if (MediaSourceType::AUDIO != m_type) - { - RIALTO_SERVER_LOG_DEBUG("RemoveSource not supported for type != AUDIO"); - return; - } - m_context.audioSourceRemoved = true; - m_gstPlayerClient->invalidateActiveRequests(m_type); - GstElement *source{nullptr}; - auto sourceElem = m_context.streamInfo.find(m_type); - if (sourceElem != m_context.streamInfo.end()) - { - source = sourceElem->second.appSrc; - } - if (!source) - { - RIALTO_SERVER_LOG_WARN("failed to flush - source is NULL"); - return; - } - sourceElem->second.buffers.clear(); - sourceElem->second.isDataNeeded = false; - sourceElem->second.isNeedDataPending = false; - m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); - if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) - { - RIALTO_SERVER_LOG_WARN("failed to send flush-start event"); - } - GstEvent *flushStop = m_gstWrapper->gstEventNewFlushStop(FALSE); - if (!m_gstWrapper->gstElementSendEvent(source, flushStop)) - { - RIALTO_SERVER_LOG_WARN("failed to send flush-stop event"); - } - - // Turn audio off, removing audio sink from playsink - m_player.setPlaybinFlags(false); - - RIALTO_SERVER_LOG_MIL("%s source removed", common::convertMediaSourceType(m_type)); -} -} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index f76396bec..5d19dc02f 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -299,7 +299,6 @@ bool MediaPipelineServerInternal::removeSourceInternal(int32_t id) return false; } - m_gstPlayer->removeSource(sourceIter->first); m_needMediaDataTimers.erase(sourceIter->first); m_attachedSources.erase(sourceIter); return true; diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp index 03eadf4bc..26d7e70a4 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp @@ -397,25 +397,6 @@ void MediaPipelineTest::willEos(GstAppSrc *appSrc) EXPECT_CALL(*m_gstWrapperMock, gstAppSrcEndOfStream(appSrc)).WillOnce(Return(GST_FLOW_OK)); } -void MediaPipelineTest::willRemoveAudioSource() -{ - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) - .WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(0)).WillOnce(Return(&m_flushStopEvent)); - EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(3).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(3).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))) - .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); -} - void MediaPipelineTest::willStop() { EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_NULL)) @@ -428,19 +409,6 @@ void MediaPipelineTest::willStop() EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)); } -void MediaPipelineTest::willSetAudioAndVideoFlags() -{ - EXPECT_CALL(*m_glibWrapperMock, gTypeFromName(StrEq("GstPlayFlags"))).Times(4).WillRepeatedly(Return(kGstPlayFlagsType)); - EXPECT_CALL(*m_glibWrapperMock, gTypeClassRef(kGstPlayFlagsType)).Times(4).WillRepeatedly(Return(&m_flagsClass)); - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryFind(StrEq("brcmaudiosink"))).WillOnce(Return(nullptr)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("audio"))).WillOnce(Return(&m_audioFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("video"))).WillOnce(Return(&m_videoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("native-video"))) - .WillOnce(Return(&m_nativeVideoFlag)); - EXPECT_CALL(*m_glibWrapperMock, gFlagsGetValueByNick(&m_flagsClass, StrEq("text"))).WillOnce(Return(&m_subtitleFlag)); - EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&m_pipeline, StrEq("flags"))); -} - void MediaPipelineTest::createSession() { // Use matchResponse to store session id @@ -809,10 +777,6 @@ void MediaPipelineTest::removeSource(int sourceId) { auto removeSourceReq{createRemoveSourceRequest(m_sessionId, sourceId)}; ConfigureAction(m_clientStub).send(removeSourceReq).expectSuccess(); - - // Sources other than audio do not do anything for RemoveSource - if (m_audioSourceId == sourceId) - waitWorker(); } void MediaPipelineTest::stop() diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.h b/tests/componenttests/server/fixtures/MediaPipelineTest.h index efe006423..82808ac56 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.h +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.h @@ -57,9 +57,7 @@ class MediaPipelineTest : public RialtoServerComponentTest void willNotifyPaused(); void willPlay(); void willEos(GstAppSrc *appSrc); - void willRemoveAudioSource(); void willStop(); - void willSetAudioAndVideoFlags(); void willSetStateInvalidForQueryPosition(); void createSession(); diff --git a/tests/componenttests/server/tests/CMakeLists.txt b/tests/componenttests/server/tests/CMakeLists.txt index 9fe1d15bb..72a5a04e7 100644 --- a/tests/componenttests/server/tests/CMakeLists.txt +++ b/tests/componenttests/server/tests/CMakeLists.txt @@ -56,13 +56,13 @@ add_gtests ( mediaPipeline/PositionUpdatesTest.cpp mediaPipeline/ProcessAudioGapTest.cpp mediaPipeline/QosUpdatesTest.cpp - mediaPipeline/RemoveAudioPlaybackTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/SetPositionTest.cpp mediaPipeline/SetSourcePositionTest.cpp mediaPipeline/SetVideoWindowTest.cpp mediaPipeline/SourceTest.cpp + mediaPipeline/SwitchAudioPlaybackTest.cpp mediaPipeline/UnderflowTest.cpp mediaPipeline/VolumeTest.cpp mediaPipeline/WriteSegmentsTest.cpp diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp index a53f69159..67bcf5283 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioOnlyPlaybackTest.cpp @@ -185,7 +185,6 @@ TEST_F(MediaPipelineTest, AudioOnlyPlayback) gstNotifyEos(); // Step 12: Remove source - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 13: Stop diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp index b9710bbf1..b2e694ecd 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp @@ -179,7 +179,6 @@ TEST_F(AudioSourceSwitchTest, SwitchAudioSource) switchAudioSource(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp index 7daa9fad3..ec861b42b 100644 --- a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp @@ -731,7 +731,6 @@ TEST_F(DualVideoPlaybackTest, playbackFullDualVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); @@ -984,7 +983,6 @@ TEST_F(DualVideoPlaybackTest, playbackNoResouceManagerSecondaryVideo) destroySecondarySession(); // Step 16: Terminate the primary media session - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); willStop(); diff --git a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp index 89d8f9fae..c4b7399ea 100644 --- a/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/EncryptedPlaybackTest.cpp @@ -331,7 +331,6 @@ TEST_F(EncryptedPlaybackTest, EncryptedPlayback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp index 39ef64847..ceaf2129f 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp @@ -303,7 +303,6 @@ TEST_F(FlushTest, flushAudioSourceSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp index 3b2cbdf7b..601c13bdd 100644 --- a/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/HaveDataFailureTest.cpp @@ -168,7 +168,6 @@ TEST_F(HaveDataFailureTest, HaveDataError) failHaveData(m_lastVideoNeedData); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp index 4f6bebdf6..4f155cd90 100644 --- a/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/MuteTest.cpp @@ -159,7 +159,6 @@ TEST_F(MuteTest, Mute) getMute(); // Step 7: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp index 233c27409..f448344f4 100644 --- a/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp @@ -286,7 +286,6 @@ TEST_F(NonFatalPlayerErrorUpdatesTest, warningMessage) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index dc42d23ae..2d11ee4f6 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -580,7 +580,6 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) getUseBuffering(); // Step 16: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -786,7 +785,6 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) setUseBufferingFailure(); // Step 16: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp index c82a0ac8a..e4f9bceb6 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PlaybackTest.cpp @@ -211,7 +211,6 @@ TEST_F(MediaPipelineTest, playback) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp index e683a603b..906278f5a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PositionUpdatesTest.cpp @@ -244,7 +244,6 @@ TEST_F(PositionUpdatesTest, PositionUpdate) gstNotifyEos(); // Step 15: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -442,7 +441,6 @@ TEST_F(PositionUpdatesTest, GetPositionSuccess) // Step 18: Remove sources // Step 19: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -549,7 +547,6 @@ TEST_F(PositionUpdatesTest, getPositionFailure) getPositionFailure(); // Step 7: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp index eefd6a982..f3503df5a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/QosUpdatesTest.cpp @@ -342,7 +342,6 @@ TEST_F(QosUpdatesTest, QosUpdates) // Step 14: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 15: Remove sources removeSource(m_audioSourceId); @@ -526,7 +525,6 @@ TEST_F(QosUpdatesTest, StatsFailure) // Step 12: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 13: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp index 4c73eeafe..9a8987e05 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/RenderFrameTest.cpp @@ -189,7 +189,6 @@ TEST_F(RenderFrameTest, RenderFrameSuccess) renderFrame(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -303,7 +302,6 @@ TEST_F(RenderFrameTest, renderFrameFailure) renderFrameFailure(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp index a93b0d624..6256fb8e0 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetPositionTest.cpp @@ -311,7 +311,6 @@ TEST_F(SetPositionTest, SetPosition) gstNotifyEos(); // Step 14: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -463,7 +462,6 @@ TEST_F(SetPositionTest, SetPositionFailure) SetPositionFailure(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp index ca5f2c3b0..96f36b0a8 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp @@ -227,7 +227,6 @@ TEST_F(SetSourcePositionTest, setSourcePositionSuccess) gstNotifyEos(); // Step 13: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp index 326842175..66e66bf1e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SetVideoWindowTest.cpp @@ -173,7 +173,6 @@ TEST_F(SetVideoWindowTest, SetVideoWindow) setVideoWindow(); // Step 6: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp index dfa103706..a956bed92 100644 --- a/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SourceTest.cpp @@ -99,7 +99,6 @@ TEST_F(MediaPipelineTest, shouldAttachAudioSourceOnly) indicateAllSourcesAttached({&m_audioAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); // Step 5: Stop @@ -196,7 +195,6 @@ TEST_F(MediaPipelineTest, shouldAttachBothSources) indicateAllSourcesAttached({&m_audioAppSrc, &m_videoAppSrc}); // Step 4: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp similarity index 57% rename from tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp rename to tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp index f5d613c95..4c329716d 100644 --- a/tests/componenttests/server/tests/mediaPipeline/RemoveAudioPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/SwitchAudioPlaybackTest.cpp @@ -1,3 +1,4 @@ + /* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: @@ -17,71 +18,110 @@ * limitations under the License. */ +#include "ActionTraits.h" +#include "ConfigureAction.h" #include "Constants.h" #include "ExpectMessage.h" #include "Matchers.h" #include "MediaPipelineTest.h" +#include "MessageBuilders.h" using testing::_; +using testing::Invoke; using testing::Return; using testing::StrEq; namespace { constexpr int kFramesToPush{3}; +constexpr bool kResetTime{false}; +constexpr bool kAsync{true}; } // namespace namespace firebolt::rialto::server::ct { -class RemoveAudioPlaybackTest : public MediaPipelineTest +class SwitchAudioPlaybackTest : public MediaPipelineTest { public: - RemoveAudioPlaybackTest() = default; - ~RemoveAudioPlaybackTest() = default; + SwitchAudioPlaybackTest() = default; + ~SwitchAudioPlaybackTest() = default; - void willReattachAudioSource() + void willFlushAudioSource() { - EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&m_flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStartEvent)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&m_flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&m_audioAppSrc), &m_flushStopEvent)) + .WillOnce(Return(true)); + } + + void flushAudioSource() + { + // After successful Flush procedure, SourceFlushedEvent is sent. + ExpectMessage expectedSourceFlushed{m_clientStub}; + expectedSourceFlushed.setFilter([&](const SourceFlushedEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // After successful Flush, NeedData for source is sent. + ExpectMessage expectedAudioNeedData{m_clientStub}; + expectedAudioNeedData.setFilter([&](const NeedMediaDataEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // Send FlushRequest and expect success + auto request{createFlushRequest(m_sessionId, m_audioSourceId, kResetTime)}; + ConfigureAction(m_clientStub) + .send(request) + .expectSuccess() + .matchResponse([&](const FlushResponse &response) { EXPECT_EQ(kAsync, response.async()); }); + + // Check received SourceFlushedEvent events + auto receivedSourceFlushed{expectedSourceFlushed.getMessage()}; + ASSERT_TRUE(receivedSourceFlushed); + EXPECT_EQ(receivedSourceFlushed->session_id(), m_sessionId); + EXPECT_EQ(receivedSourceFlushed->source_id(), m_audioSourceId); + + // Check received NeedDataReqs + auto receivedAudioNeedData{expectedAudioNeedData.getMessage()}; + ASSERT_TRUE(receivedAudioNeedData); + EXPECT_EQ(receivedAudioNeedData->session_id(), m_sessionId); + EXPECT_EQ(receivedAudioNeedData->source_id(), m_audioSourceId); + m_lastAudioNeedData = receivedAudioNeedData; + } + + void willSwitchAudioSource() + { + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + gstCapsSetSimpleStringStub(&m_newCaps, StrEq("stream-format"), G_TYPE_STRING, StrEq("raw"))); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); EXPECT_CALL(*m_gstWrapperMock, - gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_audioCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); - EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_oldCaps)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_oldCaps)); + gstCapsSetSimpleIntStub(&m_newCaps, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&m_newCaps, StrEq("rate"), G_TYPE_INT, kSampleRate)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)).WillOnce(Return(&m_audioCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_newCaps, &m_audioCaps)).WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_newCaps)); EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_audioCaps)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); - - willSetAudioAndVideoFlags(); } - void reattachAudioSource() + void switchAudioSource() { - ExpectMessage expectedNeedData{m_clientStub}; - - attachAudioSource(); - - auto receivedNeedData{expectedNeedData.getMessage()}; - ASSERT_TRUE(receivedNeedData); - EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); - EXPECT_EQ(receivedNeedData->source_id(), m_audioSourceId); - EXPECT_EQ(receivedNeedData->frame_count(), 24); - m_lastAudioNeedData = receivedNeedData; + auto attachAudioSourceReq{createAttachAudioSourceRequest(m_sessionId)}; + attachAudioSourceReq.set_switch_source(true); + ConfigureAction(m_clientStub).send(attachAudioSourceReq).expectSuccess(); + waitWorker(); } private: - GstCaps m_oldCaps{}; - gchar m_oldCapsStr{}; + GstCaps m_newCaps{}; }; /* - * Component Test: Playback content when audio source has been removed and reattached. + * Component Test: Playback content when audio source has been switched. * Test Objective: - * Test that video only playback can continue if the audio source is removed, and that audio can be restarted - * when it is reattached. + * Test that audio source can be switched mid playback and that video playback is unaffected. * * Sequence Diagrams: * Rialto Dynamic Audio Stream Switching @@ -138,49 +178,35 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * Expect that gstreamer pipeline is paused. * Expect that server notifies the client that the Network state has changed to PAUSED. * - * Step 8: Remove Audio Source - * Remove the audio source. - * Expect that audio source is removed. - * - * Step 9: Write video frames - * Write video frames. - * - * Step 10: Play - * Play the content. - * Expect that gstreamer pipeline is playing. - * Expect that server notifies the client that the Network state has changed to PLAYING. - * - * Step 11: Pause - * Pause the content. - * Expect that gstreamer pipeline is paused. - * Expect that server notifies the client that the Network state has changed to PAUSED. + * Step 8: Flush Audio Source + * Flush the audio source. + * Expect that audio source is flushed. * - * Step 12: Reattach audio source - * Attach the audio source again. - * Expect that reattach procedure is triggered. - * Expect that audio source is attached. + * Step 9: Switch Audio Source + * Switch the audio source. + * Expect that audio source is switched. * - * Step 13: Write video and audio frames + * Step 10: Write video and audio frames * Write video frames. * Write audio frames. * - * Step 14: Play + * Step 11: Play * Play the content. * Expect that gstreamer pipeline is playing. * Expect that server notifies the client that the Network state has changed to PLAYING. * - * Step 15: Remove sources + * Step 12: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 16: Stop + * Step 13: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 17: Destroy media session + * Step 14: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -194,7 +220,7 @@ class RemoveAudioPlaybackTest : public MediaPipelineTest * * Code: */ -TEST_F(RemoveAudioPlaybackTest, RemoveAudio) +TEST_F(SwitchAudioPlaybackTest, DISABLED_SwitchAudio) { // Step 1: Create a new media session createSession(); @@ -243,46 +269,33 @@ TEST_F(RemoveAudioPlaybackTest, RemoveAudio) pause(); willNotifyPaused(); notifyPaused(); + GST_STATE(&m_pipeline) = GST_STATE_PAUSED; - // Step 8: Remove Audio Source - willRemoveAudioSource(); - removeSource(m_audioSourceId); - - // Step 9: Write video frames - pushVideoData(kFramesToPush); - - // Step 10: Play - willPlay(); - play(); - - // Step 11: Pause - willPause(); - pause(); - willNotifyPaused(); - notifyPaused(); + // Step 8: Flush Audio Source + willFlushAudioSource(); + flushAudioSource(); - // Step 12: Reattach audio source - willReattachAudioSource(); - reattachAudioSource(); + // Step 9: Switch Audio Source + willSwitchAudioSource(); + switchAudioSource(); - // Step 13: Write video and audio frames + // Step 10: Write video and audio frames pushAudioData(kFramesToPush); pushVideoData(kFramesToPush); - // Step 14: Play + // Step 11: Play willPlay(); play(); - // Step 15: Remove sources - willRemoveAudioSource(); + // Step 12: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 16: Stop + // Step 13: Stop willStop(); stop(); - // Step 17: Destroy media session + // Step 14: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp index 00eb3d6b6..ed58398c2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp @@ -400,7 +400,6 @@ TEST_F(UnderflowTest, underflow) // Step 15: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 16: Remove sources removeSource(m_audioSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp index 80df4d42b..d54ee116a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/VolumeTest.cpp @@ -253,7 +253,6 @@ TEST_F(VolumeTest, Volume) getVolume(); // Step 9: Remove sources - willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); diff --git a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp index 15b0015a1..a6cd32e64 100644 --- a/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/WriteSegmentsTest.cpp @@ -206,7 +206,6 @@ TEST_F(MediaPipelineTest, WriteSegments) // Step 13: Notify end of stream gstNotifyEos(); - willRemoveAudioSource(); // Step 14: Remove sources removeSource(m_audioSourceId); diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index 7f6edfd76..ed6635e3b 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -44,7 +44,6 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/PlayTest.cpp genericPlayer/tasksTests/ProcessAudioGapTest.cpp genericPlayer/tasksTests/ReadShmDataAndAttachSamplesTest.cpp - genericPlayer/tasksTests/RemoveSourceTest.cpp genericPlayer/tasksTests/RenderFrameTest.cpp genericPlayer/tasksTests/ReportPositionTest.cpp genericPlayer/tasksTests/SetBufferingLimitTest.cpp diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 44152f81c..5129fcf12 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -231,12 +231,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleEnoughDataData) TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowEnabled) { - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = true; - context.audioSourceRemoved = false; - }); + modifyContext([&](GenericPlayerContext &context) { context.isPlaying = true; }); std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); @@ -248,29 +243,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowEna TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowDisabledNotPlaying) { - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = false; - context.audioSourceRemoved = false; - }); - - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createUnderflow(_, _, false, MediaSourceType::AUDIO)) - .WillOnce(Return(ByMove(std::move(task)))); - - m_sut->scheduleAudioUnderflow(); -} - -TEST_F(GstGenericPlayerPrivateTest, shouldScheduleAudioUnderflowWithUnderflowDisabledRemoveSource) -{ - modifyContext( - [&](GenericPlayerContext &context) - { - context.isPlaying = true; - context.audioSourceRemoved = true; - }); + modifyContext([&](GenericPlayerContext &context) { context.isPlaying = false; }); std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index b2c5158b6..752bc95fe 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -146,16 +146,6 @@ TEST_F(GstGenericPlayerTest, shouldAttachSource) m_sut->attachSource(source); } -TEST_F(GstGenericPlayerTest, shouldRemoveSource) -{ - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createRemoveSource(_, _, MediaSourceType::AUDIO)) - .WillOnce(Return(ByMove(std::move(task)))); - - m_sut->removeSource(MediaSourceType::AUDIO); -} - TEST_F(GstGenericPlayerTest, shouldAllSourcesAttached) { std::unique_ptr task{std::make_unique>()}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index f40976337..15341d800 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -38,7 +38,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -385,24 +384,11 @@ void GenericTasksTestsBase::setContextSourceNull() testContext->m_context.source = nullptr; } -void GenericTasksTestsBase::setContextAudioSourceRemoved() -{ - testContext->m_context.audioSourceRemoved = true; -} - void GenericTasksTestsBase::setContextStreamInfoEmpty() { testContext->m_context.streamInfo.clear(); } -void GenericTasksTestsBase::setContextNeedDataAudioOnly() -{ - auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; - ASSERT_NE(testContext->m_context.streamInfo.end(), audioStreamIt); - - audioStreamIt->second.isDataNeeded = true; -} - void GenericTasksTestsBase::setContextSetupSourceFinished() { testContext->m_context.setupSourceFinished = true; @@ -1862,12 +1848,6 @@ void GenericTasksTestsBase::shouldReattachAudioSource() EXPECT_CALL(testContext->m_gstPlayer, reattachSource(_)).WillOnce(Return(true)); } -void GenericTasksTestsBase::shouldEnableAudioFlagsAndSendNeedData() -{ - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(true)); - EXPECT_CALL(testContext->m_gstPlayer, notifyNeedMediaData(MediaSourceType::AUDIO)); -} - void GenericTasksTestsBase::shouldFailToReattachAudioSource() { EXPECT_CALL(testContext->m_gstPlayer, reattachSource(_)).WillOnce(Return(false)); @@ -1878,15 +1858,6 @@ void GenericTasksTestsBase::triggerReattachAudioSource() triggerAttachAudioSource(); } -void GenericTasksTestsBase::checkNewAudioSourceAttached() -{ - auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; - ASSERT_NE(testContext->m_context.streamInfo.end(), audioStreamIt); - - EXPECT_TRUE(audioStreamIt->second.isDataNeeded); - EXPECT_FALSE(testContext->m_context.audioSourceRemoved); -} - void GenericTasksTestsBase::shouldQueryPositionAndSetToZero() { EXPECT_CALL(testContext->m_gstPlayer, getPosition(NotNullMatcher())).WillOnce(Return(0)); @@ -3000,44 +2971,6 @@ void GenericTasksTestsBase::triggerRenderFrame() task.execute(); } -void GenericTasksTestsBase::shouldInvalidateActiveAudioRequests() -{ - EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); -} - -void GenericTasksTestsBase::shouldDisableAudioFlag() -{ - EXPECT_CALL(testContext->m_gstPlayer, setPlaybinFlags(false)); -} - -void GenericTasksTestsBase::triggerRemoveSourceAudio() -{ - firebolt::rialto::server::tasks::generic::RemoveSource task{testContext->m_context, testContext->m_gstPlayer, - &testContext->m_gstPlayerClient, - testContext->m_gstWrapper, - firebolt::rialto::MediaSourceType::AUDIO}; - task.execute(); -} - -void GenericTasksTestsBase::triggerRemoveSourceVideo() -{ - firebolt::rialto::server::tasks::generic::RemoveSource task{testContext->m_context, testContext->m_gstPlayer, - &testContext->m_gstPlayerClient, - testContext->m_gstWrapper, - firebolt::rialto::MediaSourceType::VIDEO}; - task.execute(); -} - -void GenericTasksTestsBase::checkAudioSourceRemoved() -{ - EXPECT_TRUE(testContext->m_context.audioSourceRemoved); -} - -void GenericTasksTestsBase::checkAudioSourceNotRemoved() -{ - EXPECT_FALSE(testContext->m_context.audioSourceRemoved); -} - void GenericTasksTestsBase::shouldFlushAudioSrcSuccess() { EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 646641e1d..bc07871f7 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -72,9 +72,7 @@ class GenericTasksTestsBase : public ::testing::Test void setContextVideoBuffer(); void setContextPlaybackRate(); void setContextSourceNull(); - void setContextAudioSourceRemoved(); void setContextStreamInfoEmpty(); - void setContextNeedDataAudioOnly(); void setContextSetupSourceFinished(); // SetupElement test methods @@ -190,10 +188,8 @@ class GenericTasksTestsBase : public ::testing::Test void shouldAttachVideoSourceWithDolbyVisionSource(); void triggerAttachVideoSourceWithDolbyVisionSource(); void shouldReattachAudioSource(); - void shouldEnableAudioFlagsAndSendNeedData(); void shouldFailToReattachAudioSource(); void triggerReattachAudioSource(); - void checkNewAudioSourceAttached(); void triggerFailToCastAudioSource(); void triggerFailToCastVideoSource(); void triggerFailToCastDolbyVisionSource(); @@ -400,14 +396,6 @@ class GenericTasksTestsBase : public ::testing::Test void triggerReadShmDataAndAttachSamplesVideo(); void triggerReadShmDataAndAttachSamples(); - // RemoveSource test methods - void shouldInvalidateActiveAudioRequests(); - void shouldDisableAudioFlag(); - void triggerRemoveSourceAudio(); - void triggerRemoveSourceVideo(); - void checkAudioSourceRemoved(); - void checkAudioSourceNotRemoved(); - // Flush test methods void shouldFlushAudio(); void shouldFlushVideo(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp index 2b241b9fd..fe39e7f95 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp @@ -167,24 +167,6 @@ TEST_F(AttachSourceTest, shouldSkipSwitchAudioSourceWhenSourceIsNotRemoved) triggerReattachAudioSource(); } -TEST_F(AttachSourceTest, shouldReattachAudioSource) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextAudioSourceRemoved(); - shouldReattachAudioSource(); - shouldEnableAudioFlagsAndSendNeedData(); - triggerReattachAudioSource(); - checkNewAudioSourceAttached(); -} - -TEST_F(AttachSourceTest, shouldFailToReattachAudioSource) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextAudioSourceRemoved(); - shouldFailToReattachAudioSource(); - triggerReattachAudioSource(); -} - TEST_F(AttachSourceTest, shouldFailToCastAudioSource) { triggerFailToCastAudioSource(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 7693164c0..8d81d3bf0 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -45,7 +45,6 @@ #include "tasks/generic/Play.h" #include "tasks/generic/ProcessAudioGap.h" #include "tasks/generic/ReadShmDataAndAttachSamples.h" -#include "tasks/generic/RemoveSource.h" #include "tasks/generic/RenderFrame.h" #include "tasks/generic/ReportPosition.h" #include "tasks/generic/SetBufferingLimit.h" @@ -181,13 +180,6 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateReadShmDataAndAttachSamples) EXPECT_NO_THROW(dynamic_cast(*task)); } -TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateRemoveSource) -{ - auto task = m_sut.createRemoveSource(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO); - EXPECT_NE(task, nullptr); - EXPECT_NO_THROW(dynamic_cast(*task)); -} - TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateReportPosition) { auto task = m_sut.createReportPosition(m_context, m_gstPlayer); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp index af3ccd199..46e67cd0e 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/NeedDataTest.cpp @@ -70,16 +70,6 @@ TEST_F(NeedDataTest, shouldSkipToNotifyNeedAudioDataWhenAnotherOneIsPending) checkNeedDataPendingForAudioOnly(); } -TEST_F(NeedDataTest, shouldSkipToNotifyNeedAudioDataWhenAudioSourceIsRemoved) -{ - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); - setContextAudioSourceRemoved(); - triggerNeedDataAudio(); - checkNeedDataForAudioOnly(); - checkNoNeedDataPendingForBothSources(); -} - TEST_F(NeedDataTest, shouldNotifyNeedVideoData) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp deleted file mode 100644 index a9153ff75..000000000 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/RemoveSourceTest.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2022 Sky UK - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "GenericTasksTestsBase.h" - -class RemoveSourceTest : public GenericTasksTestsBase -{ -protected: - RemoveSourceTest() - { - setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); - setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); - setContextNeedDataAudioOnly(); - setContextAudioBuffer(); - setContextNeedDataPendingAudioOnly(true); - } -}; - -TEST_F(RemoveSourceTest, shouldRemoveAudioSourceWithoutFlushing) -{ - setContextStreamInfoEmpty(); - shouldInvalidateActiveAudioRequests(); - triggerRemoveSourceAudio(); - checkAudioSourceRemoved(); -} - -TEST_F(RemoveSourceTest, shouldNotRemoveVideoSource) -{ - triggerRemoveSourceVideo(); - checkNeedDataForAudioOnly(); - checkNeedDataPendingForAudioOnly(); - checkAudioSourceNotRemoved(); -} - -TEST_F(RemoveSourceTest, shouldRemoveAudioSource) -{ - shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcSuccess(); - triggerRemoveSourceAudio(); - checkNoMoreNeedData(); - checkNoNeedDataPendingForBothSources(); - checkAudioSourceRemoved(); - checkBuffersEmpty(); -} - -TEST_F(RemoveSourceTest, shouldRemoveAudioSourceFlushEventError) -{ - shouldInvalidateActiveAudioRequests(); - shouldDisableAudioFlag(); - shouldFlushAudioSrcFailure(); - triggerRemoveSourceAudio(); - checkNoMoreNeedData(); - checkNoNeedDataPendingForBothSources(); - checkAudioSourceRemoved(); -} diff --git a/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp b/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp index 18717e95b..56eae34ee 100644 --- a/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/SourceTest.cpp @@ -97,7 +97,6 @@ TEST_F(RialtoServerMediaPipelineSourceTest, RemoveSourceSuccess) std::int32_t sourceId{mediaSource->getId()}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, removeSource(MediaSourceType::AUDIO)); EXPECT_EQ(m_mediaPipeline->removeSource(sourceId), true); } @@ -137,7 +136,6 @@ TEST_F(RialtoServerMediaPipelineSourceTest, AttachRemoveAttachSourceDifferentId) std::int32_t firstSourceId{mediaSource->getId()}; mainThreadWillEnqueueTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, removeSource(MediaSourceType::AUDIO)); EXPECT_EQ(m_mediaPipeline->removeSource(firstSourceId), true); mainThreadWillEnqueueTaskAndWait(); diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 902309e50..3e165fba6 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -62,10 +62,6 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const std::shared_ptr &dataReader), (const, override)); - MOCK_METHOD(std::unique_ptr, createRemoveSource, - (GenericPlayerContext & context, (IGstGenericPlayerPrivate & player), - const firebolt::rialto::MediaSourceType &type), - (const, override)); MOCK_METHOD(std::unique_ptr, createReportPosition, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player), (const, override)); MOCK_METHOD(std::unique_ptr, createCheckAudioUnderflow, diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index de4697646..a5919a427 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -34,7 +34,6 @@ class GstGenericPlayerMock : public IGstGenericPlayer virtual ~GstGenericPlayerMock() = default; MOCK_METHOD(void, attachSource, (const std::unique_ptr &mediaSource), (override)); - MOCK_METHOD(void, removeSource, (const MediaSourceType &mediaSourceType), (override)); MOCK_METHOD(void, allSourcesAttached, (), (override)); MOCK_METHOD(void, play, (), (override)); MOCK_METHOD(void, pause, (), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index ceab057ff..5e8da6b95 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -68,7 +68,6 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, removeAutoVideoSinkChild, (GObject * object), (override)); MOCK_METHOD(void, removeAutoAudioSinkChild, (GObject * object), (override)); MOCK_METHOD(GstElement *, getSink, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, setPlaybinFlags, (bool enableAudio), (override)); MOCK_METHOD(void, addAudioClippingToBuffer, (GstBuffer * buffer, uint64_t clippingStart, uint64_t clippingEnd), (const, override)); From c9607616b4c6d4e04da395a90898a7d8c00ea968 Mon Sep 17 00:00:00 2001 From: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:38:53 +0100 Subject: [PATCH 03/21] Allow to switch audio codec, when audio is re-attached (#448) Summary: Allow to switch audio codec, when audio is re-attached Type: Fix Test Plan: UT/CT, Fullstack Jira: LLAMA-18057 --- .../gstplayer/include/tasks/generic/AttachSource.h | 1 + .../source/tasks/generic/AttachSource.cpp | 14 ++++++++++++++ .../genericPlayer/tasksTests/AttachSourceTest.cpp | 10 +++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/media/server/gstplayer/include/tasks/generic/AttachSource.h b/media/server/gstplayer/include/tasks/generic/AttachSource.h index f6d8144fe..0f77545d4 100644 --- a/media/server/gstplayer/include/tasks/generic/AttachSource.h +++ b/media/server/gstplayer/include/tasks/generic/AttachSource.h @@ -47,6 +47,7 @@ class AttachSource : public IPlayerTask private: void addSource() const; + void reattachAudioSource() const; GenericPlayerContext &m_context; std::shared_ptr m_gstWrapper; diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index 86d3e53b8..8396a4d1b 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -59,6 +59,10 @@ void AttachSource::execute() const { addSource(); } + else if (m_attachedSource->getType() == MediaSourceType::AUDIO) + { + reattachAudioSource(); + } else { RIALTO_SERVER_LOG_ERROR("cannot update caps"); @@ -106,4 +110,14 @@ void AttachSource::addSource() const if (caps) m_gstWrapper->gstCapsUnref(caps); } + +void AttachSource::reattachAudioSource() const +{ + if (!m_player.reattachSource(m_attachedSource)) + { + RIALTO_SERVER_LOG_ERROR("Reattaching source failed!"); + return; + } + RIALTO_SERVER_LOG_MIL("Audio source reattached"); +} } // namespace firebolt::rialto::server::tasks::generic diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp index fe39e7f95..746827b33 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/AttachSourceTest.cpp @@ -161,9 +161,17 @@ TEST_F(AttachSourceTest, shouldFailToAttachUnknownSource) triggerAttachUnknownSource(); } -TEST_F(AttachSourceTest, shouldSkipSwitchAudioSourceWhenSourceIsNotRemoved) +TEST_F(AttachSourceTest, shouldSwitchAudioSourceWhenSourceIsReattached) { setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); + shouldReattachAudioSource(); + triggerReattachAudioSource(); +} + +TEST_F(AttachSourceTest, shouldFailToSwitchAudioSourceWhenSourceIsReattached) +{ + setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); + shouldFailToReattachAudioSource(); triggerReattachAudioSource(); } From 654c3a23266a72f0e67c60500935c4f2bb500158 Mon Sep 17 00:00:00 2001 From: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:08:07 +0100 Subject: [PATCH 04/21] Asure correct data & flush order during multiple flushes (#466) Summary: Asure correct data & flush order during multiple flushes Type: Fix Test Plan: UT/CT, Fullstack Jira: NO-JIRA --- media/client/ipc/include/MediaPipelineIpc.h | 2 +- .../client/ipc/interface/IMediaPipelineIpc.h | 4 +- media/client/ipc/source/MediaPipelineIpc.cpp | 4 +- media/client/main/include/MediaPipeline.h | 2 +- .../client/main/include/MediaPipelineProxy.h | 2 +- media/client/main/source/MediaPipeline.cpp | 4 +- media/public/include/IMediaPipeline.h | 7 +- .../include/FlushOnPrerollController.h | 10 +- .../gstplayer/include/GenericPlayerContext.h | 2 +- .../gstplayer/include/GstDispatcherThread.h | 7 + .../gstplayer/include/GstGenericPlayer.h | 11 +- .../include/IFlushOnPrerollController.h | 6 +- .../gstplayer/include/IGstDispatcherThread.h | 2 + .../include/IGstGenericPlayerPrivate.h | 17 +-- .../include/tasks/IGenericPlayerTaskFactory.h | 5 +- .../gstplayer/include/tasks/generic/Flush.h | 5 +- .../tasks/generic/GenericPlayerTaskFactory.h | 4 +- .../gstplayer/interface/IGstGenericPlayer.h | 11 +- .../source/FlushOnPrerollController.cpp | 42 ++++-- .../gstplayer/source/GstDispatcherThread.cpp | 26 +++- .../gstplayer/source/GstGenericPlayer.cpp | 61 ++++----- .../gstplayer/source/GstWebAudioPlayer.cpp | 4 +- .../gstplayer/source/tasks/generic/Flush.cpp | 20 ++- .../generic/GenericPlayerTaskFactory.cpp | 4 +- .../source/tasks/generic/HandleBusMessage.cpp | 4 - .../ipc/source/MediaPipelineModuleService.cpp | 4 +- .../include/MediaPipelineServerInternal.h | 6 +- .../source/MediaPipelineServerInternal.cpp | 12 +- .../service/include/IMediaPipelineService.h | 2 +- .../service/source/MediaPipelineService.cpp | 4 +- .../service/source/MediaPipelineService.h | 2 +- proto/mediapipelinemodule.proto | 4 +- .../tests/base/MediaPipelineTestMethods.cpp | 6 +- .../server/common/ExpectMessage.h | 2 +- .../ipc/mediaPipelineIpc/PlayPauseTest.cpp | 12 +- .../mediaPipeline/MediaPipelineProxyTest.cpp | 5 +- .../main/mediaPipeline/PlayPauseTest.cpp | 10 +- .../client/mocks/ipc/MediaPipelineIpcMock.h | 2 +- .../main/MediaPipelineAndControlClientMock.h | 2 +- .../GstDispatcherThreadTest.cpp | 121 +++++++++++++++++- .../FlushOnPrerollControllerTest.cpp | 61 ++++++--- .../GstGenericPlayerPrivateTest.cpp | 12 -- .../genericPlayer/GstGenericPlayerTest.cpp | 33 ++++- .../common/GenericTasksTestsBase.cpp | 20 +-- .../common/GenericTasksTestsBase.h | 1 - .../common/GstGenericPlayerTestCommon.cpp | 2 +- .../genericPlayer/tasksTests/FlushTest.cpp | 6 - .../GenericPlayerTaskFactoryTest.cpp | 2 +- .../tasksTests/HandleBusMessageTest.cpp | 5 - .../gstplayer/webAudioPlayer/CreateTest.cpp | 2 +- .../common/GstWebAudioPlayerTestCommon.cpp | 2 +- ...MediaPipelineModuleServiceTestsFixture.cpp | 4 +- .../MiscellaneousFunctionsTest.cpp | 12 +- .../base/MediaPipelineTestBase.cpp | 7 + .../base/MediaPipelineTestBase.h | 1 + .../gstplayer/FlushOnPrerollControllerMock.h | 6 +- .../gstplayer/GenericPlayerTaskFactoryMock.h | 2 +- .../GstDispatcherThreadFactoryMock.h | 1 + .../mocks/gstplayer/GstGenericPlayerMock.h | 2 +- .../gstplayer/GstGenericPlayerPrivateMock.h | 4 +- .../main/MediaPipelineServerInternalMock.h | 2 +- .../mocks/service/MediaPipelineServiceMock.h | 2 +- .../MediaPipelineServiceTestsFixture.cpp | 10 +- 63 files changed, 438 insertions(+), 221 deletions(-) diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index 5e50dd4f4..db45d00d9 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -79,7 +79,7 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool setVideoWindow(uint32_t x, uint32_t y, uint32_t width, uint32_t height) override; - bool play() override; + bool play(bool &async) override; bool pause() override; diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index 1d055d2b7..782a1931b 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -126,9 +126,11 @@ class IMediaPipelineIpc /** * @brief Request play on the playback session. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Request pause on the playback session. diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index 23c0375da..ff1b09847 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -348,7 +348,7 @@ bool MediaPipelineIpc::setVideoWindow(uint32_t x, uint32_t y, uint32_t width, ui return true; } -bool MediaPipelineIpc::play() +bool MediaPipelineIpc::play(bool &async) { if (!reattachChannelIfRequired()) { @@ -375,6 +375,8 @@ bool MediaPipelineIpc::play() return false; } + async = response.async(); + return true; } diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index 74bf8d13b..13075a511 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -118,7 +118,7 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; diff --git a/media/client/main/include/MediaPipelineProxy.h b/media/client/main/include/MediaPipelineProxy.h index cf0283cd2..b38371963 100644 --- a/media/client/main/include/MediaPipelineProxy.h +++ b/media/client/main/include/MediaPipelineProxy.h @@ -52,7 +52,7 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient bool allSourcesAttached() override { return m_mediaPipeline->allSourcesAttached(); } - bool play() override { return m_mediaPipeline->play(); } + bool play(bool &async) override { return m_mediaPipeline->play(async); } bool pause() override { return m_mediaPipeline->pause(); } diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index 280aff919..e529fe3b6 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -252,11 +252,11 @@ bool MediaPipeline::allSourcesAttached() return m_mediaPipelineIpc->allSourcesAttached(); } -bool MediaPipeline::play() +bool MediaPipeline::play(bool &async) { RIALTO_CLIENT_LOG_DEBUG("entry:"); - return m_mediaPipelineIpc->play(); + return m_mediaPipelineIpc->play(async); } bool MediaPipeline::pause() diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index 1064ccc1e..e6c68787e 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -1119,16 +1119,15 @@ class IMediaPipeline /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the * media player client of playback state * IMediaPipelineClient::PlaybackState::PLAYING. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - virtual bool play() = 0; + virtual bool play(bool &async) = 0; /** * @brief Pauses playback of the media. diff --git a/media/server/gstplayer/include/FlushOnPrerollController.h b/media/server/gstplayer/include/FlushOnPrerollController.h index 3b17dfaed..8ae19bfc3 100644 --- a/media/server/gstplayer/include/FlushOnPrerollController.h +++ b/media/server/gstplayer/include/FlushOnPrerollController.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_FLUSH_ONPREROLL_CONTROLLER_H_ #include "IFlushOnPrerollController.h" +#include #include #include #include @@ -37,13 +38,16 @@ class FlushOnPrerollController : public IFlushOnPrerollController FlushOnPrerollController() = default; ~FlushOnPrerollController() override = default; - bool shouldPostponeFlush(const MediaSourceType &type) const override; - void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) override; + void waitIfRequired(const MediaSourceType &type) override; + void setFlushing(const MediaSourceType &type) override; + void setPrerolling() override; void stateReached(const GstState &newPipelineState) override; + void setTargetState(const GstState &state) override; void reset() override; private: - mutable std::mutex m_mutex{}; + std::mutex m_mutex{}; + std::condition_variable m_conditionVariable{}; std::set m_flushingSources{}; std::optional m_targetState{std::nullopt}; bool m_isPrerolled{false}; diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index 29cf8eae1..d97deb4b1 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -263,7 +263,7 @@ struct GenericPlayerContext /** * @brief Workaround for the gstreamer flush issue */ - FlushOnPrerollController flushOnPrerollController; + std::shared_ptr flushOnPrerollController{std::make_shared()}; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstDispatcherThread.h b/media/server/gstplayer/include/GstDispatcherThread.h index 540f161ca..0db6fe2d9 100644 --- a/media/server/gstplayer/include/GstDispatcherThread.h +++ b/media/server/gstplayer/include/GstDispatcherThread.h @@ -34,6 +34,7 @@ class GstDispatcherThreadFactory : public IGstDispatcherThreadFactory ~GstDispatcherThreadFactory() override = default; std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const override; }; @@ -41,6 +42,7 @@ class GstDispatcherThread : public IGstDispatcherThread { public: GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper); ~GstDispatcherThread() override; @@ -58,6 +60,11 @@ class GstDispatcherThread : public IGstDispatcherThread */ IGstDispatcherThreadClient &m_client; + /** + * @brief The flush on preroll controller. + */ + std::shared_ptr m_flushOnPrerollController; + /** * @brief The gstreamer wrapper object. */ diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index a07f54f38..325421690 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -36,6 +36,7 @@ #include "tasks/IGenericPlayerTaskFactory.h" #include "tasks/IPlayerTask.h" #include +#include #include #include #include @@ -107,7 +108,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void attachSource(const std::unique_ptr &mediaSource) override; void allSourcesAttached() override; - void play() override; + void play(bool &async) override; void pause() override; void stop() override; void attachSamples(const IMediaPipeline::MediaSegmentVector &mediaSegments) override; @@ -167,7 +168,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void updateVideoCaps(int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData) override; void addAudioClippingToBuffer(GstBuffer *buffer, uint64_t clippingStart, uint64_t clippingEnd) const override; - bool changePipelineState(GstState newState) override; + GstStateChangeReturn changePipelineState(GstState newState) override; int64_t getPosition(GstElement *element) override; void startPositionReportingAndCheckAudioUnderflowTimer() override; void stopPositionReportingAndCheckAudioUnderflowTimer() override; @@ -190,8 +191,6 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva GstElement *getSink(const MediaSourceType &mediaSourceType) const override; void setSourceFlushed(const MediaSourceType &mediaSourceType) override; bool isAsync(const MediaSourceType &mediaSourceType) const; - void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) override; - void executePostponedFlushes() override; void notifyPlaybackInfo() override; private: @@ -429,9 +428,9 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva std::unique_ptr m_flushWatcher; /** - * @brief The postponed flush tasks + * @brief The ongoing state change operations counter */ - std::vector> m_postponedFlushes{}; + std::atomic m_ongoingStateChangesNumber{0}; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/IFlushOnPrerollController.h b/media/server/gstplayer/include/IFlushOnPrerollController.h index 64379b7af..76469a523 100644 --- a/media/server/gstplayer/include/IFlushOnPrerollController.h +++ b/media/server/gstplayer/include/IFlushOnPrerollController.h @@ -34,9 +34,11 @@ class IFlushOnPrerollController public: virtual ~IFlushOnPrerollController() = default; - virtual bool shouldPostponeFlush(const MediaSourceType &type) const = 0; - virtual void setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) = 0; + virtual void waitIfRequired(const MediaSourceType &type) = 0; + virtual void setFlushing(const MediaSourceType &type) = 0; + virtual void setPrerolling() = 0; virtual void stateReached(const GstState &newPipelineState) = 0; + virtual void setTargetState(const GstState &state) = 0; virtual void reset() = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/IGstDispatcherThread.h b/media/server/gstplayer/include/IGstDispatcherThread.h index 15d6083e1..a72e2a0ad 100644 --- a/media/server/gstplayer/include/IGstDispatcherThread.h +++ b/media/server/gstplayer/include/IGstDispatcherThread.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ #define FIREBOLT_RIALTO_SERVER_I_GST_DISPATCHER_THREAD_H_ +#include "IFlushOnPrerollController.h" #include "IGstDispatcherThreadClient.h" #include "IGstWrapper.h" #include @@ -36,6 +37,7 @@ class IGstDispatcherThreadFactory virtual std::unique_ptr createGstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const = 0; }; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index ac8ff8d15..d6e5ba7ed 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -174,9 +174,9 @@ class IGstGenericPlayerPrivate * * @param[in] newState : The desired state. * - * @retval true on success. + * @retval state change status */ - virtual bool changePipelineState(GstState newState) = 0; + virtual GstStateChangeReturn changePipelineState(GstState newState) = 0; /** * @brief Gets the current position of the element @@ -304,19 +304,6 @@ class IGstGenericPlayerPrivate */ virtual void setSourceFlushed(const MediaSourceType &mediaSourceType) = 0; - /** - * @brief Postpones flush for the given source type - * - * @param[in] mediaSourceType : the source type that has been flushed - * @param[in] resetTime : whether to reset the time after flush - */ - virtual void postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) = 0; - - /** - * @brief Queues postponed flushes for execution - */ - virtual void executePostponedFlushes() = 0; - /** * @brief Sends PlaybackInfo notification. Called by the worker thread. */ diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 1026de09a..f8de67cb4 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -416,12 +416,13 @@ class IGenericPlayerTaskFactory * @param[in] context : The GstPlayer context * @param[in] type : The media source type to flush * @param[in] resetTime : True if time should be reset + * @param[in] isAsync : True if flushed source is asynchronous * * @retval the new Flush task instance. */ virtual std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const = 0; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const = 0; /** * @brief Creates a SetSourcePosition task. diff --git a/media/server/gstplayer/include/tasks/generic/Flush.h b/media/server/gstplayer/include/tasks/generic/Flush.h index 6345b1d00..f3729068e 100644 --- a/media/server/gstplayer/include/tasks/generic/Flush.h +++ b/media/server/gstplayer/include/tasks/generic/Flush.h @@ -33,8 +33,8 @@ class Flush : public IPlayerTask { public: Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime); + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync); ~Flush() override; void execute() const override; @@ -45,6 +45,7 @@ class Flush : public IPlayerTask std::shared_ptr m_gstWrapper; MediaSourceType m_type; bool m_resetTime; + bool m_isAsync; }; } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 8a5c045ef..aa9a06e55 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -106,8 +106,8 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory IGstGenericPlayerPrivate &player) const override; std::unique_ptr createPing(std::unique_ptr &&heartbeatHandler) const override; std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const override; + const firebolt::rialto::MediaSourceType &type, bool resetTime, + bool isAsync) const override; std::unique_ptr createSetSourcePosition(GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, bool resetTime, double appliedRate, diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index 57be0d51d..5e6842926 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -97,14 +97,15 @@ class IGstGenericPlayer /** * @brief Starts playback of the media. * - * This method is considered to be asynchronous and MUST NOT block - * but should request playback and then return. - * * Once the backend is successfully playing it should notify the - * media player client of playback state PlaybackState::PLAYING. + * media player client of playback state + * IMediaPipelineClient::PlaybackState::PLAYING. * + * @param[out] async : True if play method call is asynchronous + * + * @retval true on success. */ - virtual void play() = 0; + virtual void play(bool &async) = 0; /** * @brief Pauses playback of the media. diff --git a/media/server/gstplayer/source/FlushOnPrerollController.cpp b/media/server/gstplayer/source/FlushOnPrerollController.cpp index 8ffce5cbc..1218b892c 100644 --- a/media/server/gstplayer/source/FlushOnPrerollController.cpp +++ b/media/server/gstplayer/source/FlushOnPrerollController.cpp @@ -18,41 +18,67 @@ */ #include "FlushOnPrerollController.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" +#include namespace firebolt::rialto::server { -bool FlushOnPrerollController::shouldPostponeFlush(const MediaSourceType &type) const +void FlushOnPrerollController::waitIfRequired(const MediaSourceType &type) { std::unique_lock lock{m_mutex}; - return m_isPrerolled && m_flushingSources.find(type) != m_flushingSources.end(); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source entry", + common::convertMediaSourceType(type)); + m_conditionVariable.wait(lock, [this, &type]() + // coverity[MISSING_LOCK:FALSE] + { return !m_isPrerolled || m_flushingSources.find(type) == m_flushingSources.end(); }); + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Waiting if required for %s source exit", + common::convertMediaSourceType(type)); } -void FlushOnPrerollController::setFlushing(const MediaSourceType &type, const GstState ¤tPipelineState) +void FlushOnPrerollController::setFlushing(const MediaSourceType &type) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set flushing for: %s", common::convertMediaSourceType(type)); std::unique_lock lock{m_mutex}; m_flushingSources.insert(type); +} + +void FlushOnPrerollController::setPrerolling() +{ + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set prerolling"); + std::unique_lock lock{m_mutex}; m_isPrerolled = false; - if (!m_targetState.has_value()) - { - m_targetState = currentPipelineState; - } + m_conditionVariable.notify_all(); } void FlushOnPrerollController::stateReached(const GstState &newPipelineState) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: State reached %s", gst_element_state_get_name(newPipelineState)); std::unique_lock lock{m_mutex}; m_isPrerolled = true; if (m_targetState.has_value() && newPipelineState == m_targetState.value()) { + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Clear state after state reached %s", + gst_element_state_get_name(newPipelineState)); m_flushingSources.clear(); - m_targetState = std::nullopt; } + m_conditionVariable.notify_all(); +} + +void FlushOnPrerollController::setTargetState(const GstState &state) +{ + std::unique_lock lock{m_mutex}; + m_targetState = state; + RIALTO_SERVER_LOG_DEBUG("FlushOnPrerollController: Set target state %s", gst_element_state_get_name(state)); } void FlushOnPrerollController::reset() { + RIALTO_SERVER_LOG_DEBUG("Reset FlushOnPrerollController"); std::unique_lock lock{m_mutex}; + m_isPrerolled = false; m_flushingSources.clear(); m_targetState = std::nullopt; + m_conditionVariable.notify_all(); } } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstDispatcherThread.cpp b/media/server/gstplayer/source/GstDispatcherThread.cpp index 90953a27f..289ab77d7 100644 --- a/media/server/gstplayer/source/GstDispatcherThread.cpp +++ b/media/server/gstplayer/source/GstDispatcherThread.cpp @@ -24,14 +24,17 @@ namespace firebolt::rialto::server { std::unique_ptr GstDispatcherThreadFactory::createGstDispatcherThread( IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) const { - return std::make_unique(client, pipeline, gstWrapper); + return std::make_unique(client, pipeline, flushOnPrerollController, gstWrapper); } GstDispatcherThread::GstDispatcherThread(IGstDispatcherThreadClient &client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper) - : m_client{client}, m_gstWrapper{gstWrapper}, m_isGstreamerDispatcherActive{true} + : m_client{client}, m_flushOnPrerollController{flushOnPrerollController}, m_gstWrapper{gstWrapper}, + m_isGstreamerDispatcherActive{true} { RIALTO_SERVER_LOG_INFO("GstDispatcherThread is starting"); m_gstBusDispatcherThread = std::thread(&GstDispatcherThread::gstBusEventHandler, this, pipeline); @@ -80,11 +83,30 @@ void GstDispatcherThread::gstBusEventHandler(GstElement *pipeline) case GST_STATE_NULL: { m_isGstreamerDispatcherActive = false; + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->reset(); + } break; } case GST_STATE_PAUSED: + { + if (m_flushOnPrerollController && pending != GST_STATE_PAUSED) + { + m_flushOnPrerollController->stateReached(newState); + } + else if (m_flushOnPrerollController && pending == GST_STATE_PAUSED) + { + m_flushOnPrerollController->setPrerolling(); + } + break; + } case GST_STATE_PLAYING: { + if (m_flushOnPrerollController) + { + m_flushOnPrerollController->stateReached(newState); + } break; } case GST_STATE_READY: diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 296cf3b70..0c3f6b9cd 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -202,8 +202,9 @@ GstGenericPlayer::GstGenericPlayer( RIALTO_SERVER_LOG_MIL("Primary video playback selected"); } - m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper); + m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + m_context.flushOnPrerollController, + m_gstWrapper); } GstGenericPlayer::~GstGenericPlayer() @@ -252,7 +253,6 @@ void GstGenericPlayer::initMsePipeline() void GstGenericPlayer::resetWorkerThread() { - m_postponedFlushes.clear(); // Shutdown task thread m_workerThread->enqueueTask(m_taskFactory->createShutdown(*this)); m_workerThread->join(); @@ -461,23 +461,6 @@ void GstGenericPlayer::setSourceFlushed(const MediaSourceType &mediaSourceType) m_flushWatcher->setFlushed(mediaSourceType); } -void GstGenericPlayer::postponeFlush(const MediaSourceType &mediaSourceType, bool resetTime) -{ - m_postponedFlushes.emplace_back(std::make_pair(mediaSourceType, resetTime)); -} - -void GstGenericPlayer::executePostponedFlushes() -{ - if (m_workerThread) - { - for (const auto &[mediaSourceType, resetTime] : m_postponedFlushes) - { - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); - } - } - m_postponedFlushes.clear(); -} - void GstGenericPlayer::notifyPlaybackInfo() { PlaybackInfo info; @@ -1178,16 +1161,32 @@ void GstGenericPlayer::cancelUnderflow(firebolt::rialto::MediaSourceType mediaSo } } -void GstGenericPlayer::play() +void GstGenericPlayer::play(bool &async) { - if (m_workerThread) + if (0 == m_ongoingStateChangesNumber) { - m_workerThread->enqueueTask(m_taskFactory->createPlay(*this)); + // Operation called on main thread, because PAUSED->PLAYING change is synchronous and needs to be done fast. + // + // m_context.pipeline can be used, because it's modified only in GstGenericPlayer + // constructor and destructor. GstGenericPlayer is created/destructed on main thread, so we won't have a crash here. + ++m_ongoingStateChangesNumber; + async = (changePipelineState(GST_STATE_PLAYING) == GST_STATE_CHANGE_ASYNC); + RIALTO_SERVER_LOG_MIL("State change to PLAYING requested"); + } + else + { + ++m_ongoingStateChangesNumber; + async = true; + if (m_workerThread) + { + m_workerThread->enqueueTask(m_taskFactory->createPlay(*this)); + } } } void GstGenericPlayer::pause() { + ++m_ongoingStateChangesNumber; if (m_workerThread) { m_workerThread->enqueueTask(m_taskFactory->createPause(m_context, *this)); @@ -1196,29 +1195,33 @@ void GstGenericPlayer::pause() void GstGenericPlayer::stop() { + ++m_ongoingStateChangesNumber; if (m_workerThread) { m_workerThread->enqueueTask(m_taskFactory->createStop(m_context, *this)); } } -bool GstGenericPlayer::changePipelineState(GstState newState) +GstStateChangeReturn GstGenericPlayer::changePipelineState(GstState newState) { if (!m_context.pipeline) { RIALTO_SERVER_LOG_ERROR("Change state failed - pipeline is nullptr"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; + --m_ongoingStateChangesNumber; + return GST_STATE_CHANGE_FAILURE; } - if (m_gstWrapper->gstElementSetState(m_context.pipeline, newState) == GST_STATE_CHANGE_FAILURE) + m_context.flushOnPrerollController->setTargetState(newState); + const GstStateChangeReturn result{m_gstWrapper->gstElementSetState(m_context.pipeline, newState)}; + if (result == GST_STATE_CHANGE_FAILURE) { RIALTO_SERVER_LOG_ERROR("Change state failed - Gstreamer returned an error"); if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::FAILURE); - return false; } - return true; + --m_ongoingStateChangesNumber; + return result; } int64_t GstGenericPlayer::getPosition(GstElement *element) @@ -2033,7 +2036,7 @@ void GstGenericPlayer::flush(const MediaSourceType &mediaSourceType, bool resetT { async = isAsync(mediaSourceType); m_flushWatcher->setFlushing(mediaSourceType, async); - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); + m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime, async)); } } diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index ef6aa666a..551730e15 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -125,8 +125,8 @@ GstWebAudioPlayer::GstWebAudioPlayer(IGstWebAudioPlayerClient *client, const uin } if ((!gstDispatcherThreadFactory) || - (!(m_gstDispatcherThread = - gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, m_gstWrapper)))) + (!(m_gstDispatcherThread = gstDispatcherThreadFactory->createGstDispatcherThread(*this, m_context.pipeline, + nullptr, m_gstWrapper)))) { termWebAudioPipeline(); resetWorkerThread(); diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 9c7c5196f..1b8dae988 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -25,10 +25,10 @@ namespace firebolt::rialto::server::tasks::generic { Flush::Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, - std::shared_ptr gstWrapper, const MediaSourceType &type, - bool resetTime) + const std::shared_ptr &gstWrapper, const MediaSourceType &type, + bool resetTime, bool isAsync) : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, - m_resetTime{resetTime} + m_resetTime{resetTime}, m_isAsync{isAsync} { RIALTO_SERVER_LOG_DEBUG("Constructing Flush"); } @@ -40,13 +40,6 @@ Flush::~Flush() void Flush::execute() const { - if (m_context.flushOnPrerollController.shouldPostponeFlush(m_type)) - { - RIALTO_SERVER_LOG_WARN("Postponing Flush for %s source", common::convertMediaSourceType(m_type)); - m_player.postponeFlush(m_type, m_resetTime); - return; - } - RIALTO_SERVER_LOG_DEBUG("Executing Flush for %s source", common::convertMediaSourceType(m_type)); // Get source first @@ -84,7 +77,7 @@ void Flush::execute() const if (GST_STATE(m_context.pipeline) >= GST_STATE_PAUSED) { m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); - m_context.flushOnPrerollController.setFlushing(m_type, GST_STATE(m_context.pipeline)); + m_context.flushOnPrerollController->waitIfRequired(m_type); // Flush source GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); @@ -98,6 +91,11 @@ void Flush::execute() const { RIALTO_SERVER_LOG_WARN("failed to send flush-stop event for %s", common::convertMediaSourceType(m_type)); } + + if (m_isAsync) + { + m_context.flushOnPrerollController->setFlushing(m_type); + } } else { diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 2b23743fc..318f04379 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -287,9 +287,9 @@ std::unique_ptr GenericPlayerTaskFactory::createPing(std::unique_pt std::unique_ptr GenericPlayerTaskFactory::createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, - bool resetTime) const + bool resetTime, bool isAsync) const { - return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime); + return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime, isAsync); } std::unique_ptr diff --git a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp index a6bbdcb8f..82c5807fb 100644 --- a/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp +++ b/media/server/gstplayer/source/tasks/generic/HandleBusMessage.cpp @@ -71,7 +71,6 @@ void HandleBusMessage::execute() const { case GST_STATE_NULL: { - m_context.flushOnPrerollController.reset(); m_gstPlayerClient->notifyPlaybackState(PlaybackState::STOPPED); break; } @@ -79,7 +78,6 @@ void HandleBusMessage::execute() const { if (pending != GST_STATE_PAUSED) { - m_context.flushOnPrerollController.stateReached(newState); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. @@ -103,8 +101,6 @@ void HandleBusMessage::execute() const } case GST_STATE_PLAYING: { - m_context.flushOnPrerollController.stateReached(newState); - m_player.executePostponedFlushes(); // If async flush was requested before HandleBusMessage task creation (but it was not executed yet) // or if async flush was created after HandleBusMessage task creation (but before its execution) // we can't report playback state, because async flush causes state loss - reported state is probably invalid. diff --git a/media/server/ipc/source/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 77c1387aa..7e08ec2f7 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -566,11 +566,13 @@ void MediaPipelineModuleService::play(::google::protobuf::RpcController *control ::firebolt::rialto::PlayResponse *response, ::google::protobuf::Closure *done) { RIALTO_SERVER_LOG_DEBUG("entry:"); - if (!m_mediaPipelineService.play(request->session_id())) + bool async{false}; + if (!m_mediaPipelineService.play(request->session_id(), async)) { RIALTO_SERVER_LOG_ERROR("Play failed"); controller->SetFailed("Operation failed"); } + response->set_async(async); done->Run(); } diff --git a/media/server/main/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index e546ee795..cfd0cd2a7 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -99,7 +99,7 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool allSourcesAttached() override; - bool play() override; + bool play(bool &async) override; bool pause() override; @@ -346,9 +346,11 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public /** * @brief Play internally, only to be called on the main thread. * + * @param[out] async : True if play method call is asynchronous + * * @retval true on success. */ - bool playInternal(); + bool playInternal(bool &async); /** * @brief Pause internally, only to be called on the main thread. diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index 5d19dc02f..3cd73fdfd 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -334,18 +334,18 @@ bool MediaPipelineServerInternal::allSourcesAttachedInternal() return true; } -bool MediaPipelineServerInternal::play() +bool MediaPipelineServerInternal::play(bool &async) { RIALTO_SERVER_LOG_DEBUG("entry:"); bool result; - auto task = [&]() { result = playInternal(); }; + auto task = [&]() { result = playInternal(async); }; - m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + m_mainThread->enqueuePriorityTaskAndWait(m_mainThreadClientId, task); return result; } -bool MediaPipelineServerInternal::playInternal() +bool MediaPipelineServerInternal::playInternal(bool &async) { if (!m_gstPlayer) { @@ -353,7 +353,7 @@ bool MediaPipelineServerInternal::playInternal() return false; } - m_gstPlayer->play(); + m_gstPlayer->play(async); return true; } @@ -1151,6 +1151,8 @@ bool MediaPipelineServerInternal::flushInternal(int32_t sourceId, bool resetTime m_gstPlayer->flush(sourceIter->first, resetTime, async); + m_needMediaDataTimers.erase(sourceIter->first); + // Reset Eos on flush auto it = m_isMediaTypeEosMap.find(sourceIter->first); if (it != m_isMediaTypeEosMap.end() && it->second) diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index 7ba9e8a1c..ae1fc5e04 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -48,7 +48,7 @@ class IMediaPipelineService virtual bool attachSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool removeSource(int sessionId, std::int32_t sourceId) = 0; virtual bool allSourcesAttached(int sessionId) = 0; - virtual bool play(int sessionId) = 0; + virtual bool play(int sessionId, bool &async) = 0; virtual bool pause(int sessionId) = 0; virtual bool stop(int sessionId) = 0; virtual bool setPlaybackRate(int sessionId, double rate) = 0; diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index 5e7c40063..5d2212017 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -169,7 +169,7 @@ bool MediaPipelineService::allSourcesAttached(int sessionId) return mediaPipelineIter->second->allSourcesAttached(); } -bool MediaPipelineService::play(int sessionId) +bool MediaPipelineService::play(int sessionId, bool &async) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to play, session id: %d", sessionId); @@ -180,7 +180,7 @@ bool MediaPipelineService::play(int sessionId) RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); return false; } - return mediaPipelineIter->second->play(); + return mediaPipelineIter->second->play(async); } bool MediaPipelineService::pause(int sessionId) diff --git a/media/server/service/source/MediaPipelineService.h b/media/server/service/source/MediaPipelineService.h index d68edadff..bf9ec234f 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -59,7 +59,7 @@ class MediaPipelineService : public IMediaPipelineService bool attachSource(int sessionId, const std::unique_ptr &source) override; bool removeSource(int sessionId, std::int32_t sourceId) override; bool allSourcesAttached(int sessionId) override; - bool play(int sessionId) override; + bool play(int sessionId, bool &async) override; bool pause(int sessionId) override; bool stop(int sessionId) override; bool setPlaybackRate(int sessionId, double rate) override; diff --git a/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index f56a19970..1807810de 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -261,7 +261,8 @@ message SetVideoWindowResponse { * @fn void play(int session_id) * @brief Starts playback on a session. * - * @param[in] session_id The id of the A/V session. + * @param[in] session_id The id of the A/V session. + * @param[out] async True if play method call is asynchronous * * This method is asynchronous. Once the backend is successfully playing it will notify the media player client of * playback state. @@ -271,6 +272,7 @@ message PlayRequest { optional int32 session_id = 1 [default = -1]; } message PlayResponse { + optional bool async = 1 [default = true]; } /** diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index 51ef2b5d4..e91d67bbb 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -892,7 +892,8 @@ void MediaPipelineTestMethods::shouldNotifyPlaybackStateFailure() void MediaPipelineTestMethods::playFailure() { - EXPECT_EQ(m_mediaPipeline->play(), false); + bool async{false}; + EXPECT_EQ(m_mediaPipeline->play(async), false); } void MediaPipelineTestMethods::pauseFailure() @@ -2033,7 +2034,8 @@ void MediaPipelineTestMethods::haveDataInternal(const std::unique_ptr &mediaPipeline, const bool status) { - EXPECT_EQ(mediaPipeline->play(), status); + bool async{false}; + EXPECT_EQ(mediaPipeline->play(async), status); } void MediaPipelineTestMethods::sendNotifyPlaybackStateInternal(const int32_t sessionId, const PlaybackState &state) diff --git a/tests/componenttests/server/common/ExpectMessage.h b/tests/componenttests/server/common/ExpectMessage.h index 839319e37..bd140f92a 100644 --- a/tests/componenttests/server/common/ExpectMessage.h +++ b/tests/componenttests/server/common/ExpectMessage.h @@ -70,7 +70,7 @@ template class ExpectMessage EventRanger &m_eventRanger; std::shared_ptr m_message{nullptr}; std::function m_filter{[](const MessageType &) { return true; }}; - std::chrono::milliseconds m_timeout{400}; + std::chrono::milliseconds m_timeout{600}; }; } // namespace firebolt::rialto::server::ct diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp index 2db4edf0b..dec195144 100644 --- a/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/PlayPauseTest.cpp @@ -43,12 +43,13 @@ class RialtoClientMediaPipelineIpcPlayPauseTest : public MediaPipelineIpcTestBas */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) { + bool async{false}; expectIpcApiCallSuccess(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), m_controllerMock.get(), playRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -56,10 +57,11 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) { + bool async{false}; expectIpcApiCallDisconnected(); expectUnsubscribeEvents(); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); // Reattach channel on destroySession EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); @@ -71,13 +73,14 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayChannelDisconnected) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) { + bool async{false}; expectIpcApiCallReconnected(); expectUnsubscribeEvents(); expectSubscribeEvents(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), true); + EXPECT_EQ(m_mediaPipelineIpc->play(async), true); } /** @@ -85,11 +88,12 @@ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayReconnectChannel) */ TEST_F(RialtoClientMediaPipelineIpcPlayPauseTest, PlayFailure) { + bool async{false}; expectIpcApiCallFailure(); EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("play"), _, _, _, _)); - EXPECT_EQ(m_mediaPipelineIpc->play(), false); + EXPECT_EQ(m_mediaPipelineIpc->play(async), false); } /** diff --git a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp index be98cad21..6e22bdf4d 100644 --- a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp @@ -100,8 +100,9 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// - EXPECT_CALL(*mediaPipelineMock, play()).WillOnce(Return(true)); - EXPECT_TRUE(proxy->play()); + bool async{false}; + EXPECT_CALL(*mediaPipelineMock, play(_)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->play(async)); ///////////////////////////////////////////// diff --git a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp index a58b80891..8c78c550f 100644 --- a/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/PlayPauseTest.cpp @@ -42,9 +42,10 @@ class RialtoClientMediaPipelinePlayPauseTest : public MediaPipelineTestBase */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(true)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(true)); - EXPECT_EQ(m_mediaPipeline->play(), true); + EXPECT_EQ(m_mediaPipeline->play(async), true); } /** @@ -52,9 +53,10 @@ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlaySuccess) */ TEST_F(RialtoClientMediaPipelinePlayPauseTest, PlayFailure) { - EXPECT_CALL(*m_mediaPipelineIpcMock, play()).WillOnce(Return(false)); + bool async{false}; + EXPECT_CALL(*m_mediaPipelineIpcMock, play(_)).WillOnce(Return(false)); - EXPECT_EQ(m_mediaPipeline->play(), false); + EXPECT_EQ(m_mediaPipeline->play(async), false); } /** diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index ed99bfb8e..32e72ba2c 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -39,7 +39,7 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, allSourcesAttached, (), (override)); MOCK_METHOD(bool, load, (MediaType type, const std::string &mimeType, const std::string &url), (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, haveData, (MediaSourceStatus status, uint32_t numFrames, uint32_t requestId), (override)); diff --git a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h index 0d1427fd4..c69140210 100644 --- a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h +++ b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h @@ -39,7 +39,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); diff --git a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp index 54aeffd82..2805d68ab 100644 --- a/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp +++ b/tests/unittests/media/server/gstplayer/dispatcherThread/GstDispatcherThreadTest.cpp @@ -18,6 +18,7 @@ */ #include "GstDispatcherThread.h" +#include "FlushOnPrerollControllerMock.h" #include "GenericPlayerTaskFactoryMock.h" #include "GstDispatcherThreadClientMock.h" #include "GstWrapperMock.h" @@ -56,6 +57,8 @@ class GstDispatcherThreadTest : public ::testing::Test dynamic_cast &>(*workerThreadFactory)}; std::unique_ptr workerThread{std::make_unique>()}; StrictMock &m_workerThreadMock{dynamic_cast &>(*workerThread)}; + std::shared_ptr> m_flushOnPrerollControllerMock{ + std::make_shared>()}; std::mutex m_dispatcherThreadMutex; std::condition_variable m_dispatcherThreadCond; @@ -88,7 +91,8 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -98,7 +102,7 @@ TEST_F(GstDispatcherThreadTest, PollTimeout) } /** - * Test that a GST_MESSAGE_STATE_CHANGED message is handled correctly. + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED) is handled correctly. */ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) { @@ -127,7 +131,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); EXPECT_CALL(m_client, handleBusMessage(_)); } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PLAYING) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPlaying) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PLAYING; + GstState pending = GST_STATE_VOID_PENDING; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, stateReached(GST_STATE_PLAYING)); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -137,7 +191,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPaused) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -163,6 +218,57 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); EXPECT_CALL(m_client, handleBusMessage(_)); + EXPECT_CALL(*m_flushOnPrerollControllerMock, reset()); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) + .WillOnce(Invoke( + [this](gpointer bus) + { + std::unique_lock lock(m_dispatcherThreadMutex); + m_dispatcherThreadDone = true; + m_dispatcherThreadCond.notify_all(); + })); + + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); + + // wait for dispatcher thread + std::unique_lock dispatcherLock(m_dispatcherThreadMutex); + bool status = m_dispatcherThreadCond.wait_for(dispatcherLock, std::chrono::milliseconds(200), + [this]() { return m_dispatcherThreadDone; }); + EXPECT_TRUE(status); +} + +/** + * Test that a GST_MESSAGE_STATE_CHANGED message (to GST_STATE_PAUSED, pending PAUSED) is handled correctly. + */ +TEST_F(GstDispatcherThreadTest, StateChangedToPrerolling) +{ + GST_MESSAGE_SRC(&m_message) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&m_message) = GST_MESSAGE_STATE_CHANGED; + + GstState oldState = GST_STATE_READY; + GstState newState = GST_STATE_PAUSED; + GstState pending = GST_STATE_PAUSED; + + GstMessage messageError = {}; + GST_MESSAGE_SRC(&messageError) = GST_OBJECT(&m_pipeline); + GST_MESSAGE_TYPE(&messageError) = GST_MESSAGE_ERROR; + + EXPECT_CALL(*m_gstWrapperMock, gstPipelineGetBus(GST_PIPELINE(&m_pipeline))).WillOnce(Return(&m_bus)); + + EXPECT_CALL(*m_gstWrapperMock, gstMessageParseStateChanged(&m_message, _, _, _)) + .WillOnce(DoAll(SetArgPointee<1>(oldState), SetArgPointee<2>(newState), SetArgPointee<3>(pending))); + + { + InSequence seq; + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&m_message)); + EXPECT_CALL(m_client, handleBusMessage(_)); + + // Signal error to stop the thread + EXPECT_CALL(*m_gstWrapperMock, gstBusTimedPopFiltered(&m_bus, 100 * GST_MSECOND, _)).WillOnce(Return(&messageError)); + EXPECT_CALL(m_client, handleBusMessage(_)); + } + EXPECT_CALL(*m_flushOnPrerollControllerMock, setPrerolling()); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&m_bus)) .WillOnce(Invoke( [this](gpointer bus) @@ -172,7 +278,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToStop) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -200,7 +307,8 @@ TEST_F(GstDispatcherThreadTest, Error) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); @@ -243,7 +351,8 @@ TEST_F(GstDispatcherThreadTest, StateChangedToPausedNonPipeline) m_dispatcherThreadCond.notify_all(); })); - auto sut = std::make_unique(m_client, &m_pipeline, m_gstWrapperMock); + auto sut = + std::make_unique(m_client, &m_pipeline, m_flushOnPrerollControllerMock, m_gstWrapperMock); // wait for dispatcher thread std::unique_lock dispatcherLock(m_dispatcherThreadMutex); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp index 803ae1eb3..ed5a0904b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/FlushOnPrerollControllerTest.cpp @@ -19,6 +19,7 @@ #include "FlushOnPrerollController.h" #include +#include using firebolt::rialto::MediaSourceType; using firebolt::rialto::server::FlushOnPrerollController; @@ -29,37 +30,65 @@ class FlushOnPrerollControllerTest : public ::testing::Test FlushOnPrerollController m_sut; }; -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNoFlushSet) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaithWhenNoFlushSet) { - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeFlushWhenNotPrerolled) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenNotPrerolled) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldPostponeAudioFlush) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenReset) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - EXPECT_TRUE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::VIDEO)); + m_sut.reset(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenReset) +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPrerolling) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.setPrerolling(); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWhenPreviousProcedureIsFinished) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); - m_sut.reset(); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + m_sut.stateReached(GST_STATE_PLAYING); + m_sut.waitIfRequired(MediaSourceType::AUDIO); + // No deadlock here +} + +TEST_F(FlushOnPrerollControllerTest, shouldNotWaitWithVideoFlushWhenOnlyAudioIsOngoing) +{ + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); + m_sut.stateReached(GST_STATE_PAUSED); + m_sut.waitIfRequired(MediaSourceType::VIDEO); + // No deadlock here } -TEST_F(FlushOnPrerollControllerTest, shouldNotPostponeAudioFlushWhenPreviousProcedureIsFinished) +TEST_F(FlushOnPrerollControllerTest, shouldWaitForAudioFlushFinish) { - m_sut.setFlushing(MediaSourceType::AUDIO, GST_STATE_PLAYING); + m_sut.setTargetState(GST_STATE_PLAYING); + m_sut.setFlushing(MediaSourceType::AUDIO); m_sut.stateReached(GST_STATE_PAUSED); + std::thread waitThread([this]() { m_sut.waitIfRequired(MediaSourceType::AUDIO); }); m_sut.stateReached(GST_STATE_PLAYING); - EXPECT_FALSE(m_sut.shouldPostponeFlush(MediaSourceType::AUDIO)); + waitThread.join(); + // No deadlock here } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 5129fcf12..be547e88f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -2080,15 +2080,3 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetShowVideoWindow) EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)); EXPECT_TRUE(m_sut->setShowVideoWindow()); } - -TEST_F(GstGenericPlayerPrivateTest, shouldExecutePostponedFlush) -{ - constexpr MediaSourceType kSourceType{MediaSourceType::AUDIO}; - constexpr bool kResetTime{true}; - m_sut->postponeFlush(kSourceType, kResetTime); - - std::unique_ptr task{std::make_unique>()}; - EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, kSourceType, kResetTime)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->executePostponedFlushes(); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index 752bc95fe..f633cb441 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -155,13 +155,40 @@ TEST_F(GstGenericPlayerTest, shouldAllSourcesAttached) m_sut->allSourcesAttached(); } -TEST_F(GstGenericPlayerTest, shouldPlay) +TEST_F(GstGenericPlayerTest, shouldPlayOnWorkerThread) { + // Pause first + std::unique_ptr pauseTask{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*pauseTask), execute()); + EXPECT_CALL(m_taskFactoryMock, createPause(_, _)).WillOnce(Return(ByMove(std::move(pauseTask)))); + + m_sut->pause(); + + // ... + + bool async = false; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); EXPECT_CALL(m_taskFactoryMock, createPlay(_)).WillOnce(Return(ByMove(std::move(task)))); - m_sut->play(); + m_sut->play(async); + EXPECT_TRUE(async); +} + +TEST_F(GstGenericPlayerTest, shouldPlayImmediatelySynchronously) +{ + bool async = false; + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(_, GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + m_sut->play(async); + EXPECT_FALSE(async); +} + +TEST_F(GstGenericPlayerTest, shouldPlayImmediatelyAsynchronously) +{ + bool async = false; + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(_, GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_ASYNC)); + m_sut->play(async); + EXPECT_TRUE(async); } TEST_F(GstGenericPlayerTest, shouldPause) @@ -880,7 +907,7 @@ TEST_F(GstGenericPlayerTest, shouldFlush) })); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)); EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime)) + EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::VIDEO, kResetTime, isAsync)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->flush(MediaSourceType::VIDEO, kResetTime, isAsync); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index 15341d800..bb73b346d 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -138,6 +138,7 @@ constexpr uint64_t kStopPosition{4523}; const std::vector kStreamHeaderVector{1, 2, 3, 4}; constexpr bool kFramed{true}; constexpr uint64_t kDisplayOffset{35}; +constexpr bool kIsAsync{true}; firebolt::rialto::IMediaPipeline::MediaSegmentVector buildAudioSamples() { @@ -2244,7 +2245,7 @@ void GenericTasksTestsBase::shouldStopGstPlayer() videoStreamIt->second.isDataNeeded = true; audioStreamIt->second.isDataNeeded = true; EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerStop() @@ -2560,12 +2561,12 @@ void GenericTasksTestsBase::checkNoEos() void GenericTasksTestsBase::shouldChangeStatePlayingSuccess() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(true)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::shouldChangeStatePlayingFailure() { - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(false)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PLAYING)).WillOnce(Return(GST_STATE_CHANGE_FAILURE)); } void GenericTasksTestsBase::triggerPlay() @@ -2583,7 +2584,7 @@ void GenericTasksTestsBase::triggerPing() void GenericTasksTestsBase::shouldPause() { EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); - EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)); + EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_PAUSED)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); } void GenericTasksTestsBase::triggerPause() @@ -3075,7 +3076,8 @@ void GenericTasksTestsBase::triggerFlush(firebolt::rialto::MediaSourceType sourc &testContext->m_gstPlayerClient, testContext->m_gstWrapper, sourceType, - kResetTime}; + kResetTime, + kIsAsync}; task.execute(); } @@ -3120,14 +3122,6 @@ void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() .WillOnce(Return(TRUE)); } -void GenericTasksTestsBase::shouldPostponeVideoFlush() -{ - testContext->m_context.flushOnPrerollController.setFlushing(firebolt::rialto::MediaSourceType::VIDEO, - GST_STATE_PLAYING); - testContext->m_context.flushOnPrerollController.stateReached(GST_STATE_PAUSED); - EXPECT_CALL(testContext->m_gstPlayer, postponeFlush(firebolt::rialto::MediaSourceType::VIDEO, kResetTime)); -} - void GenericTasksTestsBase::shouldSetSubtitleSourcePosition() { EXPECT_CALL(*testContext->m_glibWrapper, gObjectSetStub(&testContext->m_textTrackSink, StrEq("position"))); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index bc07871f7..29f64e16a 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -403,7 +403,6 @@ class GenericTasksTestsBase : public ::testing::Test void checkAudioFlushed(); void checkVideoFlushed(); void shouldFlushVideoSrcSuccess(); - void shouldPostponeVideoFlush(); // Set Source Position test methods void shouldSetSubtitleSourcePosition(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp index 22c7b56b5..3920d589a 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp @@ -199,7 +199,7 @@ void GstGenericPlayerTestCommon::expectCheckPlaySink() void GstGenericPlayerTestCommon::expectSetMessageCallback() { - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp index 64017d24d..6603d681b 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp @@ -108,9 +108,3 @@ TEST_F(FlushTest, ShouldFlushVideoWithNeedData) triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); checkVideoFlushed(); } - -TEST_F(FlushTest, ShouldPostponeFlush) -{ - shouldPostponeVideoFlush(); - triggerFlush(firebolt::rialto::MediaSourceType::VIDEO); -} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 8d81d3bf0..70c305e22 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -318,7 +318,7 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreatePing) TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateFlush) { - auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true); + auto task = m_sut.createFlush(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::AUDIO, true, true); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp index 1295f2b9b..d9b1c09e2 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/HandleBusMessageTest.cpp @@ -417,7 +417,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessage) EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -450,7 +449,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageWhenSyncFlu EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -480,7 +478,6 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -508,7 +505,6 @@ TEST_F(HandleBusMessageTest, shouldSkipHandlingStateChangedToPlayingMessageWhenA EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillOnce(Return(kIsFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillOnce(Return(kNoFlushOngoing)).WillOnce(Return(kIsFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, @@ -538,7 +534,6 @@ TEST_F(HandleBusMessageTest, shouldHandleStateChangedToPlayingMessageAndSetPendi EXPECT_CALL(*m_gstWrapper, gstMessageUnref(&m_message)); EXPECT_CALL(m_flushWatcherMock, isFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); EXPECT_CALL(m_flushWatcherMock, isAsyncFlushOngoing()).WillRepeatedly(Return(kNoFlushOngoing)); - EXPECT_CALL(m_gstPlayer, executePostponedFlushes()); firebolt::rialto::server::tasks::generic::HandleBusMessage task{m_context, m_gstPlayer, &m_gstPlayerClient, m_gstWrapper, m_glibWrapper, &m_message, diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp index 0a61ceeb5..a0fa43a6b 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/CreateTest.cpp @@ -500,7 +500,7 @@ TEST_F(RialtoServerCreateGstWebAudioPlayerTest, createGstDispatcherThreadFailure expectInitAppSrc(); expectAddElementsAutoAudioSink(); expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)).WillOnce(Return(nullptr)); + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)).WillOnce(Return(nullptr)); // Reset worker thread and pipeline on failure gstPlayerWillBeDestroyed(); diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp index 96980db37..42e890c8d 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp @@ -107,7 +107,7 @@ void GstWebAudioPlayerTestCommon::expectInitWorkerThread() void GstWebAudioPlayerTestCommon::expectInitThreads() { expectInitWorkerThread(); - EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _)) + EXPECT_CALL(m_gstDispatcherThreadFactoryMock, createGstDispatcherThread(_, _, _, _)) .WillOnce(Return(ByMove(std::move(gstDispatcherThread)))); } diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index ac3576a74..31ec98768 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp @@ -388,13 +388,13 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailAllSourcesAtta void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPlay() { expectRequestSuccess(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(true)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToPlay() { expectRequestFailure(); - EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId)).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineServiceMock, play(kHardcodedSessionId, _)).WillOnce(Return(false)); } void MediaPipelineModuleServiceTests::mediaPipelineServiceWillPause() diff --git a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp index ce00bdee8..aa4f3e2f3 100644 --- a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp @@ -43,11 +43,12 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) { + bool async{false}; loadGstPlayer(); - mainThreadWillEnqueueTaskAndWait(); + mainThreadWillEnqueuePriorityTaskAndWait(); - EXPECT_CALL(*m_gstPlayerMock, play()); - EXPECT_TRUE(m_mediaPipeline->play()); + EXPECT_CALL(*m_gstPlayerMock, play(_)); + EXPECT_TRUE(m_mediaPipeline->play(async)); } /** @@ -55,8 +56,9 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlaySuccess) */ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, PlayFailureDueToUninitializedPlayer) { - mainThreadWillEnqueueTaskAndWait(); - EXPECT_FALSE(m_mediaPipeline->play()); + bool async{false}; + mainThreadWillEnqueuePriorityTaskAndWait(); + EXPECT_FALSE(m_mediaPipeline->play(async)); } /** diff --git a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp index 491aec0e1..1543184af 100644 --- a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.cpp @@ -82,6 +82,13 @@ void MediaPipelineTestBase::mainThreadWillEnqueueTaskAndWait() .RetiresOnSaturation(); } +void MediaPipelineTestBase::mainThreadWillEnqueuePriorityTaskAndWait() +{ + EXPECT_CALL(*m_mainThreadMock, enqueuePriorityTaskAndWait(m_kMainThreadClientId, _)) + .WillOnce(Invoke([](uint32_t clientId, firebolt::rialto::server::IMainThread::Task task) { task(); })) + .RetiresOnSaturation(); +} + void MediaPipelineTestBase::loadGstPlayer() { mainThreadWillEnqueueTaskAndWait(); diff --git a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.h b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.h index d31474d53..893353141 100644 --- a/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.h +++ b/tests/unittests/media/server/main/mediaPipeline/base/MediaPipelineTestBase.h @@ -90,6 +90,7 @@ class MediaPipelineTestBase : public ::testing::Test void destroyMediaPipeline(); void mainThreadWillEnqueueTask(); void mainThreadWillEnqueueTaskAndWait(); + void mainThreadWillEnqueuePriorityTaskAndWait(); void loadGstPlayer(); int attachSource(MediaSourceType sourceType, const std::string &mimeType); void setEos(MediaSourceType sourceType); diff --git a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h index 0a4d3fff1..b3daeec0e 100644 --- a/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/FlushOnPrerollControllerMock.h @@ -28,9 +28,11 @@ namespace firebolt::rialto::server class FlushOnPrerollControllerMock : public IFlushOnPrerollController { public: - MOCK_METHOD(bool, shouldPostponeFlush, (const MediaSourceType &type), (const, override)); - MOCK_METHOD(void, setFlushing, (const MediaSourceType &type, const GstState ¤tPipelineState), (override)); + MOCK_METHOD(void, waitIfRequired, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setFlushing, (const MediaSourceType &type), (override)); + MOCK_METHOD(void, setPrerolling, (), (override)); MOCK_METHOD(void, stateReached, (const GstState &newPipelineState), (override)); + MOCK_METHOD(void, setTargetState, (const GstState &state), (override)); MOCK_METHOD(void, reset, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 3e165fba6..2cb7f0a35 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -115,7 +115,7 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (const, override)); MOCK_METHOD(std::unique_ptr, createFlush, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, bool resetTime), + const firebolt::rialto::MediaSourceType &type, bool resetTime, bool isAsync), (const, override)); MOCK_METHOD(std::unique_ptr, createSetSourcePosition, (GenericPlayerContext & context, const firebolt::rialto::MediaSourceType &type, std::int64_t position, diff --git a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h index 95b1d947a..49b6675d0 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstDispatcherThreadFactoryMock.h @@ -31,6 +31,7 @@ class GstDispatcherThreadFactoryMock : public IGstDispatcherThreadFactory public: MOCK_METHOD(std::unique_ptr, createGstDispatcherThread, (IGstDispatcherThreadClient & client, GstElement *pipeline, + const std::shared_ptr &flushOnPrerollController, const std::shared_ptr &gstWrapper), (const, override)); }; diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index a5919a427..c38c1d9eb 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -35,7 +35,7 @@ class GstGenericPlayerMock : public IGstGenericPlayer MOCK_METHOD(void, attachSource, (const std::unique_ptr &mediaSource), (override)); MOCK_METHOD(void, allSourcesAttached, (), (override)); - MOCK_METHOD(void, play, (), (override)); + MOCK_METHOD(void, play, (bool &async), (override)); MOCK_METHOD(void, pause, (), (override)); MOCK_METHOD(void, stop, (), (override)); MOCK_METHOD(void, attachSamples, (const IMediaPipeline::MediaSegmentVector &mediaSegments), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index 5e8da6b95..068a8b0b3 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -55,7 +55,7 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, updateVideoCaps, (int32_t width, int32_t height, Fraction frameRate, const std::shared_ptr &codecData), (override)); - MOCK_METHOD(bool, changePipelineState, (GstState newState), (override)); + MOCK_METHOD(GstStateChangeReturn, changePipelineState, (GstState newState), (override)); MOCK_METHOD(int64_t, getPosition, (GstElement * element), (override)); MOCK_METHOD(void, startPositionReportingAndCheckAudioUnderflowTimer, (), (override)); MOCK_METHOD(void, stopPositionReportingAndCheckAudioUnderflowTimer, (), (override)); @@ -77,8 +77,6 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, startSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(void, stopSubtitleClockResyncTimer, (), (override)); MOCK_METHOD(bool, hasSourceType, (const MediaSourceType &mediaSourceType), (const, override)); - MOCK_METHOD(void, postponeFlush, (const MediaSourceType &mediaSourceType, bool resetTime), (override)); - MOCK_METHOD(void, executePostponedFlushes, (), (override)); MOCK_METHOD(void, notifyPlaybackInfo, (), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h index 61ee32b61..27a12a686 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -36,7 +36,7 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, attachSource, (const std::unique_ptr &source), (override)); MOCK_METHOD(bool, removeSource, (int32_t id), (override)); MOCK_METHOD(bool, allSourcesAttached, (), (override)); - MOCK_METHOD(bool, play, (), (override)); + MOCK_METHOD(bool, play, (bool &async), (override)); MOCK_METHOD(bool, pause, (), (override)); MOCK_METHOD(bool, stop, (), (override)); MOCK_METHOD(bool, setPlaybackRate, (double rate), (override)); diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index 69812124d..f7b226777 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -38,7 +38,7 @@ class MediaPipelineServiceMock : public IMediaPipelineService MOCK_METHOD(bool, attachSource, (int, const std::unique_ptr &), (override)); MOCK_METHOD(bool, removeSource, (int, std::int32_t), (override)); MOCK_METHOD(bool, allSourcesAttached, (int), (override)); - MOCK_METHOD(bool, play, (int), (override)); + MOCK_METHOD(bool, play, (int, bool &), (override)); MOCK_METHOD(bool, pause, (int), (override)); MOCK_METHOD(bool, stop, (int), (override)); MOCK_METHOD(bool, setPlaybackRate, (int, double), (override)); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp index 9b9e441dd..421cb1d8b 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -138,12 +138,12 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToAllSourcesAttached() void MediaPipelineServiceTests::mediaPipelineWillPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(true)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(true)); } void MediaPipelineServiceTests::mediaPipelineWillFailToPlay() { - EXPECT_CALL(m_mediaPipelineMock, play()).WillOnce(Return(false)); + EXPECT_CALL(m_mediaPipelineMock, play(_)).WillOnce(Return(false)); } void MediaPipelineServiceTests::mediaPipelineWillPause() @@ -624,12 +624,14 @@ void MediaPipelineServiceTests::allSourcesAttachedShouldFail() void MediaPipelineServiceTests::playShouldSucceed() { - EXPECT_TRUE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_TRUE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::playShouldFail() { - EXPECT_FALSE(m_sut->play(kSessionId)); + bool isAsync{false}; + EXPECT_FALSE(m_sut->play(kSessionId, isAsync)); } void MediaPipelineServiceTests::pauseShouldSucceed() From 41507facfa49d280d805e642b84cb5a43c688773 Mon Sep 17 00:00:00 2001 From: smudri85 Date: Wed, 15 Apr 2026 14:55:05 +0200 Subject: [PATCH 05/21] Removed blocking get_state calls from aml codec switch (#480) Summary: Removed blocking get_state calls from aml codec switch Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-13606, DELIA-70194 Co-authored-by: Adam Czynszak <92790185+aczs@users.noreply.github.com> --- .../gstplayer/include/GstGenericPlayer.h | 55 ++ .../gstplayer/source/GstGenericPlayer.cpp | 507 +++++++++++++++++- .../externalLibraryMocks/GstWrapperMock.h | 7 + .../mediaPipeline/AudioSourceSwitchTest.cpp | 17 +- .../GstGenericPlayerPrivateTest.cpp | 205 +++++++ wrappers/include/GstWrapper.h | 16 + wrappers/interface/IGstWrapper.h | 63 +++ 7 files changed, 860 insertions(+), 10 deletions(-) diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 325421690..5f4f61295 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -321,6 +321,61 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva std::optional createAudioAttributes(const std::unique_ptr &source) const; + /** + * @brief Configures audio caps based on audio attributes. + * Called by worker thread only! + * + * @param[in] pAttrib : The audio attributes. + * @param[out] audioaac : Set to true if AAC, false otherwise. + * @param[in] svpenabled : Whether SVP is enabled. + * @param[in,out] appsrcCaps : The caps to configure. + */ + void configAudioCap(firebolt::rialto::wrappers::AudioAttributesPrivate *pAttrib, bool *audioaac, bool svpenabled, + GstCaps **appsrcCaps); + + /** + * @brief Halts audio playback by setting playsink to READY and decodebin to PAUSED. + * Called by worker thread only! + */ + void haltAudioPlayback(); + + /** + * @brief Resumes audio playback by syncing playsink and decodebin with parent. + * Called by worker thread only! + */ + void resumeAudioPlayback(); + + /** + * @brief First-time codec switch from AC3 to AAC when no decoder exists yet. + * Called by worker thread only! + * + * @param[in] newAudioCaps : The new audio caps to apply. + */ + void firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps); + + /** + * @brief Switches the audio codec by unlinking old parser/decoder and linking new ones. + * Called by worker thread only! + * + * @param[in] isAudioAAC : Whether the new codec is AAC. + * @param[in] newAudioCaps : The new audio caps to apply. + * + * @retval true if codec was switched, false if same codec. + */ + bool switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps); + + /** + * @brief Top-level audio track codec channel switch, ported from rdk_gstreamer_utils_soc. + * Called by worker thread only! + */ + bool performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, bool *audioaac, + bool svpenabled, GstElement *aSrc, bool *ret); + /** * @brief Sets text track position before pushing data * diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 0c3f6b9cd..bff22559c 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include "FlushWatcher.h" @@ -620,6 +622,466 @@ GstGenericPlayer::createAudioAttributes(const std::unique_ptrm_codecParam.c_str(), pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + pAttrib->m_blockAlignment); + if (pAttrib->m_codecParam.compare(0, 4, std::string("mp4a")) == 0) + { + RIALTO_SERVER_LOG_DEBUG("Using AAC"); + capsString = m_glibWrapper->gStrdupPrintf("audio/mpeg, mpegversion=4, enable-svp=(string)%s", + svpenabled ? "true" : "false"); + *audioaac = true; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Using EAC3"); + capsString = m_glibWrapper->gStrdupPrintf("audio/x-eac3, framed=(boolean)true, rate=(int)%u, channels=(int)%u, " + "alignment=(string)frame, enable-svp=(string)%s", + pAttrib->m_samplesPerSecond, pAttrib->m_numberOfChannels, + svpenabled ? "true" : "false"); + *audioaac = false; + } + *appsrcCaps = m_gstWrapper->gstCapsFromString(capsString); + m_glibWrapper->gFree(capsString); +} + +void GstGenericPlayer::haltAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("haltAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Transition Playsink to Ready + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioPlaysinkBin, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioPlaysinkBin to READY"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioPlaySinkBin State = %d", currentState); + // Transition Decodebin to Paused + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecodeBin, GST_STATE_PAUSED)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecodeBin to PAUSED"); + return; + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_PAUSED) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current DecodeBin State = %d", currentState); +} + +void GstGenericPlayer::resumeAudioPlayback() +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioPlaysinkBin || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("resumeAudioPlayback: audio playsink bin or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioPlaysinkBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioPlaysinkbin State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioDecodeBin); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecodeBin, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> Decodebin State = %d Pending = %d", currentState, pending); +} + +void GstGenericPlayer::firstTimeSwitchFromAC3toAAC(GstCaps *newAudioCaps) +{ + // this function comes from rdk_gstreamer_utils + if (!m_context.playbackGroup.m_curAudioTypefind || !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("firstTimeSwitchFromAC3toAAC: audio typefind or decode bin is null"); + return; + } + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + GstPad *pTypfdSrcPad = NULL; + GstPad *pTypfdSrcPeerPad = NULL; + GstPad *pNewAudioDecoderSrcPad = NULL; + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstElement *newQueue = NULL; + gboolean linkRet = false; + + /* Get the SinkPad of ASink - pTypfdSrcPeerPad */ + if ((pTypfdSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioTypefind, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind SrcPad = %p", pTypfdSrcPad); + if ((pTypfdSrcPeerPad = m_gstWrapper->gstPadGetPeer(pTypfdSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current Typefind Src Downstream Element Pad = %p", pTypfdSrcPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(pTypfdSrcPad, pTypfdSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Downstream Unlink Failed"); + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + newQueue = m_gstWrapper->gstElementFactoryMake("queue", "aqueue"); + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + // Add new Queue to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newQueue) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New queue = %p", newQueue); + } + if ((pNewAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", pNewAudioDecoderSrcPad); + // Connect decoder to ASINK + if (m_gstWrapper->gstPadLink(pNewAudioDecoderSrcPad, pTypfdSrcPeerPad) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + linkRet = m_gstWrapper->gstElementLink(newAudioParse, newQueue) && + m_gstWrapper->gstElementLink(newQueue, newAudioDecoder); + if (!linkRet) + RIALTO_SERVER_LOG_DEBUG("OTF -> Downstream Link Failed for typefind, parser, decoder"); + /* Force Caps */ + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioTypefind, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY"); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; + } + m_glibWrapper->gObjectSet(G_OBJECT(m_context.playbackGroup.m_curAudioTypefind), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(m_context.playbackGroup.m_curAudioTypefind); + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioTypefind, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + /* Update the state */ + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newQueue); + m_gstWrapper->gstElementGetState(newQueue, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New queue State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstObjectUnref(pTypfdSrcPad); + m_gstWrapper->gstObjectUnref(pTypfdSrcPeerPad); + m_gstWrapper->gstObjectUnref(pNewAudioDecoderSrcPad); + return; +} + +bool GstGenericPlayer::switchAudioCodec(bool isAudioAAC, GstCaps *newAudioCaps) +{ // this function comes from rdk_gstreamer_utils + bool ret = false; + RIALTO_SERVER_LOG_DEBUG("Current Audio Codec AAC = %d Same as Incoming audio Codec AAC = %d", + m_context.playbackGroup.m_isAudioAAC, isAudioAAC); + if (m_context.playbackGroup.m_isAudioAAC == isAudioAAC) + { + return ret; + } + if ((m_context.playbackGroup.m_curAudioDecoder == NULL) && (!(m_context.playbackGroup.m_isAudioAAC)) && (isAudioAAC)) + { + firstTimeSwitchFromAC3toAAC(newAudioCaps); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; + } + if (!m_context.playbackGroup.m_curAudioDecoder || !m_context.playbackGroup.m_curAudioParse || + !m_context.playbackGroup.m_curAudioDecodeBin) + { + RIALTO_SERVER_LOG_ERROR("switchAudioCodec: audio decoder, parser or decode bin is null"); + return false; + } + GstElement *newAudioParse = NULL; + GstElement *newAudioDecoder = NULL; + GstPad *newAudioParseSrcPad = NULL; + GstPad *newAudioParseSinkPad = NULL; + GstPad *newAudioDecoderSrcPad = NULL; + GstPad *newAudioDecoderSinkPad = NULL; + GstPad *audioDecSrcPad = NULL; + GstPad *audioDecSinkPad = NULL; + GstPad *audioDecSrcPeerPad = NULL; + GstPad *audioDecSinkPeerPad = NULL; + GstPad *audioParseSrcPad = NULL; + GstPad *audioParseSinkPad = NULL; + GstPad *audioParseSrcPeerPad = NULL; + GstPad *audioParseSinkPeerPad = NULL; + GstState currentState{GST_STATE_VOID_PENDING}, pending{GST_STATE_VOID_PENDING}; + + // Get AudioDecoder Src Pads + if ((audioDecSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Pad = %p", audioDecSrcPad); + // Get AudioDecoder Sink Pads + if ((audioDecSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Pad = %p", audioDecSinkPad); + // Get AudioDecoder Src Peer i.e. Downstream Element Pad + if ((audioDecSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSrcPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Src Downstream Element Pad = %p", audioDecSrcPeerPad); + // Get AudioDecoder Sink Peer i.e. Upstream Element Pad + if ((audioDecSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioDecSinkPad)) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder Sink Upstream Element Pad = %p", audioDecSinkPeerPad); + // Get AudioParser Src Pads + if ((audioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Pad = %p", audioParseSrcPad); + // Get AudioParser Sink Pads + if ((audioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(m_context.playbackGroup.m_curAudioParse, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Pad = %p", audioParseSinkPad); + // Get AudioParser Src Peer i.e. Downstream Element Pad + if ((audioParseSrcPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSrcPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Src Downstream Element Pad = %p", audioParseSrcPeerPad); + // Get AudioParser Sink Peer i.e. Upstream Element Pad + if ((audioParseSinkPeerPad = m_gstWrapper->gstPadGetPeer(audioParseSinkPad)) != NULL) // Unref the Peer Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser Sink Upstream Element Pad = %p", audioParseSinkPeerPad); + // AudioDecoder Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSrcPad, audioDecSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Downstream Unlink Failed"); + // AudioDecoder Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioDecSinkPeerPad, audioDecSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioDecoder Upstream Unlink Failed"); + // AudioParser Downstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSrcPad, audioParseSrcPeerPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Downstream Unlink Failed"); + // AudioParser Upstream Unlink + if (m_gstWrapper->gstPadUnlink(audioParseSinkPeerPad, audioParseSinkPad) == FALSE) + RIALTO_SERVER_LOG_DEBUG("OTF -> AudioParser Upstream Unlink Failed"); + // Current Audio Decoder NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioDecoder, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioDecoder to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioDecoder, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioDecoder State = %d", currentState); + // Current Audio Parser NULL + if (GST_STATE_CHANGE_FAILURE == + m_gstWrapper->gstElementSetState(m_context.playbackGroup.m_curAudioParse, GST_STATE_NULL)) + { + RIALTO_SERVER_LOG_WARN("Failed to set AudioParser to NULL"); + } + m_gstWrapper->gstElementGetState(m_context.playbackGroup.m_curAudioParse, ¤tState, &pending, + GST_CLOCK_TIME_NONE); + if (currentState == GST_STATE_NULL) + RIALTO_SERVER_LOG_DEBUG("OTF -> Current AudioParser State = %d", currentState); + // Remove Audio Decoder From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioDecoder = %p", m_context.playbackGroup.m_curAudioDecoder); + m_context.playbackGroup.m_curAudioDecoder = NULL; + } + // Remove Audio Parser From Decodebin + if (m_gstWrapper->gstBinRemove(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), + m_context.playbackGroup.m_curAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Removed AudioParser = %p", m_context.playbackGroup.m_curAudioParse); + m_context.playbackGroup.m_curAudioParse = NULL; + } + // Create new Audio Decoder and Parser. The inverse of the current + if (m_context.playbackGroup.m_isAudioAAC) + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("ac3parse", "ac3parse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("identity", "fake_aud_ac3dec"); + } + else + { + newAudioParse = m_gstWrapper->gstElementFactoryMake("aacparse", "aacparse"); + newAudioDecoder = m_gstWrapper->gstElementFactoryMake("avdec_aac", "avdec_aac"); + } + { + GstPadLinkReturn gstPadLinkRet = GST_PAD_LINK_OK; + GstElement *audioParseUpstreamEl = NULL; + // Add new Decoder to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioDecoder) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioDecoder = %p", newAudioDecoder); + } + // Add new Parser to Decodebin + if (m_gstWrapper->gstBinAdd(GST_BIN(m_context.playbackGroup.m_curAudioDecodeBin.load()), newAudioParse) == TRUE) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Added New AudioParser = %p", newAudioParse); + } + if ((newAudioDecoderSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "src")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Src Pad = %p", newAudioDecoderSrcPad); + if ((newAudioDecoderSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioDecoder, "sink")) != + NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Sink Pad = %p", newAudioDecoderSinkPad); + // Link New Decoder to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioDecoderSrcPad, audioDecSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Downstream Link Failed"); + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(audioDecSinkPeerPad, newAudioDecoderSinkPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder Upstream Link Failed"); + if ((newAudioParseSrcPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "src")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Src Pad = %p", newAudioParseSrcPad); + if ((newAudioParseSinkPad = m_gstWrapper->gstElementGetStaticPad(newAudioParse, "sink")) != NULL) // Unref the Pad + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Sink Pad = %p", newAudioParseSinkPad); + // Link New Parser to Downstream followed by UpStream + if ((gstPadLinkRet = m_gstWrapper->gstPadLink(newAudioParseSrcPad, audioParseSrcPeerPad)) != GST_PAD_LINK_OK) + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser Downstream Link Failed %d", gstPadLinkRet); + if ((audioParseUpstreamEl = GST_ELEMENT_CAST(m_gstWrapper->gstPadGetParent(audioParseSinkPeerPad))) == + m_context.playbackGroup.m_curAudioTypefind) + { + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Setting to READY"); + if (GST_STATE_CHANGE_FAILURE == m_gstWrapper->gstElementSetState(audioParseUpstreamEl, GST_STATE_READY)) + { + RIALTO_SERVER_LOG_WARN("Failed to set Typefind to READY in switchAudioCodec"); + } + m_glibWrapper->gObjectSet(G_OBJECT(audioParseUpstreamEl), "force-caps", newAudioCaps, NULL); + m_gstWrapper->gstElementSyncStateWithParent(audioParseUpstreamEl); + m_gstWrapper->gstElementGetState(audioParseUpstreamEl, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New Typefind State = %d Pending = %d", currentState, pending); + RIALTO_SERVER_LOG_DEBUG("OTF -> Typefind Syncing with Parent"); + m_context.playbackGroup.m_linkTypefindParser = true; + m_gstWrapper->gstObjectUnref(audioParseUpstreamEl); + } + m_gstWrapper->gstObjectUnref(newAudioDecoderSrcPad); + m_gstWrapper->gstObjectUnref(newAudioDecoderSinkPad); + m_gstWrapper->gstObjectUnref(newAudioParseSrcPad); + m_gstWrapper->gstObjectUnref(newAudioParseSinkPad); + } + m_gstWrapper->gstObjectUnref(audioParseSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioParseSinkPad); + m_gstWrapper->gstObjectUnref(audioParseSrcPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPeerPad); + m_gstWrapper->gstObjectUnref(audioDecSinkPad); + m_gstWrapper->gstObjectUnref(audioDecSrcPad); + m_gstWrapper->gstElementSyncStateWithParent(newAudioDecoder); + m_gstWrapper->gstElementGetState(newAudioDecoder, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioDecoder State = %d Pending = %d", currentState, pending); + m_gstWrapper->gstElementSyncStateWithParent(newAudioParse); + m_gstWrapper->gstElementGetState(newAudioParse, ¤tState, &pending, GST_CLOCK_TIME_NONE); + RIALTO_SERVER_LOG_DEBUG("OTF -> New AudioParser State = %d Pending = %d", currentState, pending); + m_context.playbackGroup.m_isAudioAAC = isAudioAAC; + return true; +} + +bool GstGenericPlayer::performAudioTrackCodecChannelSwitch(const void *pSampleAttr, + firebolt::rialto::wrappers::AudioAttributesPrivate *pAudioAttr, + uint32_t *pStatus, unsigned int *pui32Delay, + long long *pAudioChangeTargetPts, // NOLINT(runtime/int) + const long long *pcurrentDispPts, // NOLINT(runtime/int) + unsigned int *audioChangeStage, GstCaps **appsrcCaps, + bool *audioaac, bool svpenabled, GstElement *aSrc, bool *ret) +{ + // this function comes from rdk_gstreamer_utils + if (!pStatus || !pui32Delay || !pAudioChangeTargetPts || !pcurrentDispPts || !audioChangeStage || !appsrcCaps || + !audioaac || !aSrc || !ret) + { + RIALTO_SERVER_LOG_ERROR("performAudioTrackCodecChannelSwitch: invalid null parameter"); + return false; + } + + constexpr uint32_t kOk = 0; + constexpr uint32_t kWaitWhileIdling = 100; + constexpr int kAudioChangeGapThresholdMS = 40; + constexpr unsigned int kAudchgAlign = 3; + + struct timespec ts, now; + unsigned int reconfigDelayMs; + clock_gettime(CLOCK_MONOTONIC, &ts); + if (*pStatus != kOk || pSampleAttr == nullptr) + { + RIALTO_SERVER_LOG_DEBUG("No audio data ready yet"); + *pui32Delay = kWaitWhileIdling; + *ret = false; + return true; + } + RIALTO_SERVER_LOG_DEBUG("Received first audio packet after a flush, PTS"); + if (pAudioAttr) + { + const char *pCodecStr = pAudioAttr->m_codecParam.c_str(); + const char *pCodecAcc = strstr(pCodecStr, "mp4a"); + bool isAudioAAC = (pCodecAcc) ? true : false; + bool isCodecSwitch = false; + RIALTO_SERVER_LOG_DEBUG("Audio Attribute format %s channel %d samp %d, bitrate %d blockAlignment %d", pCodecStr, + pAudioAttr->m_numberOfChannels, pAudioAttr->m_samplesPerSecond, pAudioAttr->m_bitrate, + pAudioAttr->m_blockAlignment); + *pAudioChangeTargetPts = *pcurrentDispPts; + *audioChangeStage = kAudchgAlign; + if (*appsrcCaps) + { + m_gstWrapper->gstCapsUnref(*appsrcCaps); + *appsrcCaps = NULL; + } + if (isAudioAAC != *audioaac) + isCodecSwitch = true; + configAudioCap(pAudioAttr, audioaac, svpenabled, appsrcCaps); + { + gboolean sendRet = FALSE; + GstEvent *flushStart = NULL; + GstEvent *flushStop = NULL; + flushStart = m_gstWrapper->gstEventNewFlushStart(); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStart); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-start event"); + flushStop = m_gstWrapper->gstEventNewFlushStop(TRUE); + sendRet = m_gstWrapper->gstElementSendEvent(aSrc, flushStop); + if (!sendRet) + RIALTO_SERVER_LOG_DEBUG("failed to send flush-stop event"); + } + if (!isCodecSwitch) + { + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + } + else + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH mAudioAAC = %d", *audioaac); + haltAudioPlayback(); + if (switchAudioCodec(*audioaac, *appsrcCaps) == false) + { + RIALTO_SERVER_LOG_DEBUG("CODEC SWITCH FAILED switchAudioCodec mAudioAAC = %d", *audioaac); + } + m_gstWrapper->gstAppSrcSetCaps(GST_APP_SRC(aSrc), *appsrcCaps); + resumeAudioPlayback(); + } + clock_gettime(CLOCK_MONOTONIC, &now); + reconfigDelayMs = now.tv_nsec > ts.tv_nsec ? (now.tv_nsec - ts.tv_nsec) / 1000000 + : (1000 - (ts.tv_nsec - now.tv_nsec) / 1000000); + (*pAudioChangeTargetPts) += (reconfigDelayMs + kAudioChangeGapThresholdMS); + } + else + { + RIALTO_SERVER_LOG_DEBUG("first audio after change no attribute drop!"); + *pui32Delay = 0; + *ret = false; + return true; + } + *ret = true; + return true; +} + bool GstGenericPlayer::setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutputParam) { if (!m_workerThread) @@ -1055,9 +1517,24 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrgetType()].appSrc)}; GstCaps *oldCaps = m_gstWrapper->gstAppSrcGetCaps(appSrc); + if ((!oldCaps) || (!m_gstWrapper->gstCapsIsEqual(caps, oldCaps))) { RIALTO_SERVER_LOG_DEBUG("Caps not equal. Perform audio track codec channel switch."); + + GstElement *sink = getSink(MediaSourceType::AUDIO); + if (!sink) + { + RIALTO_SERVER_LOG_ERROR("Failed to get audio sink"); + if (caps) + m_gstWrapper->gstCapsUnref(caps); + if (oldCaps) + m_gstWrapper->gstCapsUnref(oldCaps); + return false; + } + std::string sinkName = GST_ELEMENT_NAME(sink); + m_gstWrapper->gstObjectUnref(sink); + int sampleAttributes{ 0}; // rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch checks if this param != NULL only. std::uint32_t status{0}; // must be 0 to make rdk_gstreamer_utils::performAudioTrackCodecChannelSwitch work @@ -1071,14 +1548,28 @@ bool GstGenericPlayer::reattachSource(const std::unique_ptrperformAudioTrackCodecChannelSwitch(&m_context.playbackGroup, &sampleAttributes, &(*audioAttributes), - &status, &ui32Delay, &audioChangeTargetPts, ¤tDispPts, - &audioChangeStage, - &caps, // may fail for amlogic - that implementation changes - // this parameter, it's probably used by Netflix later - &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + + bool result = false; + RIALTO_SERVER_LOG_MIL("Changing audio source. Sink name: %s, old caps: %s, new codec: %s", sinkName.c_str(), + oldCapsStr.c_str(), audioAttributes->m_codecParam.c_str()); + if (m_glibWrapper->gStrHasPrefix(sinkName.c_str(), "amlhalasink")) + { + // due to problems audio codec change in prerolling, temporarily moved the code from rdk gstreamer utils to + // Rialto and applied fixes + result = performAudioTrackCodecChannelSwitch(&sampleAttributes, &(*audioAttributes), &status, &ui32Delay, + &audioChangeTargetPts, ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, GST_ELEMENT(appSrc), &retVal); + } + else + { + result = m_rdkGstreamerUtilsWrapper->performAudioTrackCodecChannelSwitch(&m_context.playbackGroup, + &sampleAttributes, + &(*audioAttributes), &status, + &ui32Delay, &audioChangeTargetPts, + ¤tDispPts, &audioChangeStage, + &caps, &audioAac, svpEnabled, + GST_ELEMENT(appSrc), &retVal); + } if (!result || !retVal) { diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index 401b8e2f7..c30acf396 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -220,6 +220,13 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(gboolean, gstIsBaseParse, (GstElement * element), (const, override)); MOCK_METHOD(void, gstBaseParseSetPtsInterpolation, (GstBaseParse * parse, gboolean ptsInterpolate), (const, override)); + MOCK_METHOD(GstStateChangeReturn, gstElementGetState, + (GstElement * element, GstState *state, GstState *pending, GstClockTime timeout), (override)); + MOCK_METHOD(GstPad *, gstPadGetPeer, (GstPad * pad), (override)); + MOCK_METHOD(gboolean, gstPadUnlink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(GstPadLinkReturn, gstPadLink, (GstPad * srcpad, GstPad *sinkpad), (override)); + MOCK_METHOD(gboolean, gstBinRemove, (GstBin * bin, GstElement *element), (override)); + MOCK_METHOD(GstObject *, gstPadGetParent, (GstPad * pad), (override)); GstCaps *gstCapsNewSimple(const char *media_type, const char *fieldname, ...) const override { diff --git a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp index b2e694ecd..4e0a87425 100644 --- a/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/AudioSourceSwitchTest.cpp @@ -26,6 +26,8 @@ #include "MessageBuilders.h" using testing::_; +using testing::AtLeast; +using testing::Invoke; using testing::Return; using testing::StrEq; @@ -39,11 +41,20 @@ namespace firebolt::rialto::server::ct class AudioSourceSwitchTest : public MediaPipelineTest { public: - AudioSourceSwitchTest() = default; - ~AudioSourceSwitchTest() = default; + AudioSourceSwitchTest() { m_audioSink = gst_element_factory_make("fakesrc", nullptr); } + ~AudioSourceSwitchTest() override { gst_object_unref(m_audioSink); } void willSwitchAudioSource() { + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke( + [this](gpointer object, const gchar *first_property_name, void *element) + { + GstElement **elementPtr = reinterpret_cast(element); + *elementPtr = m_audioSink; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioSink)).Times(AtLeast(0)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_audioSink))).WillRepeatedly(Return("audio_sink")); EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&m_audioCaps)); EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleStringStub(&m_audioCaps, StrEq("alignment"), G_TYPE_STRING, StrEq("nal"))); @@ -61,6 +72,7 @@ class AudioSourceSwitchTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&m_audioCaps, &m_oldCaps)).WillOnce(Return(FALSE)); EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&m_oldCaps)).WillOnce(Return(&m_oldCapsStr)); EXPECT_CALL(*m_glibWrapperMock, gFree(&m_oldCapsStr)); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(_, _, _, _, _, _, _, _, _, _, kSvpEnabled, GST_ELEMENT(&m_audioAppSrc), _)) @@ -80,6 +92,7 @@ class AudioSourceSwitchTest : public MediaPipelineTest private: GstCaps m_oldCaps{}; gchar m_oldCapsStr{}; + GstElement *m_audioSink{}; }; /* * Component Test: Switch audio source procedure test diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index be547e88f..4e848aed5 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -1939,6 +1939,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[13]{"audio/x-eac3"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -1960,6 +1961,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("fakesink"), StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -1968,6 +1975,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachMpegAudioSource) std::unique_ptr source = std::make_unique("audio/aac", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) @@ -1976,6 +1984,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink0"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -1996,6 +2005,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2004,6 +2019,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachEac3AudioSource) std::unique_ptr source = std::make_unique("audio/x-eac3", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) @@ -2012,6 +2028,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) GstCaps newGstCaps{}; GstCaps oldGstCaps{}; gchar capsStr[11]{"audio/mpeg"}; + GstElement *fakeSink = gst_element_factory_make("fakesink", "fakesink1"); setPipelineState(GST_STATE_PAUSED); firebolt::rialto::wrappers::PlaybackGroupPrivate *playbackGroup; modifyContext( @@ -2032,6 +2049,12 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer object, const gchar *first_property_name, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); EXPECT_CALL(*m_rdkGstreamerUtilsWrapperMock, performAudioTrackCodecChannelSwitch(playbackGroup, _, _, _, _, _, _, _, _, _, _, _, _)) .WillOnce(Return(true)); @@ -2040,6 +2063,188 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachRawAudioSource) std::unique_ptr source = std::make_unique("audio/x-raw", false); EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceNoCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + gchar capsStr[11]{"audio/mpeg"}; // old caps was AAC + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink0"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = true; // current codec is AAC + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink0"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (AAC->AAC, no codec switch): + // configAudioCap unrefs original caps and creates new ones + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // no codec switch - just set new caps + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // end of reattachSource: caps was updated to configCaps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceWithFirstTimeCodecSwitch) +{ + GstAppSrc audioSrc{}; + GstCaps newGstCaps{}; + GstCaps oldGstCaps{}; + GstCaps configCaps{}; + GstEvent flushStartEvent{}; + GstEvent flushStopEvent{}; + GstPad typefindSrcPad{}; + GstPad typefindSrcPeerPad{}; + GstElement newAudioDecoder{}; + GstElement newAudioParse{}; + GstElement newQueue{}; + GstPad newAudioDecoderSrcPad{}; + GstElement typefind{}; + GstElement decodeBin{}; + GstElement playsinkBin{}; + gchar capsStr[13]{"audio/x-eac3"}; // old caps was EAC3 (no "audio/mpeg" → audioAac=false) + gchar configCapsStr[] = "audio/mpeg, mpegversion=4, enable-svp=(string)true"; + GstElement *fakeSink = gst_element_factory_make("fakesink", "amlhalasink1"); + setPipelineState(GST_STATE_PAUSED); + modifyContext( + [&](GenericPlayerContext &context) + { + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.playbackGroup.m_isAudioAAC = false; // current codec is EAC3 + context.playbackGroup.m_curAudioDecoder = nullptr; // first time switch: no existing decoder + context.playbackGroup.m_curAudioTypefind = &typefind; + context.playbackGroup.m_curAudioDecodeBin = &decodeBin; + context.playbackGroup.m_curAudioPlaysinkBin = &playsinkBin; + }); + + // createCapsFromMediaSource for audio/aac + EXPECT_CALL(*m_gstWrapperMock, gstCapsNewEmptySimple(StrEq("audio/mpeg"))).WillOnce(Return(&newGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&newGstCaps, StrEq("mpegversion"), G_TYPE_INT, 4)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(GST_APP_SRC(&audioSrc))).WillOnce(Return(&oldGstCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsIsEqual(&newGstCaps, &oldGstCaps)).WillOnce(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsToString(&oldGstCaps)).WillOnce(Return(capsStr)); + EXPECT_CALL(*m_glibWrapperMock, gFree(capsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&oldGstCaps)); + // getPosition + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(_)).WillOnce(Return(GST_STATE_PAUSED)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStateReturn(_)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstStateLock(_)).WillOnce(Return()); + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryPosition(_, GST_FORMAT_TIME, _)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstStateUnlock(_)).WillOnce(Return()); + // getSink + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq("audio-sink"), _)) + .WillOnce(Invoke([&](gpointer, const gchar *, void *element) + { *reinterpret_cast(element) = fakeSink; })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(fakeSink)); + // getSinkChildIfAutoAudioSink + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(fakeSink))).WillOnce(Return(kElementTypeName.c_str())); + // amlhalasink path + EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(StrEq("amlhalasink1"), StrEq("amlhalasink"))).WillOnce(Return(TRUE)); + // performAudioTrackCodecChannelSwitch (EAC3->AAC, codec switch): + // configAudioCap unrefs original caps and creates new AAC caps + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&newGstCaps)); + EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(configCapsStr)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsFromString(configCapsStr)).WillOnce(Return(&configCaps)); + EXPECT_CALL(*m_glibWrapperMock, gFree(configCapsStr)); + // flush events + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStart()).WillOnce(Return(&flushStartEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStartEvent)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstEventNewFlushStop(kResetTime)).WillOnce(Return(&flushStopEvent)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSendEvent(GST_ELEMENT(&audioSrc), &flushStopEvent)).WillOnce(Return(TRUE)); + // haltAudioPlayback + resumeAudioPlayback: playsinkBin and decodeBin each called twice + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&playsinkBin, GST_STATE_READY)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&playsinkBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&decodeBin, GST_STATE_PAUSED)) + .WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&decodeBin, _, _, GST_CLOCK_TIME_NONE)).Times(2); + // switchAudioCodec -> firstTimeSwitchFromAC3toAAC + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&typefind, StrEq("src"))).WillOnce(Return(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadGetPeer(&typefindSrcPad)).WillOnce(Return(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadUnlink(&typefindSrcPad, &typefindSrcPeerPad)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("aacparse"), StrEq("aacparse"))) + .WillOnce(Return(&newAudioParse)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("avdec_aac"), StrEq("avdec_aac"))) + .WillOnce(Return(&newAudioDecoder)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("queue"), StrEq("aqueue"))).WillOnce(Return(&newQueue)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(_, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetStaticPad(&newAudioDecoder, StrEq("src"))) + .WillOnce(Return(&newAudioDecoderSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstPadLink(&newAudioDecoderSrcPad, &typefindSrcPeerPad)).WillOnce(Return(GST_PAD_LINK_OK)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newAudioParse, &newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementLink(&newQueue, &newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&typefind, GST_STATE_READY)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(&typefind, StrEq("force-caps"))); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&typefind)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&typefind, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioDecoder)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioDecoder, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newQueue)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newQueue, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&newAudioParse)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetState(&newAudioParse, _, _, GST_CLOCK_TIME_NONE)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&typefindSrcPeerPad)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&newAudioDecoderSrcPad)); + // gstAppSrcSetCaps after codec switch + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(GST_APP_SRC(&audioSrc), &configCaps)); + // resumeAudioPlayback + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&playsinkBin)).WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&decodeBin)).WillOnce(Return(TRUE)); + // end of reattachSource: caps was updated to configCaps inside performAudioTrackCodecChannelSwitch + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + + std::unique_ptr source = + std::make_unique("audio/aac", false); + EXPECT_TRUE(m_sut->reattachSource(source)); + gst_object_unref(fakeSink); } TEST_F(GstGenericPlayerPrivateTest, shouldSetSourceFlushed) diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 055484c43..1c109aa74 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -595,6 +595,22 @@ class GstWrapper : public IGstWrapper { gst_base_parse_set_pts_interpolation(parse, ptsInterpolate); } + + GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) override + { + return gst_element_get_state(element, state, pending, timeout); + } + + GstPad *gstPadGetPeer(GstPad *pad) override { return gst_pad_get_peer(pad); } + + gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_unlink(srcpad, sinkpad); } + + GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) override { return gst_pad_link(srcpad, sinkpad); } + + gboolean gstBinRemove(GstBin *bin, GstElement *element) override { return gst_bin_remove(bin, element); } + + GstObject *gstPadGetParent(GstPad *pad) override { return gst_pad_get_parent(pad); } }; }; // namespace firebolt::rialto::wrappers diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index 0eca162f7..a7a184624 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -1402,6 +1402,69 @@ class IGstWrapper * @param ptsInterpolate : TRUE if parser should interpolate PTS timestamps */ virtual void gstBaseParseSetPtsInterpolation(GstBaseParse *parse, gboolean ptsInterpolate) const = 0; + + /** + * @brief Gets the state of the element (blocking version with timeout). + * + * @param[in] element : A GstElement to get state of. + * @param[out] state : A pointer to GstState to hold the state, or NULL. + * @param[out] pending : A pointer to GstState to hold the pending state, or NULL. + * @param[in] timeout : A GstClockTime to specify the timeout. + * + * @retval GST_STATE_CHANGE_SUCCESS if the element has no more pending state + * and the last state change succeeded, GST_STATE_CHANGE_ASYNC if the + * element is still performing a state change, or other values on failure. + */ + virtual GstStateChangeReturn gstElementGetState(GstElement *element, GstState *state, GstState *pending, + GstClockTime timeout) = 0; + + /** + * @brief Gets the peer pad of the given pad. + * + * @param[in] pad : A GstPad to get the peer of. + * + * @retval The peer GstPad. Unref after usage. NULL if pad has no peer. + */ + virtual GstPad *gstPadGetPeer(GstPad *pad) = 0; + + /** + * @brief Unlinks the source pad from the sink pad. + * + * @param[in] srcpad : The source GstPad to unlink. + * @param[in] sinkpad : The sink GstPad to unlink. + * + * @retval TRUE if the pads were unlinked, FALSE otherwise. + */ + virtual gboolean gstPadUnlink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Links the source pad to the sink pad. + * + * @param[in] srcpad : The source GstPad to link. + * @param[in] sinkpad : The sink GstPad to link. + * + * @retval A result code indicating if the connection worked or what went wrong. + */ + virtual GstPadLinkReturn gstPadLink(GstPad *srcpad, GstPad *sinkpad) = 0; + + /** + * @brief Removes an element from the bin. + * + * @param[in] bin : The bin to remove the element from. + * @param[in] element : The element to remove. + * + * @retval TRUE if the element could be removed, FALSE otherwise. + */ + virtual gboolean gstBinRemove(GstBin *bin, GstElement *element) = 0; + + /** + * @brief Gets the parent of a pad. + * + * @param[in] pad : The pad to get the parent of. + * + * @retval The parent GstObject. Unref after usage. NULL if pad has no parent. + */ + virtual GstObject *gstPadGetParent(GstPad *pad) = 0; }; }; // namespace firebolt::rialto::wrappers From cad26f1a5fd0c1f71ebd0d1f38626e000e0ecc01 Mon Sep 17 00:00:00 2001 From: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:56:33 +0200 Subject: [PATCH 06/21] =?UTF-8?q?Fixed=20initialisation=20of=20playbackGro?= =?UTF-8?q?up=20elements=20in=20GenericPlayerContex=E2=80=A6=20(#484)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Fixed initialisation of playbackGroup elements in GenericPlayerContext. Added linking of typefind and the parser after the audio switch Type: Feature Test Plan: UT/CT, Fullstack Jira: RDKEMW-16807 --- media/server/gstplayer/include/Utils.h | 1 + .../gstplayer/source/GstGenericPlayer.cpp | 5 ++ media/server/gstplayer/source/Utils.cpp | 5 ++ .../source/tasks/generic/DeepElementAdded.cpp | 41 ++++++---- .../tasks/generic/UpdatePlaybackGroup.cpp | 14 ++++ .../GstGenericPlayerPrivateTest.cpp | 1 + .../common/GenericTasksTestsBase.cpp | 78 +++++++++++++++---- .../common/GenericTasksTestsBase.h | 2 + .../tasksTests/UpdatePlaybackGroupTest.cpp | 16 ++++ 9 files changed, 131 insertions(+), 32 deletions(-) diff --git a/media/server/gstplayer/include/Utils.h b/media/server/gstplayer/include/Utils.h index 81037266d..7351ec903 100644 --- a/media/server/gstplayer/include/Utils.h +++ b/media/server/gstplayer/include/Utils.h @@ -32,6 +32,7 @@ namespace firebolt::rialto::server bool isVideoDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index bff22559c..375401a6f 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -301,6 +301,11 @@ void GstGenericPlayer::termPipeline() m_gstWrapper->gstObjectUnref(m_context.videoSink); m_context.videoSink = nullptr; } + if (m_context.playbackGroup.m_curAudioPlaysinkBin) + { + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_context.playbackGroup.m_curAudioPlaysinkBin = nullptr; + } // Delete the pipeline m_gstWrapper->gstObjectUnref(m_context.pipeline); diff --git a/media/server/gstplayer/source/Utils.cpp b/media/server/gstplayer/source/Utils.cpp index 243aa060d..520778de4 100644 --- a/media/server/gstplayer/source/Utils.cpp +++ b/media/server/gstplayer/source/Utils.cpp @@ -64,6 +64,11 @@ bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, Gs return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); } +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) +{ + return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO); +} + bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) { return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); diff --git a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp index 83e6bef12..4eb337ead 100644 --- a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp +++ b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp @@ -19,6 +19,7 @@ #include "tasks/generic/DeepElementAdded.h" #include "RialtoServerLogging.h" +#include "Utils.h" namespace { @@ -69,42 +70,48 @@ void DeepElementAdded::execute() const m_context.playbackGroup.m_gstPipeline = GST_ELEMENT(m_pipeline); RIALTO_SERVER_LOG_DEBUG("Element = %p Bin = %p Pipeline = %p", m_element, m_bin, m_pipeline); - if (m_callbackRegistered) - { - m_context.playbackGroup.m_curAudioTypefind = m_element; - } if (m_elementName) { if (m_gstWrapper->gstObjectCast(m_bin) == m_gstWrapper->gstObjectCast(m_context.playbackGroup.m_curAudioDecodeBin)) { - if (m_glibWrapper->gStrrstr(m_elementName, "parse")) + if (isAudioParser(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioParse = %s", m_elementName); m_context.playbackGroup.m_curAudioParse = m_element; } - else if (m_glibWrapper->gStrrstr(m_elementName, "dec")) + else if (isAudioDecoder(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioDecoder = %s", m_elementName); m_context.playbackGroup.m_curAudioDecoder = m_element; } + else if (m_callbackRegistered && m_glibWrapper->gStrrstr(m_elementName, "typefind")) + { + RIALTO_SERVER_LOG_DEBUG("curAudioTypefind = %s", m_elementName); + m_context.playbackGroup.m_curAudioTypefind = m_element; + } } - else + else if (isAudioSink(*m_gstWrapper, m_element)) { - if (m_glibWrapper->gStrrstr(m_elementName, "audiosink")) + GstElement *audioSinkParent = reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); + if (audioSinkParent) { - GstElement *audioSinkParent = - reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); - if (audioSinkParent) + gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); + RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); + if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) { - gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); - RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); - if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) + RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); + if (m_context.playbackGroup.m_curAudioPlaysinkBin) { - RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); - m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + // Unref previous audio playsink bin, if exists + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); } - m_glibWrapper->gFree(audioSinkParentName); + m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + } + else + { + m_gstWrapper->gstObjectUnref(audioSinkParent); } + m_glibWrapper->gFree(audioSinkParentName); } } } diff --git a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp index 36f3f79b7..b5807ac23 100644 --- a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp +++ b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp @@ -68,6 +68,20 @@ void UpdatePlaybackGroup::execute() const { m_player.setUseBuffering(); } + if (m_context.playbackGroup.m_linkTypefindParser && m_context.playbackGroup.m_curAudioTypefind && + m_context.playbackGroup.m_curAudioParse) + { + if (m_gstWrapper->gstElementLink(m_context.playbackGroup.m_curAudioTypefind, + m_context.playbackGroup.m_curAudioParse)) + { + RIALTO_SERVER_LOG_DEBUG("Linked typefind to parser"); + m_context.playbackGroup.m_linkTypefindParser = false; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Failed to link typefind to parser"); + } + } } m_glibWrapper->gFree(elementName); m_gstWrapper->gstObjectUnref(typeFindParent); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 4e848aed5..2c599dede 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -2240,6 +2240,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceWithFirs EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&decodeBin)).WillOnce(Return(TRUE)); // end of reattachSource: caps was updated to configCaps inside performAudioTrackCodecChannelSwitch EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&playsinkBin)); std::unique_ptr source = std::make_unique("audio/aac", false); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index bb73b346d..55cee05be 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -1941,7 +1941,7 @@ void GenericTasksTestsBase::shouldNotRegisterCallbackWhenElementNameIsNotTypefin EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("typefind"))) - .WillOnce(Return(nullptr)); + .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } @@ -1952,7 +1952,7 @@ void GenericTasksTestsBase::shouldRegisterCallbackForTypefindElement() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("typefind"))) - .WillOnce(Return(testContext->m_typefindElementName)); + .WillRepeatedly(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(G_OBJECT(testContext->m_element), StrEq("have-type"), _, &testContext->m_gstPlayer)) .WillOnce(Return(kSignalId)); @@ -1982,9 +1982,19 @@ void GenericTasksTestsBase::shouldUpdatePlaybackGroupWhenCallbackIsCalled() void GenericTasksTestsBase::shouldSetTypefindElement() { - EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) + .WillOnce(Return(&testContext->m_obj1)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::triggerDeepElementAdded() @@ -2030,8 +2040,11 @@ void GenericTasksTestsBase::shouldSetParseElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_parseElementName, StrEq("parse"))) - .WillOnce(Return(testContext->m_parseElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkParsePlaybackGroupAdded() @@ -2056,10 +2069,15 @@ void GenericTasksTestsBase::shouldSetDecoderElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("parse"))) - .WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("dec"))) - .WillOnce(Return(testContext->m_decoderElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() @@ -2074,8 +2092,11 @@ void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() void GenericTasksTestsBase::shouldSetGenericElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::shouldSetAudioSinkElement() @@ -2089,8 +2110,11 @@ void GenericTasksTestsBase::shouldSetAudioSinkElement() EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_audioSinkElementName)); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_audioSinkElementName, StrEq("audiosink"))) - .WillOnce(Return(testContext->m_audioSinkElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::shouldHaveNullParentSink() @@ -2098,6 +2122,7 @@ void GenericTasksTestsBase::shouldHaveNullParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(nullptr)); } @@ -2108,11 +2133,16 @@ void GenericTasksTestsBase::shouldHaveNonBinParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("bin"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } void GenericTasksTestsBase::shouldHaveBinParentSink() { + // Previous bin should be unrefed + testContext->m_context.playbackGroup.m_curAudioPlaysinkBin = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_childElement)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) @@ -2235,6 +2265,24 @@ void GenericTasksTestsBase::shouldTriggerSetUseBuffering() EXPECT_CALL(testContext->m_gstPlayer, setUseBuffering()).WillOnce(Return(true)); } +void GenericTasksTestsBase::shouldLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(TRUE)); +} + +void GenericTasksTestsBase::shouldFailToLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(FALSE)); +} + void GenericTasksTestsBase::shouldStopGstPlayer() { auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 29f64e16a..b1ee082db 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -235,6 +235,8 @@ class GenericTasksTestsBase : public ::testing::Test void checkPlaybackGroupAdded(); void setUseBufferingPending(); void shouldTriggerSetUseBuffering(); + void shouldLinkTypefindAndParser(); + void shouldFailToLinkTypefindAndParser(); // Stop test methods void shouldStopGstPlayer(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp index dfed351b4..74312e50f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp @@ -72,3 +72,19 @@ TEST_F(UpdatePlaybackGroupTest, shouldTriggerUseBuffering) triggerUpdatePlaybackGroup(); checkPlaybackGroupAdded(); } + +TEST_F(UpdatePlaybackGroupTest, shouldLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} + +TEST_F(UpdatePlaybackGroupTest, shouldFailToLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldFailToLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} From ae413b045d7c84841fd93a5ccb235c8a15c07889 Mon Sep 17 00:00:00 2001 From: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> Date: Fri, 8 May 2026 15:12:23 +0200 Subject: [PATCH 07/21] Do not treat server as deadlocked when outdated acks are received (#495) Summary: Do not treat server as deadlocked when outdated acks are received Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-18258 --- .../gstplayer/source/tasks/generic/Flush.cpp | 1 + .../common/source/HealthcheckService.cpp | 30 +++++++++++++------ .../common/source/HealthcheckService.h | 2 +- .../common/HealthcheckServiceTests.cpp | 19 ++++++++++++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 1b8dae988..022d4088d 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -79,6 +79,7 @@ void Flush::execute() const m_player.stopPositionReportingAndCheckAudioUnderflowTimer(); m_context.flushOnPrerollController->waitIfRequired(m_type); + RIALTO_SERVER_LOG_MIL("Sending flush event for %s source.", common::convertMediaSourceType(m_type)); // Flush source GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) diff --git a/serverManager/common/source/HealthcheckService.cpp b/serverManager/common/source/HealthcheckService.cpp index 6a478a64d..7abc8e205 100644 --- a/serverManager/common/source/HealthcheckService.cpp +++ b/serverManager/common/source/HealthcheckService.cpp @@ -65,7 +65,7 @@ void HealthcheckService::onPingSent(int serverId, int pingId) return; } m_remainingPings.insert(serverId); - m_failedPings.try_emplace(serverId, 0); + m_failedPings.try_emplace(serverId, std::set{}); } void HealthcheckService::onPingFailed(int serverId, int pingId) @@ -86,7 +86,7 @@ void HealthcheckService::onPingFailed(int serverId, int pingId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - m_failedPings.emplace(serverId, 1); + m_failedPings.emplace(serverId, std::set{pingId}); } } @@ -95,15 +95,26 @@ void HealthcheckService::onAckReceived(int serverId, int pingId, bool success) std::unique_lock lock{m_mutex}; if (pingId != m_currentPingId) { - RIALTO_SERVER_MANAGER_LOG_WARN("Unexpected ack received from server id: %d. Current ping id: %d, received ping " - "id: %d", - serverId, m_currentPingId, pingId); + if (success && m_failedPings[serverId].find(pingId) != m_failedPings[serverId].end()) + { + RIALTO_SERVER_MANAGER_LOG_WARN("Late ack received for server id: %d, Current ping id: %d, received ping " + "id: %d. Removing from failed pings list", + serverId, m_currentPingId, pingId); + m_failedPings[serverId].erase(pingId); + } + else + { + RIALTO_SERVER_MANAGER_LOG_ERROR("Unexpected ack received from server id: %d. Current ping id: %d, received " + "ping " + "id: %d", + serverId, m_currentPingId, pingId); + } return; } m_remainingPings.erase(serverId); if (success) { - m_failedPings[serverId] = 0; + m_failedPings[serverId].clear(); } else { @@ -136,12 +147,13 @@ void HealthcheckService::sendPing() void HealthcheckService::handleError(int serverId) { m_sessionServerAppManager.onSessionServerStateChanged(serverId, firebolt::rialto::common::SessionServerState::ERROR); - unsigned &failedPingsNum{m_failedPings[serverId]}; - if (++failedPingsNum >= m_kNumOfFailedPingsBeforeRecovery) + auto &failedPings{m_failedPings[serverId]}; + failedPings.insert(m_currentPingId); + if (failedPings.size() >= m_kNumOfFailedPingsBeforeRecovery) { RIALTO_SERVER_MANAGER_LOG_WARN( "Max num of failed pings reached for server with id: %d. Starting recovery action", serverId); - failedPingsNum = 0; + failedPings.clear(); m_sessionServerAppManager.restartServer(serverId); } } diff --git a/serverManager/common/source/HealthcheckService.h b/serverManager/common/source/HealthcheckService.h index d0517bdb4..b3649ac0b 100644 --- a/serverManager/common/source/HealthcheckService.h +++ b/serverManager/common/source/HealthcheckService.h @@ -53,7 +53,7 @@ class HealthcheckService : public IHealthcheckService std::mutex m_mutex; int m_currentPingId; std::set m_remainingPings; - std::map m_failedPings; + std::map> m_failedPings; }; } // namespace rialto::servermanager::common diff --git a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp index 4289aa15b..70d6509c8 100644 --- a/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp +++ b/tests/unittests/serverManager/unittests/common/HealthcheckServiceTests.cpp @@ -285,3 +285,22 @@ TEST_F(HealthcheckServiceTests, WillFailToFailPingWithWrongId) // There should be no error indication here. triggerOnPingFailed(pingId + 1); } + +TEST_F(HealthcheckServiceTests, WillSkipRestartingServerWhenAcksAreReceivedLater) +{ + int pingId{-1}; + timerWillBeCreated(); + createSut(); + pingWillBeSent(pingId); + triggerPingTimeout(); + const int firstPingId{pingId}; + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); + triggerOnAckReceived(kServerId, firstPingId, kSuccess); + triggerOnPingSent(pingId); + errorIndicationWillBeSent(); + pingWillBeSent(pingId); + triggerPingTimeout(); +} From 84496a2499ad757774d9308714dbf8dc2abfc966 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Wed, 20 May 2026 18:31:00 +0000 Subject: [PATCH 08/21] HDCP retry to rialto --- media/client/ipc/source/MediaKeysIpc.cpp | 7 +- media/public/include/MediaCommon.h | 4 +- .../ipc/source/MediaKeysModuleService.cpp | 8 +- .../main/source/MediaKeysCapabilities.cpp | 2 + .../main/source/MediaKeysServerInternal.cpp | 86 ++++++++++++++++--- wrappers/CMakeLists.txt | 2 + wrappers/include/OcdmSession.h | 2 + wrappers/source/OcdmSession.cpp | 65 ++++++++++++++ 8 files changed, 163 insertions(+), 13 deletions(-) diff --git a/media/client/ipc/source/MediaKeysIpc.cpp b/media/client/ipc/source/MediaKeysIpc.cpp index 24909664e..aa664fc55 100644 --- a/media/client/ipc/source/MediaKeysIpc.cpp +++ b/media/client/ipc/source/MediaKeysIpc.cpp @@ -123,6 +123,10 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &errorStatus) { return "FAIL"; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + return "OUTPUT_RESTRICTED"; + } } return "UNKNOWN"; } @@ -334,7 +338,7 @@ bool MediaKeysIpc::containsKey(int32_t keySessionId, const std::vector } MediaKeyErrorStatus MediaKeysIpc::createKeySession(KeySessionType sessionType, std::weak_ptr client, - bool isLDL, int32_t &keySessionId) + bool isLDL, int32_t &keySessionId) { if (!reattachChannelIfRequired()) { @@ -415,6 +419,7 @@ MediaKeyErrorStatus MediaKeysIpc::generateRequest(int32_t keySessionId, InitData break; } + firebolt::rialto::GenerateRequestRequest request; request.set_media_keys_handle(m_mediaKeysHandle); request.set_key_session_id(keySessionId); diff --git a/media/public/include/MediaCommon.h b/media/public/include/MediaCommon.h index 92b544b8c..50252b01b 100644 --- a/media/public/include/MediaCommon.h +++ b/media/public/include/MediaCommon.h @@ -292,7 +292,8 @@ enum class MediaKeyErrorStatus NOT_SUPPORTED, /**< The request parameters are not supported. */ INVALID_STATE, /**< The object is in an invalid state for the operation. */ INTERFACE_NOT_IMPLEMENTED, /**< The interface is not implemented. */ - BUFFER_TOO_SMALL /**< The size of the buffer is too small. */ + BUFFER_TOO_SMALL, /**< The size of the buffer is too small. */ + OUTPUT_RESTRICTED }; /** @@ -473,6 +474,7 @@ struct PlaybackInfo int64_t currentPosition{-1}; /**< The current playback position */ double volume{1.0}; /**< The current volume */ }; + } // namespace firebolt::rialto #endif // FIREBOLT_RIALTO_MEDIA_COMMON_H_ diff --git a/media/server/ipc/source/MediaKeysModuleService.cpp b/media/server/ipc/source/MediaKeysModuleService.cpp index 4cb5f6293..68cc5772e 100644 --- a/media/server/ipc/source/MediaKeysModuleService.cpp +++ b/media/server/ipc/source/MediaKeysModuleService.cpp @@ -66,6 +66,10 @@ convertMediaKeyErrorStatus(const firebolt::rialto::MediaKeyErrorStatus &errorSta { return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + { + // TODO + } } return firebolt::rialto::ProtoMediaKeyErrorStatus::FAIL; } @@ -100,6 +104,7 @@ firebolt::rialto::InitDataType covertInitDataType(firebolt::rialto::GenerateRequ return firebolt::rialto::InitDataType::UNKNOWN; } } + } // namespace namespace firebolt::rialto::server::ipc @@ -277,11 +282,12 @@ void MediaKeysModuleService::generateRequest(::google::protobuf::RpcController * ::google::protobuf::Closure *done) { RIALTO_SERVER_LOG_DEBUG("entry:"); - MediaKeyErrorStatus status = m_cdmService.generateRequest(request->media_keys_handle(), request->key_session_id(), covertInitDataType(request->init_data_type()), std::vector{request->init_data().begin(), request->init_data().end()}); + + response->set_error_status(convertMediaKeyErrorStatus(status)); done->Run(); } diff --git a/media/server/main/source/MediaKeysCapabilities.cpp b/media/server/main/source/MediaKeysCapabilities.cpp index f0ad87a81..9988d61eb 100644 --- a/media/server/main/source/MediaKeysCapabilities.cpp +++ b/media/server/main/source/MediaKeysCapabilities.cpp @@ -47,6 +47,8 @@ const char *toString(const firebolt::rialto::MediaKeyErrorStatus &status) return "NOT_SUPPORTED"; case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: return "INVALID_STATE"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; } return "Unknown"; } diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 7fd1b58bf..3f5d69b28 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -18,10 +18,40 @@ */ #include +#include +#include #include "MediaKeysServerInternal.h" #include "RialtoServerLogging.h" +/*namespace +{ +const char *toString(const firebolt::rialto::MediaKeyErrorStatus &status) +{ + switch (status) + { + case firebolt::rialto::MediaKeyErrorStatus::OK: + return "OK"; + case firebolt::rialto::MediaKeyErrorStatus::FAIL: + return "FAIL"; + case firebolt::rialto::MediaKeyErrorStatus::BAD_SESSION_ID: + return "BAD_SESSION_ID"; + case firebolt::rialto::MediaKeyErrorStatus::INTERFACE_NOT_IMPLEMENTED: + return "INTERFACE_NOT_IMPLEMENTED"; + case firebolt::rialto::MediaKeyErrorStatus::BUFFER_TOO_SMALL: + return "BUFFER_TOO_SMALL"; + case firebolt::rialto::MediaKeyErrorStatus::NOT_SUPPORTED: + return "NOT_SUPPORTED"; + case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: + return "INVALID_STATE"; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + return "OUTPUT_RESTRICTED"; + } + return "Unknown"; +} +} */// namespace + + namespace firebolt::rialto { const char *mediaKeyErrorStatusToString(const MediaKeyErrorStatus &status) @@ -51,6 +81,9 @@ std::shared_ptr IMediaKeysFactory::createFactory() namespace firebolt::rialto::server { +constexpr std::chrono::milliseconds kOutputRestrictedRetryInterval{250}; +constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{6}; + int32_t generateSessionId() { static int32_t keySessionId{0}; @@ -569,12 +602,51 @@ MediaKeyErrorStatus MediaKeysServerInternal::getCdmKeySessionIdInternal(int32_t MediaKeyErrorStatus MediaKeysServerInternal::decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) { - RIALTO_SERVER_LOG_DEBUG("entry:"); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: entry:decrypt"); - MediaKeyErrorStatus status; - auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; + MediaKeyErrorStatus status{MediaKeyErrorStatus::FAIL}; + const auto deadline = std::chrono::steady_clock::now() + kOutputRestrictedRetryTimeout; + do + { + auto task = [&]() { status = decryptInternal(keySessionId, encrypted, caps); }; + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session id :%d", keySessionId); + switch (status) + { + case firebolt::rialto::MediaKeyErrorStatus::OK: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : OK"); + break; + case firebolt::rialto::MediaKeyErrorStatus::FAIL: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : FAIL"); + break; + case firebolt::rialto::MediaKeyErrorStatus::BAD_SESSION_ID: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : BAD_SESSION_ID"); + break; + case firebolt::rialto::MediaKeyErrorStatus::INTERFACE_NOT_IMPLEMENTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : INTERFACE_NOT_IMPLEMENTED"); + break; + case firebolt::rialto::MediaKeyErrorStatus::BUFFER_TOO_SMALL: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : BUFFER_TOO_SMALL"); + break; + case firebolt::rialto::MediaKeyErrorStatus::NOT_SUPPORTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : NOT_SUPPORTED"); + break; + case firebolt::rialto::MediaKeyErrorStatus::INVALID_STATE: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : INVALID_STATE"); + break; + case firebolt::rialto::MediaKeyErrorStatus::OUTPUT_RESTRICTED: + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : Key session status : OUTPUT_RESTRICTED"); + break; + } + + if (status != MediaKeyErrorStatus::OUTPUT_RESTRICTED) + { + break; + } + RIALTO_SERVER_LOG_WARN("Decrypt returned OUTPUT_RESTRICTED, retrying after delay"); + std::this_thread::sleep_for(kOutputRestrictedRetryInterval); + } while (std::chrono::steady_clock::now() < deadline); - m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); return status; } @@ -598,12 +670,6 @@ MediaKeyErrorStatus MediaKeysServerInternal::decryptInternal(int32_t keySessionI return status; } -bool MediaKeysServerInternal::isNetflixPlayreadyKeySystem() const -{ - RIALTO_SERVER_LOG_DEBUG("entry:"); - return m_kKeySystem.find("netflix") != std::string::npos; -} - void MediaKeysServerInternal::ping(std::unique_ptr &&heartbeatHandler) { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/wrappers/CMakeLists.txt b/wrappers/CMakeLists.txt index 3be841fd1..d70a28037 100644 --- a/wrappers/CMakeLists.txt +++ b/wrappers/CMakeLists.txt @@ -110,6 +110,8 @@ target_include_directories( PRIVATE include + ../common/interface/ + ../logging/include/ ${GStreamerApp_INCLUDE_DIRS} $ ${WRAPPER_INCLUDES} diff --git a/wrappers/include/OcdmSession.h b/wrappers/include/OcdmSession.h index 51841f701..c76716106 100644 --- a/wrappers/include/OcdmSession.h +++ b/wrappers/include/OcdmSession.h @@ -94,6 +94,7 @@ class OcdmSession : public IOcdmSession private: using OcdmGstSessionDecryptExFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstBuffer *, const uint32_t, GstBuffer *, GstBuffer *, uint32_t, GstCaps *); + using OcdmGstSessionDecryptBufferOnceFn = OpenCDMError (*)(struct OpenCDMSession *, GstBuffer *, GstCaps *); /** * @brief The System handle. */ @@ -115,6 +116,7 @@ class OcdmSession : public IOcdmSession struct OpenCDMSession *m_session; static OcdmGstSessionDecryptExFn m_ocdmGstSessionDecryptEx; + static OcdmGstSessionDecryptBufferOnceFn m_ocdmGstSessionDecryptBufferOnce; /** * @brief Requests the processing of the challenge data. diff --git a/wrappers/source/OcdmSession.cpp b/wrappers/source/OcdmSession.cpp index a236826a9..821cd3043 100644 --- a/wrappers/source/OcdmSession.cpp +++ b/wrappers/source/OcdmSession.cpp @@ -23,6 +23,8 @@ #include "opencdm/open_cdm_ext.h" #include #include +#include +#include "RialtoCommonLogging.h" namespace { @@ -109,6 +111,7 @@ const firebolt::rialto::KeyStatus convertKeyStatus(const KeyStatus &ocdmKeyStatu namespace firebolt::rialto::wrappers { OcdmSession::OcdmGstSessionDecryptExFn OcdmSession::m_ocdmGstSessionDecryptEx{nullptr}; +OcdmSession::OcdmGstSessionDecryptBufferOnceFn OcdmSession::m_ocdmGstSessionDecryptBufferOnce{nullptr}; OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient *client) : m_systemHandle(systemHandle), m_ocdmSessionClient(client), m_session(nullptr) @@ -121,6 +124,11 @@ OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient { m_ocdmGstSessionDecryptEx = (OcdmGstSessionDecryptExFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_ex"); + m_ocdmGstSessionDecryptBufferOnce = + (OcdmGstSessionDecryptBufferOnceFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_buffer_once"); + if(m_ocdmGstSessionDecryptBufferOnce != NULL){ + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : m_ocdmGstSessionDecryptBufferOnce exists\n"); + } }); } @@ -190,11 +198,68 @@ MediaKeyErrorStatus OcdmSession::update(const uint8_t response[], uint32_t respo MediaKeyErrorStatus OcdmSession::decryptBuffer(GstBuffer *encrypted, GstCaps *caps) { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer()\n"); if (!m_session) { return MediaKeyErrorStatus::FAIL; } + if (m_ocdmGstSessionDecryptBufferOnce) + { + // Extract key ID from the buffer's protection metadata + std::vector keyId; + GstProtectionMeta *pm = reinterpret_cast(gst_buffer_get_protection_meta(encrypted)); + if (pm) + { + const GValue *kidValue = gst_structure_get_value(pm->info, "kid"); + if (kidValue) + { + GstBuffer *kidBuf = gst_value_get_buffer(kidValue); + if (kidBuf) + { + GstMapInfo kidMap; + if (gst_buffer_map(kidBuf, &kidMap, GST_MAP_READ)) + { + keyId.assign(kidMap.data, kidMap.data + kidMap.size); + gst_buffer_unmap(kidBuf, &kidMap); + } + } + } + } + + // Pre-decrypt key status check: return OUTPUT_RESTRICTED immediately (no sleep) so + // the caller (MediaKeysServerInternal::decrypt) can retry from the GStreamer thread. + if (!keyId.empty()) + { + const ::KeyStatus preStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (preStatus == OutputRestricted || preStatus == OutputRestrictedHDCP22) + { + + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Pre decrypt)\n"); + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + OpenCDMError result = m_ocdmGstSessionDecryptBufferOnce(m_session, encrypted, caps); + + // Post-decrypt status check: a failed decrypt during HDCP reauth may not carry a + // specific error code, so confirm via key status before signalling the caller to retry. + if (result != ERROR_NONE && !keyId.empty()) + { + const ::KeyStatus postStatus = + opencdm_session_status(m_session, keyId.data(), static_cast(keyId.size())); + if (postStatus == OutputRestricted || postStatus == OutputRestrictedHDCP22) + { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Post decrypt)\n"); + return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } + } + + return convertOpenCdmError(result); + } + + // Fallback: adapter without _once handles retries internally. OpenCDMError status = opencdm_gstreamer_session_decrypt_buffer(m_session, encrypted, caps); return convertOpenCdmError(status); } From 31c94c5083175cd22c15ab7ab270d6067960dc2b Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Wed, 20 May 2026 22:20:21 +0000 Subject: [PATCH 09/21] Adding dlopen() --- media/server/main/source/MediaKeysServerInternal.cpp | 7 +++++++ wrappers/source/OcdmSession.cpp | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 3f5d69b28..a6ccf09ee 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -670,6 +670,13 @@ MediaKeyErrorStatus MediaKeysServerInternal::decryptInternal(int32_t keySessionI return status; } +bool MediaKeysServerInternal::isNetflixPlayreadyKeySystem() const +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + return m_kKeySystem.find("netflix") != std::string::npos; +} + + void MediaKeysServerInternal::ping(std::unique_ptr &&heartbeatHandler) { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/wrappers/source/OcdmSession.cpp b/wrappers/source/OcdmSession.cpp index 821cd3043..80e8a1954 100644 --- a/wrappers/source/OcdmSession.cpp +++ b/wrappers/source/OcdmSession.cpp @@ -122,10 +122,10 @@ OcdmSession::OcdmSession(struct OpenCDMSystem *systemHandle, IOcdmSessionClient std::call_once(flag, []() { + void* handle = dlopen("libocdm.so", RTLD_LAZY); m_ocdmGstSessionDecryptEx = (OcdmGstSessionDecryptExFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_ex"); - m_ocdmGstSessionDecryptBufferOnce = - (OcdmGstSessionDecryptBufferOnceFn)dlsym(RTLD_DEFAULT, "opencdm_gstreamer_session_decrypt_buffer_once"); + m_ocdmGstSessionDecryptBufferOnce = (OcdmGstSessionDecryptBufferOnceFn)dlsym(handle,"opencdm_gstreamer_session_decrypt_buffer_once"); if(m_ocdmGstSessionDecryptBufferOnce != NULL){ RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : m_ocdmGstSessionDecryptBufferOnce exists\n"); } From 1467ce83682addc9aa51c41c0ace55dda0c09a12 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Wed, 20 May 2026 22:36:36 +0000 Subject: [PATCH 10/21] More logging for keyStatus --- wrappers/source/OcdmSession.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/wrappers/source/OcdmSession.cpp b/wrappers/source/OcdmSession.cpp index 80e8a1954..1c1b33638 100644 --- a/wrappers/source/OcdmSession.cpp +++ b/wrappers/source/OcdmSession.cpp @@ -238,7 +238,10 @@ MediaKeyErrorStatus OcdmSession::decryptBuffer(GstBuffer *encrypted, GstCaps *ca RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Pre decrypt)\n"); return MediaKeyErrorStatus::OUTPUT_RESTRICTED; + } else { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning error(Pre decrypt)\n"); } + } OpenCDMError result = m_ocdmGstSessionDecryptBufferOnce(m_session, encrypted, caps); @@ -253,7 +256,9 @@ MediaKeyErrorStatus OcdmSession::decryptBuffer(GstBuffer *encrypted, GstCaps *ca { RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning MediaKeyErrorStatus::OUTPUT_RESTRICTED(Post decrypt)\n"); return MediaKeyErrorStatus::OUTPUT_RESTRICTED; - } + } else { + RIALTO_COMMON_LOG_ERROR("DEBUG PURPOSE : OcdmSession::decryptBuffer() : returning error(Post decrypt)\n"); + } } return convertOpenCdmError(result); From ffee15ba1c458545bdf05bc469486da8a894b6bc Mon Sep 17 00:00:00 2001 From: balasaraswathy-n Date: Thu, 21 May 2026 15:38:41 +0530 Subject: [PATCH 11/21] Update MediaKeysServerInternal.cpp --- media/server/main/source/MediaKeysServerInternal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index a6ccf09ee..fa66f7b9a 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -81,8 +81,8 @@ std::shared_ptr IMediaKeysFactory::createFactory() namespace firebolt::rialto::server { -constexpr std::chrono::milliseconds kOutputRestrictedRetryInterval{250}; -constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{6}; +constexpr std::chrono::seconds kOutputRestrictedRetryInterval{1}; +constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{4}; int32_t generateSessionId() { From 881e8434cc3052a105264a554cbfe7fed43fad63 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Thu, 21 May 2026 14:53:16 +0000 Subject: [PATCH 12/21] Revert interval time changes --- media/server/main/source/MediaKeysServerInternal.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index fa66f7b9a..a309332fa 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -81,8 +81,8 @@ std::shared_ptr IMediaKeysFactory::createFactory() namespace firebolt::rialto::server { -constexpr std::chrono::seconds kOutputRestrictedRetryInterval{1}; -constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{4}; +constexpr std::chrono::seconds kOutputRestrictedRetryInterval{250}; +constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{6}; int32_t generateSessionId() { From f9fd54af80e336b8f60df141233056b63b009484 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Thu, 21 May 2026 17:45:18 +0000 Subject: [PATCH 13/21] Reverting the interval change --- media/server/main/source/MediaKeysServerInternal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index a309332fa..a6ccf09ee 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -81,7 +81,7 @@ std::shared_ptr IMediaKeysFactory::createFactory() namespace firebolt::rialto::server { -constexpr std::chrono::seconds kOutputRestrictedRetryInterval{250}; +constexpr std::chrono::milliseconds kOutputRestrictedRetryInterval{250}; constexpr std::chrono::seconds kOutputRestrictedRetryTimeout{6}; int32_t generateSessionId() From c60ab3c193c4d16696f8e4707d208cddf5254b69 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 00:27:30 +0000 Subject: [PATCH 14/21] Increase the priority of ping routine --- media/server/main/source/MediaKeysServerInternal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index a6ccf09ee..72c920055 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -682,7 +682,7 @@ void MediaKeysServerInternal::ping(std::unique_ptr &&heartbea RIALTO_SERVER_LOG_DEBUG("entry:"); auto task = [&]() { heartbeatHandler.reset(); }; - m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + m_mainThread->enqueuePriorityTaskAndWait(m_mainThreadClientId, task); } MediaKeyErrorStatus MediaKeysServerInternal::getMetricSystemData(std::vector &buffer) From 004ba6d2539f1670c07c741a67e88f000a3c0040 Mon Sep 17 00:00:00 2001 From: varatharajan568 <130632918+varatharajan568@users.noreply.github.com> Date: Fri, 22 May 2026 07:41:49 +0530 Subject: [PATCH 15/21] Update MediaKeysServerInternal.cpp --- media/server/main/source/MediaKeysServerInternal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 72c920055..82283d982 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -679,7 +679,7 @@ bool MediaKeysServerInternal::isNetflixPlayreadyKeySystem() const void MediaKeysServerInternal::ping(std::unique_ptr &&heartbeatHandler) { - RIALTO_SERVER_LOG_DEBUG("entry:"); + RIALTO_SERVER_LOG_ERROR("entry:"); auto task = [&]() { heartbeatHandler.reset(); }; m_mainThread->enqueuePriorityTaskAndWait(m_mainThreadClientId, task); From 06af3626f83375fec26e691ae039c7e7d97c65c9 Mon Sep 17 00:00:00 2001 From: varatharajan568 <130632918+varatharajan568@users.noreply.github.com> Date: Fri, 22 May 2026 07:43:16 +0530 Subject: [PATCH 16/21] Update MediaKeysServerInternal.cpp --- media/server/main/source/MediaKeysServerInternal.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 82283d982..2f1cb7ee7 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -683,6 +683,7 @@ void MediaKeysServerInternal::ping(std::unique_ptr &&heartbea auto task = [&]() { heartbeatHandler.reset(); }; m_mainThread->enqueuePriorityTaskAndWait(m_mainThreadClientId, task); + RIALTO_SERVER_LOG_ERROR("exit:"); } MediaKeyErrorStatus MediaKeysServerInternal::getMetricSystemData(std::vector &buffer) From d8312264749aff8b7ce5d2963d2e02dfc6aa70d8 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 19:22:36 +0000 Subject: [PATCH 17/21] Comment the mutex for ping --- media/server/service/source/CdmService.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index 8ea716f61..f79db0cf1 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -494,6 +494,7 @@ MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypt { RIALTO_SERVER_LOG_DEBUG("CdmService requested to decrypt, key session id: %d", keySessionId); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : CdmService : decrypt entry"); std::lock_guard lock{m_mediaKeysMutex}; auto mediaKeysHandleIter{m_sessionInfo.find(keySessionId)}; if (mediaKeysHandleIter == m_sessionInfo.end()) @@ -580,12 +581,14 @@ void CdmService::decrementSessionIdUsageCounter(int32_t keySessionId) void CdmService::ping(const std::shared_ptr &heartbeatProcedure) { - std::lock_guard lock{m_mediaKeysMutex}; +// std::lock_guard lock{m_mediaKeysMutex}; + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping entry") for (const auto &mediaKeyPair : m_mediaKeys) { auto &mediaKeys = mediaKeyPair.second; mediaKeys->ping(heartbeatProcedure->createHandler()); } + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping exit") } MediaKeyErrorStatus CdmService::getMetricSystemData(int mediaKeysHandle, std::vector &buffer) From 7f1c8efc590869732e2c2ab33d37c2773d9237ea Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 19:32:11 +0000 Subject: [PATCH 18/21] Uncommenting the mutex with ping --- media/server/service/source/CdmService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index f79db0cf1..6892daeb3 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -581,7 +581,7 @@ void CdmService::decrementSessionIdUsageCounter(int32_t keySessionId) void CdmService::ping(const std::shared_ptr &heartbeatProcedure) { -// std::lock_guard lock{m_mediaKeysMutex}; + std::lock_guard lock{m_mediaKeysMutex}; RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping entry") for (const auto &mediaKeyPair : m_mediaKeys) { From 88573bae94fba1126896f6f8a56b9e00923c9f1f Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 19:47:43 +0000 Subject: [PATCH 19/21] Commenting the mutex with ping --- media/server/service/source/CdmService.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index 6892daeb3..71e467252 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -581,14 +581,14 @@ void CdmService::decrementSessionIdUsageCounter(int32_t keySessionId) void CdmService::ping(const std::shared_ptr &heartbeatProcedure) { - std::lock_guard lock{m_mediaKeysMutex}; - RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping entry") +// std::lock_guard lock{m_mediaKeysMutex}; + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping entry"); for (const auto &mediaKeyPair : m_mediaKeys) { auto &mediaKeys = mediaKeyPair.second; mediaKeys->ping(heartbeatProcedure->createHandler()); } - RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping exit") + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping exit"); } MediaKeyErrorStatus CdmService::getMetricSystemData(int mediaKeysHandle, std::vector &buffer) From e0a350d6d0ecd90382870ab67b6e25bd21bc06fe Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 19:57:27 +0000 Subject: [PATCH 20/21] Handling ping and decrypt with rialto --- media/server/service/source/CdmService.cpp | 41 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index 71e467252..e16dc58c9 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -492,7 +492,26 @@ bool CdmService::isServerCertificateSupported(const std::string &keySystem) MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) { - RIALTO_SERVER_LOG_DEBUG("CdmService requested to decrypt, key session id: %d", keySessionId); + + RIALTO_SERVER_LOG_ERROR("CdmService requested to decrypt, key session id: %d", keySessionId); + + // Acquire mutex ONLY to resolve the pointer — then release immediately + IMediaKeysServerInternal *mediaKeys{nullptr}; + { + std::lock_guard lock{m_mediaKeysMutex}; + auto mediaKeysHandleIter{m_sessionInfo.find(keySessionId)}; + if (mediaKeysHandleIter == m_sessionInfo.end()) + { + RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exist", keySessionId); + return MediaKeyErrorStatus::FAIL; + } + mediaKeys = m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle].get(); + } + // Mutex released — ping() can now proceed freely + + return mediaKeys->decrypt(keySessionId, encrypted, caps); + +/* RIALTO_SERVER_LOG_DEBUG("CdmService requested to decrypt, key session id: %d", keySessionId); RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE : CdmService : decrypt entry"); std::lock_guard lock{m_mediaKeysMutex}; @@ -502,7 +521,7 @@ MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypt RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exists", keySessionId); return MediaKeyErrorStatus::FAIL; } - return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->decrypt(keySessionId, encrypted, caps); + return m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle]->decrypt(keySessionId, encrypted, caps);*/ } bool CdmService::isNetflixPlayreadyKeySystem(int32_t keySessionId) @@ -581,14 +600,28 @@ void CdmService::decrementSessionIdUsageCounter(int32_t keySessionId) void CdmService::ping(const std::shared_ptr &heartbeatProcedure) { -// std::lock_guard lock{m_mediaKeysMutex}; + + std::unique_lock lock{m_mediaKeysMutex, std::try_to_lock}; + if (!lock.owns_lock()) + { + // Server is alive but busy in decrypt retry — acknowledge ping + RIALTO_SERVER_LOG_WARN("CdmService::ping - mutex busy (decrypt in progress), skipping CDM ping"); + return; // HeartbeatProcedure destructor sends success ack + } + for (const auto &mediaKeyPair : m_mediaKeys) + { + auto &mediaKeys = mediaKeyPair.second; + mediaKeys->ping(heartbeatProcedure->createHandler()); + } + + /*std::lock_guard lock{m_mediaKeysMutex}; RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping entry"); for (const auto &mediaKeyPair : m_mediaKeys) { auto &mediaKeys = mediaKeyPair.second; mediaKeys->ping(heartbeatProcedure->createHandler()); } - RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping exit"); + RIALTO_SERVER_LOG_ERROR("DEBUG PURPOSE: ping exit");*/ } MediaKeyErrorStatus CdmService::getMetricSystemData(int mediaKeysHandle, std::vector &buffer) From acce42dff1effc1491fec29babc0d55900858733 Mon Sep 17 00:00:00 2001 From: LekshmiMR Date: Fri, 22 May 2026 21:02:48 +0000 Subject: [PATCH 21/21] Handling corner cases especially crash --- .../main/include/MediaKeysServerInternal.h | 6 +++++ .../main/source/MediaKeysServerInternal.cpp | 9 +++++++- media/server/service/source/CdmService.cpp | 23 ++++++++----------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/media/server/main/include/MediaKeysServerInternal.h b/media/server/main/include/MediaKeysServerInternal.h index 6e934e004..f0c8c5527 100644 --- a/media/server/main/include/MediaKeysServerInternal.h +++ b/media/server/main/include/MediaKeysServerInternal.h @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include namespace firebolt::rialto::server { @@ -149,6 +152,9 @@ class MediaKeysServerInternal : public IMediaKeysServerInternal */ uint32_t m_mainThreadClientId; + + std::atomic_bool m_isShuttingDown{false}; + /** * @brief Creates a session internally, only to be called on the main thread. * diff --git a/media/server/main/source/MediaKeysServerInternal.cpp b/media/server/main/source/MediaKeysServerInternal.cpp index 2f1cb7ee7..0ee356f28 100644 --- a/media/server/main/source/MediaKeysServerInternal.cpp +++ b/media/server/main/source/MediaKeysServerInternal.cpp @@ -178,7 +178,7 @@ MediaKeysServerInternal::MediaKeysServerInternal( MediaKeysServerInternal::~MediaKeysServerInternal() { RIALTO_SERVER_LOG_DEBUG("entry:"); - + m_isShuttingDown = true; auto task = [&]() { m_ocdmSystem.reset(); @@ -643,8 +643,15 @@ MediaKeyErrorStatus MediaKeysServerInternal::decrypt(int32_t keySessionId, GstBu { break; } + if (m_isShuttingDown.load()) + { + RIALTO_SERVER_LOG_ERROR("Decrypt retry aborted — shutdown in progress"); + break; + } RIALTO_SERVER_LOG_WARN("Decrypt returned OUTPUT_RESTRICTED, retrying after delay"); + std::this_thread::sleep_for(kOutputRestrictedRetryInterval); + } while (std::chrono::steady_clock::now() < deadline); return status; diff --git a/media/server/service/source/CdmService.cpp b/media/server/service/source/CdmService.cpp index e16dc58c9..3f5fcb881 100644 --- a/media/server/service/source/CdmService.cpp +++ b/media/server/service/source/CdmService.cpp @@ -493,23 +493,18 @@ bool CdmService::isServerCertificateSupported(const std::string &keySystem) MediaKeyErrorStatus CdmService::decrypt(int32_t keySessionId, GstBuffer *encrypted, GstCaps *caps) { - RIALTO_SERVER_LOG_ERROR("CdmService requested to decrypt, key session id: %d", keySessionId); - - // Acquire mutex ONLY to resolve the pointer — then release immediately IMediaKeysServerInternal *mediaKeys{nullptr}; { std::lock_guard lock{m_mediaKeysMutex}; - auto mediaKeysHandleIter{m_sessionInfo.find(keySessionId)}; - if (mediaKeysHandleIter == m_sessionInfo.end()) - { - RIALTO_SERVER_LOG_ERROR("Media keys handle for mksId: %d does not exist", keySessionId); - return MediaKeyErrorStatus::FAIL; - } - mediaKeys = m_mediaKeys[mediaKeysHandleIter->second.mediaKeysHandle].get(); - } - // Mutex released — ping() can now proceed freely - - return mediaKeys->decrypt(keySessionId, encrypted, caps); + auto iter = m_sessionInfo.find(keySessionId); + if (iter == m_sessionInfo.end()) return MediaKeyErrorStatus::FAIL; + mediaKeys = m_mediaKeys[iter->second.mediaKeysHandle].get(); + // Increment ref counter to prevent destruction while in use + ++iter->second.refCounter; + } + auto status = mediaKeys->decrypt(keySessionId, encrypted, caps); + decrementSessionIdUsageCounter(keySessionId); // May trigger deferred release + return status; /* RIALTO_SERVER_LOG_DEBUG("CdmService requested to decrypt, key session id: %d", keySessionId);