diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index 5dafd54a3..db70b6343 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -105,6 +105,8 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool flush(int32_t sourceId, bool resetTime) override; + bool setSourcePosition(int32_t sourceId, int64_t position) override; + private: /** * @brief The media player client ipc. diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index ec8ba3af1..f962e55e1 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -240,6 +240,18 @@ class IMediaPipelineIpc * @retval true on success. */ virtual bool flush(int32_t sourceId, bool resetTime) = 0; + + /** + * @brief Set the source position in nanoseconds. + * + * This method sets the start position for a source. + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] position : The position in nanoseconds. + * + * @retval true on success. + */ + virtual bool setSourcePosition(int32_t sourceId, int64_t position) = 0; }; }; // namespace firebolt::rialto::client diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index faf48f8b8..abd25ed94 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -552,7 +552,7 @@ bool MediaPipelineIpc::getPosition(int64_t &position) // check the result if (ipcController->Failed()) { - RIALTO_CLIENT_LOG_ERROR("failed to set position due to '%s'", ipcController->ErrorText().c_str()); + RIALTO_CLIENT_LOG_ERROR("failed to get position due to '%s'", ipcController->ErrorText().c_str()); return false; } @@ -777,6 +777,38 @@ bool MediaPipelineIpc::flush(int32_t sourceId, bool resetTime) return true; } +bool MediaPipelineIpc::setSourcePosition(int32_t sourceId, int64_t position) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::SetSourcePositionRequest request; + + request.set_session_id(m_sessionId); + request.set_source_id(sourceId); + request.set_position(position); + + firebolt::rialto::SetSourcePositionResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->setSourcePosition(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to set source position due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + return true; +} + void MediaPipelineIpc::onPlaybackStateUpdated(const std::shared_ptr &event) { /* Ignore event if not for this session */ diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index a28fda354..78ec90e46 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -163,6 +163,8 @@ class MediaPipeline : public IMediaPipeline, public IMediaPipelineIpcClient, pub bool flush(int32_t sourceId, bool resetTime) override; + bool setSourcePosition(int32_t sourceId, int64_t position) override; + void notifyApplicationState(ApplicationState state) override; protected: diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index f5f3106c3..08bc72687 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -488,6 +488,13 @@ bool MediaPipeline::flush(int32_t sourceId, bool resetTime) return false; } +bool MediaPipeline::setSourcePosition(int32_t sourceId, int64_t position) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + return m_mediaPipelineIpc->setSourcePosition(sourceId, position); +} + void MediaPipeline::discardNeedDataRequest(uint32_t needDataRequestId) { // Find the needDataRequest for this needDataRequestId diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index 1936c5c84..818797e97 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -1206,6 +1206,18 @@ class IMediaPipeline * @retval true on success. */ virtual bool flush(int32_t sourceId, bool resetTime) = 0; + + /** + * @brief Set the source position in nanoseconds. + * + * This method sets the start position for a source. + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] position : The position in nanoseconds. + * + * @retval true on success. + */ + virtual bool setSourcePosition(int32_t sourceId, int64_t position) = 0; }; }; // namespace firebolt::rialto diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 576b4c58b..7e1b9f1c5 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -58,6 +58,7 @@ add_library( source/tasks/generic/SetVideoGeometry.cpp source/tasks/generic/SetVolume.cpp source/tasks/generic/SetMute.cpp + source/tasks/generic/SetSourcePosition.cpp source/tasks/generic/Shutdown.cpp source/tasks/generic/Stop.cpp source/tasks/generic/Underflow.cpp diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index f25afdbbd..7d82d050b 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -225,6 +225,20 @@ struct GenericPlayerContext * Attribute can be used only in worker thread */ bool wereAllSourcesAttached{false}; + + /** + * @brief Flag used to check if FinishSetupSource is finished. It is needed to avoid need data overwriting. + * + * Attribute can be used only in worker thread + */ + bool setupSourceFinished{false}; + + /** + * @brief Queued source positions. Used by SetSourcePosition task to request pushing new sample. + * + * Attribute can be used only in worker thread + */ + std::map initialPositions; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 7790adea1..29a612383 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -117,6 +117,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva bool getMute(bool &mute) override; void ping(std::unique_ptr &&heartbeatHandler) override; void flush(const MediaSourceType &mediaSourceType, bool resetTime) override; + void setSourcePosition(const MediaSourceType &mediaSourceType, int64_t position) override; private: void scheduleNeedMediaData(GstAppSrc *src) override; @@ -225,6 +226,16 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva */ bool setCodecData(GstCaps *caps, const std::shared_ptr &codecData) const; + /** + * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. + * + * @param[in] source : The Gst Source element, that should receive new sample + * @param[in] buffer : The next GstBuffer to push + * + * @retval True if operation was performed + */ + bool pushSampleIfRequired(GstElement *source, GstBuffer *buffer); + private: /** * @brief The player context. diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 667b19dd7..7dd767527 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -344,15 +344,26 @@ class IGenericPlayerTaskFactory * @brief Creates a Flush task. * * @param[in] context : The GstPlayer context - * @param[in] player : The GstGenericPlayer instance * @param[in] type : The media source type to flush * @param[in] resetTime : True if time should be reset * * @retval the new Flush task instance. */ - virtual std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, - bool resetTime) const = 0; + virtual std::unique_ptr + createFlush(GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, bool resetTime) const = 0; + + /** + * @brief Creates a SetSourcePosition task. + * + * @param[in] context : The GstPlayer context + * @param[in] type : The media source type to set position + * @param[in] position : The new source position + * + * @retval the new SetSourcePosition task instance. + */ + virtual std::unique_ptr createSetSourcePosition(GenericPlayerContext &context, + const firebolt::rialto::MediaSourceType &type, + std::int64_t position) const = 0; }; } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/include/tasks/generic/Flush.h b/media/server/gstplayer/include/tasks/generic/Flush.h index 6345b1d00..426a758c8 100644 --- a/media/server/gstplayer/include/tasks/generic/Flush.h +++ b/media/server/gstplayer/include/tasks/generic/Flush.h @@ -22,7 +22,6 @@ #include "GenericPlayerContext.h" #include "IGstGenericPlayerClient.h" -#include "IGstGenericPlayerPrivate.h" #include "IGstWrapper.h" #include "IPlayerTask.h" #include @@ -32,7 +31,7 @@ namespace firebolt::rialto::server::tasks::generic class Flush : public IPlayerTask { public: - Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, + Flush(GenericPlayerContext &context, IGstGenericPlayerClient *client, std::shared_ptr gstWrapper, const MediaSourceType &type, bool resetTime); ~Flush() override; @@ -40,7 +39,6 @@ class Flush : public IPlayerTask private: GenericPlayerContext &m_context; - IGstGenericPlayerPrivate &m_player; IGstGenericPlayerClient *m_gstPlayerClient; std::shared_ptr m_gstWrapper; MediaSourceType m_type; diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index e8cf59f0d..28d1b4323 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -87,9 +87,11 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory std::unique_ptr createRenderFrame(GenericPlayerContext &context, IGstGenericPlayerPrivate &player) const override; std::unique_ptr createPing(std::unique_ptr &&heartbeatHandler) const override; - std::unique_ptr createFlush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, + std::unique_ptr createFlush(GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, bool resetTime) const override; + std::unique_ptr createSetSourcePosition(GenericPlayerContext &context, + const firebolt::rialto::MediaSourceType &type, + std::int64_t position) const override; private: IGstGenericPlayerClient *m_client; diff --git a/media/server/gstplayer/include/tasks/generic/SetSourcePosition.h b/media/server/gstplayer/include/tasks/generic/SetSourcePosition.h new file mode 100644 index 000000000..da02231c7 --- /dev/null +++ b/media/server/gstplayer/include/tasks/generic/SetSourcePosition.h @@ -0,0 +1,50 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_SOURCE_POSITION_H_ +#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_SOURCE_POSITION_H_ + +#include "GenericPlayerContext.h" +#include "IGstGenericPlayerClient.h" +#include "IGstWrapper.h" +#include "IPlayerTask.h" +#include +#include + +namespace firebolt::rialto::server::tasks::generic +{ +class SetSourcePosition : public IPlayerTask +{ +public: + SetSourcePosition(GenericPlayerContext &context, IGstGenericPlayerClient *client, + const std::shared_ptr &gstWrapper, + const MediaSourceType &type, std::int64_t position); + ~SetSourcePosition() override; + void execute() const override; + +private: + GenericPlayerContext &m_context; + IGstGenericPlayerClient *m_gstPlayerClient; + std::shared_ptr m_gstWrapper; + MediaSourceType m_type; + std::int64_t m_position; +}; +} // namespace firebolt::rialto::server::tasks::generic + +#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_SOURCE_POSITION_H_ diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index 38d0953f3..409d6ee07 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -260,6 +260,16 @@ class IGstGenericPlayer * */ virtual void flush(const MediaSourceType &mediaSourceType, bool resetTime) = 0; + + /** + * @brief Set the source position in nanoseconds. + * + * This method sets the start position for a source. + * + * @param[in] mediaSourceType : The media source type to flush. + * @param[in] position : The position in nanoseconds. + */ + virtual void setSourcePosition(const MediaSourceType &mediaSourceType, int64_t position) = 0; }; }; // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 563cd3b2c..9579147e3 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -485,6 +485,15 @@ void GstGenericPlayer::attachAudioData() if (m_context.audioAppSrc) { + GstBuffer *firstBuffer{nullptr}; + if (!m_context.audioBuffers.empty()) + { + firstBuffer = m_context.audioBuffers.front(); + } + if (pushSampleIfRequired(m_context.audioAppSrc, firstBuffer) && firstBuffer) + { + m_context.audioBuffers.pop_front(); + } for (GstBuffer *buffer : m_context.audioBuffers) { m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(m_context.audioAppSrc), buffer); @@ -524,6 +533,15 @@ void GstGenericPlayer::attachVideoData() } if (m_context.videoAppSrc) { + GstBuffer *firstBuffer{nullptr}; + if (!m_context.videoBuffers.empty()) + { + firstBuffer = m_context.videoBuffers.front(); + } + if (pushSampleIfRequired(m_context.videoAppSrc, firstBuffer) && firstBuffer) + { + m_context.videoBuffers.pop_front(); + } for (GstBuffer *buffer : m_context.videoBuffers) { m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(m_context.videoAppSrc), buffer); @@ -630,6 +648,41 @@ bool GstGenericPlayer::setCodecData(GstCaps *caps, const std::shared_ptrgstSegmentNew()}; + m_gstWrapper->gstSegmentInit(segment, GST_FORMAT_TIME); + if (!m_gstWrapper->gstSegmentDoSeek(segment, m_context.playbackRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, + GST_SEEK_TYPE_SET, initialPosition->second, GST_SEEK_TYPE_SET, + GST_CLOCK_TIME_NONE, nullptr)) + { + RIALTO_SERVER_LOG_WARN("Segment seek failed."); + m_gstWrapper->gstSegmentFree(segment); + m_context.initialPositions.erase(initialPosition); + return false; + } + + RIALTO_SERVER_LOG_MIL("New segment: [%" GST_TIME_FORMAT ", %" GST_TIME_FORMAT "], rate: %f \n", + GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), segment->rate); + + GstCaps *currentCaps = m_gstWrapper->gstAppSrcGetCaps(GST_APP_SRC(source)); + GstSample *sample = m_gstWrapper->gstSampleNew(buffer, currentCaps, segment, nullptr); + m_gstWrapper->gstAppSrcPushSample(GST_APP_SRC(source), sample); + m_gstWrapper->gstSampleUnref(sample); + m_gstWrapper->gstCapsUnref(currentCaps); + + m_gstWrapper->gstSegmentFree(segment); + m_context.initialPositions.erase(initialPosition); + + return true; +} + void GstGenericPlayer::scheduleNeedMediaData(GstAppSrc *src) { if (m_workerThread) @@ -937,7 +990,15 @@ void GstGenericPlayer::flush(const MediaSourceType &mediaSourceType, bool resetT { if (m_workerThread) { - m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, *this, mediaSourceType, resetTime)); + m_workerThread->enqueueTask(m_taskFactory->createFlush(m_context, mediaSourceType, resetTime)); + } +} + +void GstGenericPlayer::setSourcePosition(const MediaSourceType &mediaSourceType, int64_t position) +{ + if (m_workerThread) + { + m_workerThread->enqueueTask(m_taskFactory->createSetSourcePosition(m_context, mediaSourceType, position)); } } diff --git a/media/server/gstplayer/source/GstSrc.cpp b/media/server/gstplayer/source/GstSrc.cpp index 08014e4f5..1af23598b 100644 --- a/media/server/gstplayer/source/GstSrc.cpp +++ b/media/server/gstplayer/source/GstSrc.cpp @@ -364,7 +364,7 @@ void GstSrc::setupAndAddAppArc(IDecryptionService *decryptionService, GstElement { // Configure and add appsrc m_glibWrapper->gObjectSet(streamInfo.appSrc, "block", FALSE, "format", GST_FORMAT_TIME, "stream-type", - GST_APP_STREAM_TYPE_STREAM, "min-percent", 20, nullptr); + GST_APP_STREAM_TYPE_STREAM, "min-percent", 20, "handle-segment-change", TRUE, nullptr); m_gstWrapper->gstAppSrcSetCallbacks(GST_APP_SRC(streamInfo.appSrc), callbacks, userData, nullptr); if (type == firebolt::rialto::MediaSourceType::VIDEO) { diff --git a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp index a9321a8b2..90028c2ca 100644 --- a/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/FinishSetupSource.cpp @@ -120,5 +120,7 @@ void FinishSetupSource::execute() const // Notify GstPlayerClient of Idle state once setup has finished if (m_gstPlayerClient) m_gstPlayerClient->notifyPlaybackState(PlaybackState::IDLE); + + m_context.setupSourceFinished = true; } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 47abc363e..85b1508ee 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -19,15 +19,13 @@ #include "tasks/generic/Flush.h" #include "RialtoServerLogging.h" -#include "tasks/generic/NeedData.h" namespace firebolt::rialto::server::tasks::generic { -Flush::Flush(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client, +Flush::Flush(GenericPlayerContext &context, IGstGenericPlayerClient *client, std::shared_ptr gstWrapper, const MediaSourceType &type, bool resetTime) - : m_context{context}, m_player{player}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, - m_resetTime{resetTime} + : m_context{context}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, m_resetTime{resetTime} { RIALTO_SERVER_LOG_DEBUG("Constructing Flush"); } @@ -88,23 +86,6 @@ void Flush::execute() const } m_gstPlayerClient->invalidateActiveRequests(m_type); - // Query current segment, if we don't reset time - std::int64_t position{0}; - double rate{1.0}; - gint64 stop{-1}; - if (!m_resetTime) - { - GstFormat format{GST_FORMAT_UNDEFINED}; - gint64 start{0}; - m_gstWrapper->gstElementQueryPosition(m_context.pipeline, GST_FORMAT_TIME, &position); - GstQuery *query{m_gstWrapper->gstQueryNewSegment(GST_FORMAT_TIME)}; - if (m_gstWrapper->gstElementQuery(m_context.pipeline, query)) - { - m_gstWrapper->gstQueryParseSegment(query, &rate, &format, &start, &stop); - } - m_gstWrapper->gstQueryUnref(query); - } - // Flush source GstEvent *flushStart = m_gstWrapper->gstEventNewFlushStart(); if (!m_gstWrapper->gstElementSendEvent(source, flushStart)) @@ -112,36 +93,17 @@ void Flush::execute() const RIALTO_SERVER_LOG_WARN("failed to send flush-start event"); } - // Update segment if we don't reset time - if (!m_resetTime) - { - GstSegment *segment{m_gstWrapper->gstSegmentNew()}; - m_gstWrapper->gstSegmentInit(segment, GST_FORMAT_TIME); - m_gstWrapper->gstSegmentDoSeek(segment, rate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, position, - GST_SEEK_TYPE_SET, stop, nullptr); - - RIALTO_SERVER_LOG_INFO("Set new seamless segment: [%" GST_TIME_FORMAT ", %" GST_TIME_FORMAT "], rate: %f \n", - GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), segment->rate); - - if (!m_gstWrapper->gstBaseSrcNewSeamlessSegment(GST_BASE_SRC(source), segment->start, segment->stop, - segment->start)) - { - RIALTO_SERVER_LOG_WARN("Failed to set seamless segment event"); - } - m_gstWrapper->gstSegmentFree(segment); - } - GstEvent *flushStop = m_gstWrapper->gstEventNewFlushStop(m_resetTime); if (!m_gstWrapper->gstElementSendEvent(source, flushStop)) { RIALTO_SERVER_LOG_WARN("failed to send flush-stop event"); } + // Reset Eos info + m_context.endOfStreamInfo.erase(m_type); + m_context.eosNotified = false; + // Notify client, that flush has been finished m_gstPlayerClient->notifySourceFlushed(m_type); - - // Trigger NeedData for flushed source - NeedData task{m_context, m_gstPlayerClient, GST_APP_SRC(source)}; - task.execute(); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 4b4ad2797..78a7d2df3 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -38,6 +38,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetVideoGeometry.h" #include "tasks/generic/SetVolume.h" #include "tasks/generic/SetupElement.h" @@ -231,10 +232,15 @@ 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 { - return std::make_unique(context, player, m_client, m_gstWrapper, type, resetTime); + return std::make_unique(context, m_client, m_gstWrapper, type, resetTime); +} + +std::unique_ptr GenericPlayerTaskFactory::createSetSourcePosition( + GenericPlayerContext &context, const firebolt::rialto::MediaSourceType &type, std::int64_t position) const +{ + return std::make_unique(context, m_client, m_gstWrapper, type, position); } } // namespace firebolt::rialto::server diff --git a/media/server/gstplayer/source/tasks/generic/SetSourcePosition.cpp b/media/server/gstplayer/source/tasks/generic/SetSourcePosition.cpp new file mode 100644 index 000000000..11b6160fa --- /dev/null +++ b/media/server/gstplayer/source/tasks/generic/SetSourcePosition.cpp @@ -0,0 +1,75 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SetSourcePosition.h" +#include "RialtoServerLogging.h" +#include "tasks/generic/NeedData.h" + +namespace firebolt::rialto::server::tasks::generic +{ +SetSourcePosition::SetSourcePosition(GenericPlayerContext &context, IGstGenericPlayerClient *client, + const std::shared_ptr &gstWrapper, + const MediaSourceType &type, std::int64_t position) + : m_context{context}, m_gstPlayerClient{client}, m_gstWrapper{gstWrapper}, m_type{type}, m_position{position} +{ + RIALTO_SERVER_LOG_DEBUG("Constructing SetSourcePosition"); +} + +SetSourcePosition::~SetSourcePosition() +{ + RIALTO_SERVER_LOG_DEBUG("SetSourcePosition finished"); +} + +void SetSourcePosition::execute() const +{ + RIALTO_SERVER_LOG_DEBUG("Executing SetSourcePosition"); + + if (MediaSourceType::UNKNOWN == m_type) + { + RIALTO_SERVER_LOG_WARN("failed to set source position - source type is unknown"); + return; + } + + // Get source first + 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 set source position - source is NULL"); + return; + } + + m_context.initialPositions[source] = m_position; + + if (m_context.setupSourceFinished) + { + // Reset Eos info + m_context.endOfStreamInfo.erase(m_type); + m_context.eosNotified = false; + + // Trigger NeedData for source + NeedData task{m_context, m_gstPlayerClient, GST_APP_SRC(source)}; + task.execute(); + } +} +} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/ipc/include/MediaPipelineModuleService.h b/media/server/ipc/include/MediaPipelineModuleService.h index 3d7fc354b..e3bc66d59 100644 --- a/media/server/ipc/include/MediaPipelineModuleService.h +++ b/media/server/ipc/include/MediaPipelineModuleService.h @@ -96,6 +96,10 @@ class MediaPipelineModuleService : public IMediaPipelineModuleService ::firebolt::rialto::GetMuteResponse *response, ::google::protobuf::Closure *done) override; void flush(::google::protobuf::RpcController *controller, const ::firebolt::rialto::FlushRequest *request, ::firebolt::rialto::FlushResponse *response, ::google::protobuf::Closure *done) override; + void setSourcePosition(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetSourcePositionRequest *request, + ::firebolt::rialto::SetSourcePositionResponse *response, + ::google::protobuf::Closure *done) override; private: service::IMediaPipelineService &m_mediaPipelineService; diff --git a/media/server/ipc/source/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 0ddfb807a..898e9db7c 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -620,4 +620,18 @@ void MediaPipelineModuleService::flush(::google::protobuf::RpcController *contro done->Run(); } + +void MediaPipelineModuleService::setSourcePosition(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetSourcePositionRequest *request, + ::firebolt::rialto::SetSourcePositionResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + if (!m_mediaPipelineService.setSourcePosition(request->session_id(), request->source_id(), request->position())) + { + RIALTO_SERVER_LOG_ERROR("Set Source Position failed."); + controller->SetFailed("Operation failed"); + } + done->Run(); +} } // namespace firebolt::rialto::server::ipc diff --git a/media/server/main/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index 897e2d20f..d67c3e067 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -130,6 +130,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool flush(int32_t sourceId, bool resetTime) override; + bool setSourcePosition(int32_t sourceId, int64_t position) override; + AddSegmentStatus addSegment(uint32_t needDataRequestId, const std::unique_ptr &mediaSegment) override; std::weak_ptr getClient() override; @@ -445,7 +447,19 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public * * @retval true on success. */ - virtual bool flushInternal(int32_t sourceId, bool resetTime); + bool flushInternal(int32_t sourceId, bool resetTime); + + /** + * @brief Set the source position in nanoseconds. + * + * This method sets the start position for a source. + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] position : The position in nanoseconds. + * + * @retval true on success. + */ + bool setSourcePositionInternal(int32_t sourceId, int64_t position); }; }; // namespace firebolt::rialto::server diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index 44572f0ac..aaf17755d 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -846,6 +846,52 @@ bool MediaPipelineServerInternal::flushInternal(int32_t sourceId, bool resetTime } m_gstPlayer->flush(sourceIter->first, resetTime); + + // Reset Eos on flush + auto it = m_isMediaTypeEosMap.find(sourceIter->first); + if (it != m_isMediaTypeEosMap.end() && it->second) + { + it->second = false; + } + + return true; +} + +bool MediaPipelineServerInternal::setSourcePosition(int32_t sourceId, int64_t position) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + bool result; + auto task = [&]() { result = setSourcePositionInternal(sourceId, position); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; +} + +bool MediaPipelineServerInternal::setSourcePositionInternal(int32_t sourceId, int64_t position) +{ + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed to set source position - Gstreamer player has not been loaded"); + return false; + } + auto sourceIter = std::find_if(m_attachedSources.begin(), m_attachedSources.end(), + [sourceId](const auto &src) { return src.second == sourceId; }); + if (sourceIter == m_attachedSources.end()) + { + RIALTO_SERVER_LOG_ERROR("Failed to set source position - Source not found"); + return false; + } + + m_gstPlayer->setSourcePosition(sourceIter->first, position); + + // Reset Eos on seek + auto it = m_isMediaTypeEosMap.find(sourceIter->first); + if (it != m_isMediaTypeEosMap.end() && it->second) + { + it->second = false; + } + return true; } diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index b4821a5f0..009cd2d61 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -64,6 +64,7 @@ class IMediaPipelineService virtual bool setMute(int sessionId, bool mute) = 0; virtual bool getMute(int sessionId, bool &mute) = 0; virtual bool flush(int sessionId, std::int32_t sourceId, bool resetTime) = 0; + virtual bool setSourcePosition(int sessionId, int32_t sourceId, int64_t position) = 0; virtual std::vector getSupportedMimeTypes(MediaSourceType type) = 0; virtual bool isMimeTypeSupported(const std::string &mimeType) = 0; virtual void ping(const std::shared_ptr &heartbeatProcedure) = 0; diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index ebc22c1db..b4d9d719a 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -366,6 +366,20 @@ bool MediaPipelineService::flush(int sessionId, std::int32_t sourceId, bool rese return mediaPipelineIter->second->flush(sourceId, resetTime); } +bool MediaPipelineService::setSourcePosition(int sessionId, int32_t sourceId, int64_t position) +{ + RIALTO_SERVER_LOG_DEBUG("Set Source Position requested, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exist", sessionId); + return false; + } + return mediaPipelineIter->second->setSourcePosition(sourceId, position); +} + std::vector MediaPipelineService::getSupportedMimeTypes(MediaSourceType type) { return m_mediaPipelineCapabilities->getSupportedMimeTypes(type); diff --git a/media/server/service/source/MediaPipelineService.h b/media/server/service/source/MediaPipelineService.h index af2237b57..d7e152d62 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -75,6 +75,7 @@ class MediaPipelineService : public IMediaPipelineService bool setMute(int sessionId, bool mute) override; bool getMute(int sessionId, bool &mute) override; bool flush(int sessionId, std::int32_t sourceId, bool resetTime) override; + bool setSourcePosition(int sessionId, int32_t sourceId, int64_t position) override; std::vector getSupportedMimeTypes(MediaSourceType type) override; bool isMimeTypeSupported(const std::string &mimeType) override; void ping(const std::shared_ptr &heartbeatProcedure) override; diff --git a/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index de18c6eec..e58f2ed53 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -429,6 +429,25 @@ message FlushRequest { message FlushResponse { } +/** + * @fn void setSourcePosition(int session_id, int source_id, int64 position) + * @brief Set the source position in nanoseconds. + * + * @param[in] session_id The id of the A/V session. + * @param[in] source_id The id of the media source. + * @param[in] position The position in nanoseconds. + * + * This method sets the start position for a source. + * + */ +message SetSourcePositionRequest { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; + optional int64 position = 3 [default = -1]; +} +message SetSourcePositionResponse { +} + /** * @brief Event sent the playback state has changed. * @@ -739,4 +758,11 @@ service MediaPipelineModule { */ rpc flush(FlushRequest) returns (FlushResponse) { } + + /** + * @brief This method sets the start position for a source. + * @see SetSourcePositionRequest + */ + rpc setSourcePosition(SetSourcePositionRequest) returns (SetSourcePositionResponse) { + } } diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index 107bb78b2..bea4c43d1 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -194,21 +194,17 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(GstStructure *, gstCapsGetStructure, (const GstCaps *caps, guint index), (const, override)); MOCK_METHOD(const gchar *, gstStructureGetName, (const GstStructure *structure), (const, override)); MOCK_METHOD(gboolean, gstObjectSetName, (GstObject * object, const gchar *name), (const, override)); - MOCK_METHOD(GstQuery *, gstQueryNewSegment, (GstFormat), (const, override)); - MOCK_METHOD(gboolean, gstElementQuery, (GstElement *, GstQuery *), (const, override)); - MOCK_METHOD(void, gstQueryParseSegment, - (GstQuery * query, gdouble *rate, GstFormat *format, gint64 *startValue, gint64 *stopValue), - (const, override)); - MOCK_METHOD(void, gstQueryUnref, (GstQuery * query), (const, override)); MOCK_METHOD(gboolean, gstSegmentDoSeek, (GstSegment *, gdouble, GstFormat, GstSeekFlags, GstSeekType, guint64, GstSeekType, guint64, gboolean *), (const, override)); - MOCK_METHOD(gboolean, gstBaseSrcNewSeamlessSegment, (GstBaseSrc * src, gint64 start, gint64 stop, gint64 time), - (const, override)); MOCK_METHOD(GstContext *, gstContextNew, (const gchar *context_type, gboolean persistent), (const, override)); MOCK_METHOD(GstStructure *, gstContextWritableStructure, (GstContext * context), (const, override)); MOCK_METHOD(void, gstElementSetContext, (GstElement * element, GstContext *context), (const, override)); MOCK_METHOD(void, gstContextUnref, (GstContext * context), (const, override)); + MOCK_METHOD(GstSample *, gstSampleNew, + (GstBuffer * buffer, GstCaps *caps, const GstSegment *segment, GstStructure *info), (const, override)); + MOCK_METHOD(void, gstSampleUnref, (GstSample * sample), (const, override)); + MOCK_METHOD(GstFlowReturn, gstAppSrcPushSample, (GstAppSrc * appsrc, GstSample *sample), (const, override)); GstCaps *gstCapsNewSimple(const char *media_type, const char *fieldname, ...) const override { diff --git a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h index 7098bccc5..1a0831936 100644 --- a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h +++ b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h @@ -204,6 +204,14 @@ MATCHER_P2(setPositionRequestMatcher, sessionId, position, "") return ((kRequest->session_id() == sessionId) && (kRequest->position() == position)); } +MATCHER_P3(setSourcePositionRequestMatcher, sessionId, sourceId, position, "") +{ + const ::firebolt::rialto::SetSourcePositionRequest *kRequest = + dynamic_cast(arg); + return ((kRequest->session_id() == sessionId) && (kRequest->position() == position) && + (kRequest->source_id() == sourceId)); +} + MATCHER_P2(setVolumeRequestMatcher, sessionId, volume, "") { const ::firebolt::rialto::SetVolumeRequest *kRequest = diff --git a/tests/componenttests/client/CMakeLists.txt b/tests/componenttests/client/CMakeLists.txt index 9927cb601..b579a27ea 100644 --- a/tests/componenttests/client/CMakeLists.txt +++ b/tests/componenttests/client/CMakeLists.txt @@ -59,6 +59,7 @@ add_gtests ( tests/mse/PlaybackErrorNotificationTest.cpp tests/mse/MediaPipelineCapabilitiesTest.cpp tests/mse/FlushTest.cpp + tests/mse/SetSourcePositionTest.cpp # Web Audio Component tests tests/webaudio/CreateWebAudioPlayerTest.cpp diff --git a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h index b27ec2bc8..fac1acd55 100644 --- a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h +++ b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h @@ -88,6 +88,10 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule MOCK_METHOD(void, flush, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::FlushRequest *request, ::firebolt::rialto::FlushResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, setSourcePosition, + (::google::protobuf::RpcController * controller, + const ::firebolt::rialto::SetSourcePositionRequest *request, + ::firebolt::rialto::SetSourcePositionResponse *response, ::google::protobuf::Closure *done)); void defaultReturn(::google::protobuf::RpcController *controller, ::google::protobuf::Closure *done) { diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index b7dc0a994..e84324a1d 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -85,6 +85,7 @@ const std::vector kAudioMimeType{"audio/mp4", "audio/aac", "audio/x const std::vector kVideoMimeType{"video/h264", "video/h265", "video/x-av1", "video/x-vp9", "video/mp4"}; const std::vector kUnknownMimeType{}; constexpr bool kResetTime{true}; +constexpr double kPosition{1234}; } // namespace namespace firebolt::rialto::client::ct @@ -1319,6 +1320,30 @@ void MediaPipelineTestMethods::flushFailure() EXPECT_FALSE(m_mediaPipeline->flush(kAudioSourceId, kResetTime)); } +void MediaPipelineTestMethods::shouldSetSourcePosition() +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + setSourcePosition(_, setSourcePositionRequestMatcher(kSessionId, kAudioSourceId, kPosition), _, _)) + .WillOnce(WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn))); +} + +void MediaPipelineTestMethods::setSourcePosition() +{ + EXPECT_TRUE(m_mediaPipeline->setSourcePosition(kAudioSourceId, kPosition)); +} + +void MediaPipelineTestMethods::shouldFailToSetSourcePosition() +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + setSourcePosition(_, setSourcePositionRequestMatcher(kSessionId, kAudioSourceId, kPosition), _, _)) + .WillOnce(WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::failureReturn))); +} + +void MediaPipelineTestMethods::setSourcePositionFailure() +{ + EXPECT_FALSE(m_mediaPipeline->setSourcePosition(kAudioSourceId, kPosition)); +} + /*************************** Private methods ********************************/ void MediaPipelineTestMethods::resetWriteLocation(uint32_t partitionId) diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index a1a460b9f..4c323dd8e 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -122,6 +122,8 @@ class MediaPipelineTestMethods void shouldGetPosition(const int64_t position); void shouldFlush(); void shouldFailToFlush(); + void shouldSetSourcePosition(); + void shouldFailToSetSourcePosition(); // MediaPipelineClient Expect methods void shouldNotifyNetworkStateBuffering(); @@ -225,6 +227,8 @@ class MediaPipelineTestMethods void isMimeTypeNotSupported(); void flush(); void flushFailure(); + void setSourcePosition(); + void setSourcePositionFailure(); // Event methods void sendNotifyNetworkStateBuffering(); diff --git a/tests/componenttests/client/tests/mse/SetSourcePositionTest.cpp b/tests/componenttests/client/tests/mse/SetSourcePositionTest.cpp new file mode 100644 index 000000000..7bb5304c5 --- /dev/null +++ b/tests/componenttests/client/tests/mse/SetSourcePositionTest.cpp @@ -0,0 +1,122 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ClientComponentTest.h" +#include + +namespace firebolt::rialto::client::ct +{ +class SetSourcePositionTest : public ClientComponentTest +{ +public: + SetSourcePositionTest() : ClientComponentTest() + { + ClientComponentTest::startApplicationRunning(); + MediaPipelineTestMethods::startAudioVideoMediaSessionPrerollPaused(); + } + + ~SetSourcePositionTest() + { + MediaPipelineTestMethods::endAudioVideoMediaSession(); + ClientComponentTest::stopApplication(); + } +}; + +/* + * Component Test: Set Source Position success + * Test Objective: + * Test that Set Source Position is successfully handled. + * + * Sequence Diagrams: + * Set Source Position - https://wiki.rdkcentral.com/display/ASP/Rialto+Flush+and+Seek+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Create memory region for the shared buffer. + * Create a server that handles Control IPC requests. + * Initalise the control state to running for this test application. + * Initalise a audio video media session paused and prerolled. + * + * Test Steps: + * Step 1: Set Source Position + * Server notifies the client that set source position procedure has been queued in server + * Expect that the procedure status is propagated to the client. + * + * Test Teardown: + * Terminate the media session. + * Memory region created for the shared buffer is closed. + * Server is terminated. + * + * Expected Results: + * Set Source Position is handled and forwarded to the server. + * + * Code: + */ +TEST_F(SetSourcePositionTest, setSourcePositionSuccess) +{ + // Step 1: Set Source Position + MediaPipelineTestMethods::shouldSetSourcePosition(); + MediaPipelineTestMethods::setSourcePosition(); +} + +/* + * Component Test: Set Source Position failures + * Test Objective: + * Check that failures returned directly from the Set Source Position api are handled correctly. + * + * Sequence Diagrams: + * Set Source Position - https://wiki.rdkcentral.com/display/ASP/Rialto+Flush+and+Seek+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Create memory region for the shared buffer. + * Create a server that handles Control IPC requests. + * Initalise the control state to running for this test application. + * Initalise a audio video media session paused and prerolled. + * + * Test Steps: + * Step 1: Set Source Position failure + * Server notifies the client that Set Source Position failed + * Expect that the procedure status is propagated to the client. + * + * Test Teardown: + * Terminate the media session. + * Memory region created for the shared buffer is closed. + * Server is terminated. + * + * Expected Results: + * FAILURE is notifed if set source position fails. + * + * Code: + */ +TEST_F(SetSourcePositionTest, failures) +{ + // Step 1: Set Source Position failure + MediaPipelineTestMethods::shouldFailToSetSourcePosition(); + MediaPipelineTestMethods::setSourcePositionFailure(); +} +} // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/server/common/ActionTraits.h b/tests/componenttests/server/common/ActionTraits.h index 86af4f8a4..f27fd7702 100644 --- a/tests/componenttests/server/common/ActionTraits.h +++ b/tests/componenttests/server/common/ActionTraits.h @@ -224,6 +224,14 @@ struct Flush static constexpr auto m_kFunction{&Stub::flush}; }; +struct SetSourcePosition +{ + using RequestType = ::firebolt::rialto::SetSourcePositionRequest; + using ResponseType = ::firebolt::rialto::SetSourcePositionResponse; + using Stub = ::firebolt::rialto::MediaPipelineModule_Stub; + static constexpr auto m_kFunction{&Stub::setSourcePosition}; +}; + // mediakeys module struct CreateMediaKeys { diff --git a/tests/componenttests/server/common/Constants.h b/tests/componenttests/server/common/Constants.h index 1ee292d7a..ee6a87347 100644 --- a/tests/componenttests/server/common/Constants.h +++ b/tests/componenttests/server/common/Constants.h @@ -43,6 +43,8 @@ constexpr double kPlaybackRate{0.5}; constexpr std::int64_t kCurrentPosition{1234}; constexpr double kVolume{0.7}; constexpr QosInfo kQosInfo{234, 567}; +constexpr std::uint64_t kPosition{1234}; +constexpr double kRate{1.0}; } // namespace firebolt::rialto::server::ct #endif // FIREBOLT_RIALTO_SERVER_CT_CONSTANTS_H_ diff --git a/tests/componenttests/server/common/MessageBuilders.cpp b/tests/componenttests/server/common/MessageBuilders.cpp index 5e6b20679..34d2949df 100644 --- a/tests/componenttests/server/common/MessageBuilders.cpp +++ b/tests/componenttests/server/common/MessageBuilders.cpp @@ -262,6 +262,16 @@ ::firebolt::rialto::FlushRequest createFlushRequest(int sessionId, int sourceId, return request; } +::firebolt::rialto::SetSourcePositionRequest createSetSourcePositionRequest(int sessionId, int sourceId, + std::int64_t position) +{ + ::firebolt::rialto::SetSourcePositionRequest request; + request.set_session_id(sessionId); + request.set_source_id(sourceId); + request.set_position(position); + return request; +} + ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine() { ::firebolt::rialto::CreateMediaKeysRequest request; diff --git a/tests/componenttests/server/common/MessageBuilders.h b/tests/componenttests/server/common/MessageBuilders.h index cdc1e3e5c..bf075367b 100644 --- a/tests/componenttests/server/common/MessageBuilders.h +++ b/tests/componenttests/server/common/MessageBuilders.h @@ -61,6 +61,8 @@ ::firebolt::rialto::SetMuteRequest createSetMuteRequest(int sessionId); ::firebolt::rialto::GetMuteRequest createGetMuteRequest(int sessionId); ::firebolt::rialto::SetVideoWindowRequest createSetVideoWindowRequest(int sessionId); ::firebolt::rialto::FlushRequest createFlushRequest(int sessionId, int sourceId, bool resetTime); +::firebolt::rialto::SetSourcePositionRequest createSetSourcePositionRequest(int sessionId, int sourceId, + std::int64_t position); // media keys module ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine(); diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp index e8ecc00c6..7584f33fb 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp @@ -151,6 +151,7 @@ void MediaPipelineTest::willSetupAndAddSource(GstAppSrc *appSrc) EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(appSrc), StrEq("format"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(appSrc), StrEq("stream-type"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(appSrc), StrEq("min-percent"))); + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(appSrc), StrEq("handle-segment-change"))); EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(appSrc, kMaxBytes)); EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetStreamType(appSrc, GST_APP_STREAM_TYPE_SEEKABLE)); EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(m_sourceName.data())).RetiresOnSaturation(); @@ -251,6 +252,73 @@ void MediaPipelineTest::willPushVideoData(const std::unique_ptr &segment, + GstBuffer &buffer, GstCaps &capsCopy) +{ + std::string dataCopy(segment->getData(), segment->getData() + segment->getDataLength()); + EXPECT_CALL(*m_gstWrapperMock, gstBufferNewAllocate(nullptr, segment->getDataLength(), nullptr)) + .InSequence(m_bufferAllocateSeq) + .WillOnce(Return(&buffer)) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstBufferFill(&buffer, 0, BufferMatcher(dataCopy), segment->getDataLength())) + .WillOnce(Return(segment->getDataLength())) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_audioAppSrc)) + .Times(2) + .WillRepeatedly(Return(&m_audioCaps)) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsCopy(&m_audioCaps)).WillOnce(Return(&capsCopy)).RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&capsCopy, StrEq("rate"), G_TYPE_INT, kSampleRate)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&capsCopy, StrEq("channels"), G_TYPE_INT, kNumOfChannels)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(&m_audioAppSrc, &capsCopy)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_audioCaps)).Times(2).RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&capsCopy)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&m_segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&m_segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&m_segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleNew(&buffer, &m_audioCaps, &m_segment, nullptr)).WillOnce(Return(m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushSample(&m_audioAppSrc, m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleUnref(m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&m_segment)); +} + +void MediaPipelineTest::willPushVideoSample(const std::unique_ptr &segment, + GstBuffer &buffer, GstCaps &capsCopy) +{ + std::string dataCopy(segment->getData(), segment->getData() + segment->getDataLength()); + EXPECT_CALL(*m_gstWrapperMock, gstBufferNewAllocate(nullptr, segment->getDataLength(), nullptr)) + .InSequence(m_bufferAllocateSeq) + .WillOnce(Return(&buffer)) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstBufferFill(&buffer, 0, BufferMatcher(dataCopy), segment->getDataLength())) + .WillOnce(Return(segment->getDataLength())) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&m_videoAppSrc)) + .Times(2) + .WillRepeatedly(Return(&m_videoCaps)) + .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsCopy(&m_videoCaps)).WillOnce(Return(&capsCopy)).RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&capsCopy, StrEq("width"), G_TYPE_INT, kWidth)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleIntStub(&capsCopy, StrEq("height"), G_TYPE_INT, kHeight)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsSetSimpleFractionStub(&capsCopy, StrEq("framerate"), GST_TYPE_FRACTION, _, _)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCaps(&m_videoAppSrc, &capsCopy)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&m_videoCaps)).Times(2).RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&capsCopy)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&m_segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&m_segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&m_segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleNew(&buffer, &m_videoCaps, &m_segment, nullptr)).WillOnce(Return(m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushSample(&m_videoAppSrc, m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleUnref(m_sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&m_segment)); +} + void MediaPipelineTest::willPause() { EXPECT_CALL(*m_gstWrapperMock, gstElementSetState(&m_pipeline, GST_STATE_PAUSED)) @@ -496,6 +564,70 @@ void MediaPipelineTest::pushVideoData(unsigned dataCountToPush, int needDataFram m_lastVideoNeedData = receivedNeedData; } +void MediaPipelineTest::pushAudioSample(int needDataFrameCount) +{ + // First, generate new data + std::unique_ptr segment{SegmentBuilder().basicAudioSegment(m_audioSourceId)()}; + GstBuffer buffer{}; + GstCaps capsCopy{}; + + // Next, create frame writer + ASSERT_TRUE(m_lastAudioNeedData); + std::shared_ptr shmInfo{ + std::make_shared(MediaPlayerShmInfo{m_lastAudioNeedData->shm_info().max_metadata_bytes(), + m_lastAudioNeedData->shm_info().metadata_offset(), + m_lastAudioNeedData->shm_info().media_data_offset(), + m_lastAudioNeedData->shm_info().max_media_bytes()})}; + auto writer{common::IMediaFrameWriterFactory::getFactory()->createFrameWriter(m_shmHandle.getShm(), shmInfo)}; + + // Write frames to shm and add gst expects + EXPECT_EQ(writer->writeFrame(segment), AddSegmentStatus::OK); + willPushAudioSample(segment, buffer, capsCopy); + + // Finally, send HaveData and receive new NeedData + ExpectMessage expectedNeedData{m_clientStub}; + auto haveDataReq{createHaveDataRequest(m_sessionId, writer->getNumFrames(), m_lastAudioNeedData->request_id())}; + ConfigureAction(m_clientStub).send(haveDataReq).expectSuccess(); + 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(), needDataFrameCount); + m_lastAudioNeedData = receivedNeedData; +} + +void MediaPipelineTest::pushVideoSample(int needDataFrameCount) +{ + // First, generate new data + std::unique_ptr segment{SegmentBuilder().basicVideoSegment(m_videoSourceId)()}; + GstBuffer buffer{}; + GstCaps capsCopy{}; + + // Next, create frame writer + ASSERT_TRUE(m_lastVideoNeedData); + std::shared_ptr shmInfo{ + std::make_shared(MediaPlayerShmInfo{m_lastVideoNeedData->shm_info().max_metadata_bytes(), + m_lastVideoNeedData->shm_info().metadata_offset(), + m_lastVideoNeedData->shm_info().media_data_offset(), + m_lastVideoNeedData->shm_info().max_media_bytes()})}; + auto writer{common::IMediaFrameWriterFactory::getFactory()->createFrameWriter(m_shmHandle.getShm(), shmInfo)}; + + // Write frames to shm and add gst expects + EXPECT_EQ(writer->writeFrame(segment), AddSegmentStatus::OK); + willPushVideoSample(segment, buffer, capsCopy); + + // Finally, send HaveData and receive new NeedData + ExpectMessage expectedNeedData{m_clientStub}; + auto haveDataReq{createHaveDataRequest(m_sessionId, writer->getNumFrames(), m_lastVideoNeedData->request_id())}; + ConfigureAction(m_clientStub).send(haveDataReq).expectSuccess(); + auto receivedNeedData{expectedNeedData.getMessage()}; + ASSERT_TRUE(receivedNeedData); + EXPECT_EQ(receivedNeedData->session_id(), m_sessionId); + EXPECT_EQ(receivedNeedData->source_id(), m_videoSourceId); + EXPECT_EQ(receivedNeedData->frame_count(), needDataFrameCount); + m_lastVideoNeedData = receivedNeedData; +} + void MediaPipelineTest::eosAudio(unsigned dataCountToPush) { // First, generate new data diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.h b/tests/componenttests/server/fixtures/MediaPipelineTest.h index 8682a0c56..a8a6e3896 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.h +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.h @@ -48,6 +48,10 @@ class MediaPipelineTest : public RialtoServerComponentTest GstCaps &capsCopy, bool shouldNotify); void willPushVideoData(const std::unique_ptr &segment, GstBuffer &buffer, GstCaps &capsCopy, bool shouldNotify); + void willPushAudioSample(const std::unique_ptr &segment, GstBuffer &buffer, + GstCaps &capsCopy); + void willPushVideoSample(const std::unique_ptr &segment, GstBuffer &buffer, + GstCaps &capsCopy); void willPause(); void willNotifyPaused(); void willPlay(); @@ -67,6 +71,8 @@ class MediaPipelineTest : public RialtoServerComponentTest void gstNeedData(GstAppSrc *appSrc, int frameCount); void pushAudioData(unsigned dataCountToPush, int needDataFrameCount); void pushVideoData(unsigned dataCountToPush, int needDataFrameCount); + void pushAudioSample(int needDataFrameCount); + void pushVideoSample(int needDataFrameCount); void play(); void eosAudio(unsigned dataCountToPush); void eosVideo(unsigned dataCountToPush); @@ -107,6 +113,8 @@ class MediaPipelineTest : public RialtoServerComponentTest GstPad m_ghostPad{}; GstEvent m_flushStartEvent{}; GstEvent m_flushStopEvent{}; + GstSegment m_segment{}; + GstSample *m_sample{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastAudioNeedData{nullptr}; std::shared_ptr<::firebolt::rialto::NeedMediaDataEvent> m_lastVideoNeedData{nullptr}; diff --git a/tests/componenttests/server/tests/CMakeLists.txt b/tests/componenttests/server/tests/CMakeLists.txt index 518f8d09f..6e2a0da73 100644 --- a/tests/componenttests/server/tests/CMakeLists.txt +++ b/tests/componenttests/server/tests/CMakeLists.txt @@ -52,6 +52,7 @@ add_gtests ( mediaPipeline/RemoveAudioPlaybackTest.cpp mediaPipeline/NonFatalPlayerErrorUpdatesTest.cpp mediaPipeline/FlushTest.cpp + mediaPipeline/SetSourcePositionTest.cpp mediaKeys/MediaKeysTestMethods.cpp mediaKeys/MediaKeysTest.cpp mediaKeys/SessionReadyForDecryptionTest.cpp diff --git a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp index 2a7c6ca84..d6b246e6a 100644 --- a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp @@ -160,6 +160,8 @@ class DualVideoPlaybackTest : public MediaPipelineTest EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(&m_secondaryVideoAppSrc), StrEq("format"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(&m_secondaryVideoAppSrc), StrEq("stream-type"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(GST_ELEMENT(&m_secondaryVideoAppSrc), StrEq("min-percent"))); + EXPECT_CALL(*m_glibWrapperMock, + gObjectSetStub(GST_ELEMENT(&m_secondaryVideoAppSrc), StrEq("handle-segment-change"))); EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(&m_secondaryVideoAppSrc, (8 * 1024 * 1024))); EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetStreamType(&m_secondaryVideoAppSrc, GST_APP_STREAM_TYPE_SEEKABLE)); EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(m_sourceName.data())).RetiresOnSaturation(); diff --git a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp index 7176fc8a4..e20580053 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FlushTest.cpp @@ -19,6 +19,7 @@ #include "ActionTraits.h" #include "ConfigureAction.h" +#include "Constants.h" #include "ExpectMessage.h" #include "Matchers.h" #include "MediaPipelineTest.h" @@ -39,6 +40,7 @@ class FlushTest : public MediaPipelineTest { GstEvent m_flushStartEvent{}; GstEvent m_flushStopEvent{}; + GstSegment m_segment{}; public: FlushTest() = default; @@ -56,11 +58,6 @@ class FlushTest : public MediaPipelineTest void flush() { - // After successful Flush, NeedData for flushed source is sent. - ExpectMessage expectedAudioNeedData{m_clientStub}; - expectedAudioNeedData.setFilter([&](const NeedMediaDataEvent &event) - { return event.source_id() == m_audioSourceId; }); - // After successful Flush procedure, SourceFlushedEvent is sent. ExpectMessage expectedSourceFlushed{m_clientStub}; expectedSourceFlushed.setFilter([&](const SourceFlushedEvent &event) @@ -75,6 +72,18 @@ class FlushTest : public MediaPipelineTest ASSERT_TRUE(receivedSourceFlushed); EXPECT_EQ(receivedSourceFlushed->session_id(), m_sessionId); EXPECT_EQ(receivedSourceFlushed->source_id(), m_audioSourceId); + } + + void setSourcePosition() + { + // After successful SetSourcePosition, NeedData for source is sent. + ExpectMessage expectedAudioNeedData{m_clientStub}; + expectedAudioNeedData.setFilter([&](const NeedMediaDataEvent &event) + { return event.source_id() == m_audioSourceId; }); + + // Send SetSourcePositionRequest and expect success + auto request{createSetSourcePositionRequest(m_sessionId, m_audioSourceId, kPosition)}; + ConfigureAction(m_clientStub).send(request).expectSuccess(); // Check received NeedDataReqs auto receivedAudioNeedData{expectedAudioNeedData.getMessage()}; @@ -158,30 +167,36 @@ class FlushTest : public MediaPipelineTest * Expect that FlushResponse has success status * Server should notify the client that flush has been finished. * - * Step 9: End of audio stream + * Step 9: Set Source Position + * Trigger set source position procedure + * Expect that SetSourcePositionResponse has success status + * Expect, that NeedData event is sent by Rialto Server + * Expect that after HaveData, new audio sample is pushed + * + * Step 10: End of audio stream * Send audio haveData with one frame and EOS status * Expect that Gstreamer is notified about end of stream * - * Step 10: End of video stream + * Step 11: End of video stream * Send video haveData with one frame and EOS status * Expect that Gstreamer is notified about end of stream * - * Step 11: Notify end of stream + * Step 12: Notify end of stream * Simulate, that gst_message_eos is received by Rialto Server * Expect that server notifies the client that the Network state has changed to END_OF_STREAM. * - * Step 12: Remove sources + * Step 13: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 13: Stop + * Step 14: 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 14: Destroy media session + * Step 15: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -242,26 +257,30 @@ TEST_F(FlushTest, flushAudioSourceSuccess) willFlush(); flush(); - // Step 9: End of audio stream - // Step 10: End of video stream + // Step 9: Set Source Position + setSourcePosition(); + pushAudioSample(kFrameCount); + + // Step 10: End of audio stream + // Step 11: End of video stream willEos(&m_audioAppSrc); eosAudio(kFramesToPush); willEos(&m_videoAppSrc); eosVideo(kFramesToPush); - // Step 11: Notify end of stream + // Step 12: Notify end of stream gstNotifyEos(); - // Step 12: Remove sources + // Step 13: Remove sources willRemoveAudioSource(); removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 13: Stop + // Step 14: Stop willStop(); stop(); - // Step 14: Destroy media session + // Step 15: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp new file mode 100644 index 000000000..69a185d82 --- /dev/null +++ b/tests/componenttests/server/tests/mediaPipeline/SetSourcePositionTest.cpp @@ -0,0 +1,314 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 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 "ActionTraits.h" +#include "ConfigureAction.h" +#include "Constants.h" +#include "ExpectMessage.h" +#include "Matchers.h" +#include "MediaPipelineTest.h" +#include "MessageBuilders.h" + +namespace +{ +constexpr unsigned kFramesToPush{1}; +constexpr int kFrameCount{3}; +} // namespace + +using testing::Return; + +namespace firebolt::rialto::server::ct +{ +class SetSourcePositionTest : public MediaPipelineTest +{ +public: + SetSourcePositionTest() = default; + ~SetSourcePositionTest() override = default; + + void setSourcePosition(int sourceId) + { + // Send SetSourcePositionRequest and expect success + auto request{createSetSourcePositionRequest(m_sessionId, sourceId, kPosition)}; + ConfigureAction(m_clientStub).send(request).expectSuccess(); + } + + void setSourcePositionFailure() + { + auto request{createSetSourcePositionRequest(m_sessionId, m_audioSourceId, kPosition)}; + ConfigureAction(m_clientStub).send(request).expectFailure(); + } +}; + +/* + * Component Test: Set Source Position success + * Test Objective: + * Test that Set Source Position is successfully handled. + * + * Sequence Diagrams: + * Set Source Position - https://wiki.rdkcentral.com/display/ASP/Rialto+Flush+and+Seek+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Set Rialto Server to Active + * Connect Rialto Client Stub + * Map Shared Memory + * + * Test Steps: + * Step 1: Create a new media session + * Send CreateSessionRequest to Rialto Server + * Expect that successful CreateSessionResponse is received + * Save returned session id + * + * Step 2: Load content + * Send LoadRequest to Rialto Server + * Expect that successful LoadResponse is received + * Expect that GstPlayer instance is created. + * Expect that client is notified that the NetworkState has changed to BUFFERING. + * + * Step 3: Attach all sources + * Attach the audio source. + * Expect that audio source is attached. + * Attach the video source. + * Expect that video source is attached. + * Expect that rialto source is setup + * Expect that all sources are attached. + * Expect that the Playback state has changed to IDLE. + * + * Step 4: Pause + * Pause the content. + * Expect that gstreamer pipeline is paused. + * + * Step 5: Write 1 audio frame + * Gstreamer Stub notifies, that it needs audio data + * Expect that server notifies the client that it needs 3 frames of audio data. + * Write 1 frame of audio data to the shared buffer. + * Send HaveData message + * Expect that server notifies the client that it needs 3 frames of audio data. + * + * Step 6: Write 1 video frame + * Gstreamer Stub notifies, that it needs video data + * Expect that server notifies the client that it needs 3 frames of video data. + * Write 1 frame of video data to the shared buffer. + * Send HaveData message + * Expect that server notifies the client that it needs 3 frames of video data. + * + * Step 7: Notify buffered and Paused + * Expect that server notifies the client that the Network state has changed to BUFFERED. + * Gstreamer Stub notifies, that pipeline state is in PAUSED state + * Expect that server notifies the client that the Network state has changed to PAUSED. + * + * Step 8: Set Audio Source Position + * Trigger set source position procedure for audio source + * Expect that SetSourcePositionResponse has success status + * Expect that after HaveData, new audio sample is pushed + * + * Step 9: Set Video Source Position + * Trigger set source position procedure for video source + * Expect that SetSourcePositionResponse has success status + * Expect that after HaveData, new video sample is pushed + * + * Step 10: End of audio stream + * Send audio haveData with one frame and EOS status + * Expect that Gstreamer is notified about end of stream + * + * Step 11: End of video stream + * Send video haveData with one frame and EOS status + * Expect that Gstreamer is notified about end of stream + * + * Step 12: Notify end of stream + * Simulate, that gst_message_eos is received by Rialto Server + * Expect that server notifies the client that the Network state has changed to END_OF_STREAM. + * + * Step 13: Remove sources + * Remove the audio source. + * Expect that audio source is removed. + * Remove the video source. + * Expect that video source is removed. + * + * Step 14: 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 15: Destroy media session + * Send DestroySessionRequest. + * Expect that the session is destroyed on the server. + * + * Test Teardown: + * Memory region created for the shared buffer is unmapped. + * Server is terminated. + * + * Expected Results: + * Set Source Position is handled and forwarded to the gstreamer. + * + * Code: + */ +TEST_F(SetSourcePositionTest, setSourcePositionSuccess) +{ + // Step 1: Create a new media session + createSession(); + + // Step 2: Load content + gstPlayerWillBeCreated(); + load(); + + // Step 3: Attach all sources + audioSourceWillBeAttached(); + attachAudioSource(); + videoSourceWillBeAttached(); + attachVideoSource(); + sourceWillBeSetup(); + setupSource(); + willSetupAndAddSource(&m_audioAppSrc); + willSetupAndAddSource(&m_videoAppSrc); + willFinishSetupAndAddSource(); + indicateAllSourcesAttached(); + + // Step 4: Pause + willPause(); + pause(); + + // Step 5: Write 1 audio frame + // Step 6: Write 1 video frame + // Step 7: Notify buffered and Paused + gstNeedData(&m_audioAppSrc, kFrameCount); + gstNeedData(&m_videoAppSrc, kFrameCount); + { + ExpectMessage expectedNetworkStateChange{m_clientStub}; + + pushAudioData(kFramesToPush, kFrameCount); + pushVideoData(kFramesToPush, kFrameCount); + + auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; + ASSERT_TRUE(receivedNetworkStateChange); + EXPECT_EQ(receivedNetworkStateChange->session_id(), m_sessionId); + EXPECT_EQ(receivedNetworkStateChange->state(), ::firebolt::rialto::NetworkStateChangeEvent_NetworkState_BUFFERED); + } + willNotifyPaused(); + notifyPaused(); + + // Step 8: Set Audio Source Position + setSourcePosition(m_audioSourceId); + pushAudioSample(kFrameCount); + + // Step 9: Set Video Source Position + setSourcePosition(m_videoSourceId); + pushVideoSample(kFrameCount); + + // Step 10: End of audio stream + // Step 11: End of video stream + willEos(&m_audioAppSrc); + eosAudio(kFramesToPush); + willEos(&m_videoAppSrc); + eosVideo(kFramesToPush); + + // Step 12: Notify end of stream + gstNotifyEos(); + + // Step 13: Remove sources + willRemoveAudioSource(); + removeSource(m_audioSourceId); + removeSource(m_videoSourceId); + + // Step 14: Stop + willStop(); + stop(); + + // Step 15: Destroy media session + gstPlayerWillBeDestructed(); + destroySession(); +} + +/* + * Component Test: Set Source Position failure + * Test Objective: + * Test that Set Source Position failure is handled. + * + * Sequence Diagrams: + * Set Source Position - https://wiki.rdkcentral.com/display/ASP/Rialto+Flush+and+Seek+Design + * + * Test Setup: + * Language: C++ + * Testing Framework: Google Test + * Components: MediaPipeline + * + * Test Initialize: + * Set Rialto Server to Active + * Connect Rialto Client Stub + * Map Shared Memory + * + * Test Steps: + * Step 1: Create a new media session + * Send CreateSessionRequest to Rialto Server + * Expect that successful CreateSessionResponse is received + * Save returned session id + * + * Step 2: Load content + * Send LoadRequest to Rialto Server + * Expect that successful LoadResponse is received + * Expect that GstPlayer instance is created. + * Expect that client is notified that the NetworkState has changed to BUFFERING. + * + * Step 3: Set Source Position Failure + * SetSourcePositionRequest sent for unknown source + * Expect that SetSourcePositionResponse has error status + * + * Step 4: 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 5: Destroy media session + * Send DestroySessionRequest. + * Expect that the session is destroyed on the server. + * + * Test Teardown: + * Memory region created for the shared buffer is unmapped. + * Server is terminated. + * + * Expected Results: + * Set Source Position failure status is forwarded to client + * + * Code: + */ +TEST_F(SetSourcePositionTest, SetSourcePositionFailure) +{ + // Step 1: Create a new media session + createSession(); + + // Step 2: Load content + gstPlayerWillBeCreated(); + load(); + + // Step 3: Set Source Position Failure + setSourcePositionFailure(); + + // Step 4: Stop + willStop(); + stop(); + + // Step 5: Destroy media session + gstPlayerWillBeDestructed(); + destroySession(); +} +} // namespace firebolt::rialto::server::ct diff --git a/tests/unittests/media/client/ipc/CMakeLists.txt b/tests/unittests/media/client/ipc/CMakeLists.txt index c6c9e2fc0..a01e71575 100644 --- a/tests/unittests/media/client/ipc/CMakeLists.txt +++ b/tests/unittests/media/client/ipc/CMakeLists.txt @@ -41,6 +41,7 @@ add_gtests ( mediaPipelineIpc/GetMuteTest.cpp mediaPipelineIpc/SetMuteTest.cpp mediaPipelineIpc/FlushTest.cpp + mediaPipelineIpc/SetSourcePositionTest.cpp # MediaPipelineCapabilitiesIpc tests mediaPipelineCapabilitiesIpc/MediaPipelineCapabilitiesIpcTest.cpp diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/SetSourcePositionTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetSourcePositionTest.cpp new file mode 100644 index 000000000..30400753b --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetSourcePositionTest.cpp @@ -0,0 +1,97 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcSetSourcePositionTest : public MediaPipelineIpcTestBase +{ +protected: + const int64_t m_kPosition = 123; + const int32_t m_kSourceId{1}; + + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } +}; + +/** + * Test that setSourcePosition can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcSetSourcePositionTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setSourcePosition"), m_controllerMock.get(), + setSourcePositionRequestMatcher(m_sessionId, m_kSourceId, m_kPosition), _, + m_blockingClosureMock.get())); + + EXPECT_EQ(m_mediaPipelineIpc->setSourcePosition(m_kSourceId, m_kPosition), true); +} + +/** + * Test that setSourcePosition fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetSourcePositionTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + EXPECT_EQ(m_mediaPipelineIpc->setSourcePosition(m_kSourceId, m_kPosition), false); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that setSourcePosition fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetSourcePositionTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setSourcePosition"), _, _, _, _)); + + EXPECT_EQ(m_mediaPipelineIpc->setSourcePosition(m_kSourceId, m_kPosition), true); +} + +/** + * Test that setSourcePosition fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcSetSourcePositionTest, SetSourcePositionFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setSourcePosition"), _, _, _, _)); + + EXPECT_EQ(m_mediaPipelineIpc->setSourcePosition(m_kSourceId, m_kPosition), false); +} diff --git a/tests/unittests/media/client/main/CMakeLists.txt b/tests/unittests/media/client/main/CMakeLists.txt index 560d7b2c2..5ec96e8a9 100644 --- a/tests/unittests/media/client/main/CMakeLists.txt +++ b/tests/unittests/media/client/main/CMakeLists.txt @@ -38,6 +38,7 @@ add_gtests ( mediaPipeline/SetMuteTest.cpp mediaPipeline/GetMuteTest.cpp mediaPipeline/FlushTest.cpp + mediaPipeline/SetSourcePositionTest.cpp # MediaPipelineCapabilities tests mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp diff --git a/tests/unittests/media/client/main/mediaPipeline/SetSourcePositionTest.cpp b/tests/unittests/media/client/main/mediaPipeline/SetSourcePositionTest.cpp new file mode 100644 index 000000000..21dde802d --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/SetSourcePositionTest.cpp @@ -0,0 +1,61 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineSetSourcePositionTest : public MediaPipelineTestBase +{ +protected: + const int32_t m_kSourceId{1}; + const int64_t m_kPosition{1234}; + + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that setSourcePosition returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineSetSourcePositionTest, Success) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setSourcePosition(m_kSourceId, m_kPosition)).WillOnce(Return(true)); + + EXPECT_EQ(m_mediaPipeline->setSourcePosition(m_kSourceId, m_kPosition), true); +} + +/** + * Test that setSourcePosition returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineSetSourcePositionTest, Failure) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setSourcePosition(m_kSourceId, m_kPosition)).WillOnce(Return(false)); + + EXPECT_EQ(m_mediaPipeline->setSourcePosition(m_kSourceId, m_kPosition), false); +} diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index 76a051cf3..f046b88be 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -52,6 +52,7 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, setMute, (bool mute), (override)); MOCK_METHOD(bool, getMute, (bool &mute), (override)); MOCK_METHOD(bool, flush, (int32_t sourceId, bool resetTime), (override)); + MOCK_METHOD(bool, setSourcePosition, (int32_t sourceId, int64_t position), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index 8ed651daa..be9a61d7e 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -46,6 +46,7 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/CheckAudioUnderflowTest.cpp genericPlayer/tasksTests/SetPlaybackRateTest.cpp genericPlayer/tasksTests/SetPositionTest.cpp + genericPlayer/tasksTests/SetSourcePositionTest.cpp genericPlayer/tasksTests/SetupElementTest.cpp genericPlayer/tasksTests/SetupSourceTest.cpp genericPlayer/tasksTests/SetVideoGeometryTest.cpp diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 9d7aa7f5c..2ca14a57e 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -544,6 +544,102 @@ TEST_F(GstGenericPlayerPrivateTest, shouldAttachAudioData) m_sut->attachAudioData(); } +TEST_F(GstGenericPlayerPrivateTest, shouldAttachAudioSample) +{ + constexpr std::int64_t kPosition{124}; + constexpr double kRate{1.0}; + GstBuffer buffer{}; + GstAppSrc audioSrc{}; + GstSegment segment{}; + GstSample *sample{nullptr}; + GstCaps caps{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioBuffers.emplace_back(&buffer); + context.audioNeedData = true; + context.playbackRate = kRate; + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.initialPositions[GST_ELEMENT(&audioSrc)] = kPosition; + }); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&audioSrc)).WillOnce(Return(&caps)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleNew(&buffer, &caps, &segment, nullptr)).WillOnce(Return(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushSample(&audioSrc, sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleUnref(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&caps)); + m_sut->attachAudioData(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldAttachAudioDataWhenAttachingSampleFails) +{ + constexpr std::int64_t kPosition{124}; + constexpr double kRate{1.0}; + GstBuffer buffer{}; + GstAppSrc audioSrc{}; + GstSegment segment{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioBuffers.emplace_back(&buffer); + context.audioNeedData = true; + context.playbackRate = kRate; + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.initialPositions[GST_ELEMENT(&audioSrc)] = kPosition; + }); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(false)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushBuffer(_, &buffer)); + m_sut->attachAudioData(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldAttachAudioSampleWithAdditionalData) +{ + constexpr std::int64_t kPosition{124}; + constexpr double kRate{1.0}; + GstBuffer buffer1{}; + GstBuffer buffer2{}; + GstAppSrc audioSrc{}; + GstSegment segment{}; + GstSample *sample{nullptr}; + GstCaps caps{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioBuffers.emplace_back(&buffer1); + context.audioBuffers.emplace_back(&buffer2); + context.audioNeedData = true; + context.playbackRate = kRate; + context.streamInfo[firebolt::rialto::MediaSourceType::AUDIO].appSrc = GST_ELEMENT(&audioSrc); + context.initialPositions[GST_ELEMENT(&audioSrc)] = kPosition; + }); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&audioSrc)).WillOnce(Return(&caps)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleNew(&buffer1, &caps, &segment, nullptr)).WillOnce(Return(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushSample(&audioSrc, sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleUnref(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&caps)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushBuffer(_, &buffer2)); + m_sut->attachAudioData(); +} + TEST_F(GstGenericPlayerPrivateTest, shouldCancelAudioUnderflowAndResume) { GstBuffer buffer{}; @@ -606,6 +702,42 @@ TEST_F(GstGenericPlayerPrivateTest, shouldAttachVideoData) m_sut->attachVideoData(); } +TEST_F(GstGenericPlayerPrivateTest, shouldAttachVideoSampleWithAdditionalData) +{ + constexpr std::int64_t kPosition{124}; + constexpr double kRate{1.0}; + GstBuffer buffer1{}; + GstBuffer buffer2{}; + GstAppSrc videoSrc{}; + GstSegment segment{}; + GstSample *sample{nullptr}; + GstCaps caps{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.videoBuffers.emplace_back(&buffer1); + context.videoBuffers.emplace_back(&buffer2); + context.videoNeedData = true; + context.playbackRate = kRate; + context.streamInfo[firebolt::rialto::MediaSourceType::VIDEO].appSrc = GST_ELEMENT(&videoSrc); + context.initialPositions[GST_ELEMENT(&videoSrc)] = kPosition; + }); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcGetCaps(&videoSrc)).WillOnce(Return(&caps)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentNew()).WillOnce(Return(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentInit(&segment, GST_FORMAT_TIME)); + EXPECT_CALL(*m_gstWrapperMock, + gstSegmentDoSeek(&segment, kRate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, kPosition, + GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE, nullptr)) + .WillOnce(Return(true)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleNew(&buffer1, &caps, &segment, nullptr)).WillOnce(Return(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushSample(&videoSrc, sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSampleUnref(sample)); + EXPECT_CALL(*m_gstWrapperMock, gstSegmentFree(&segment)); + EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&caps)); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcPushBuffer(_, &buffer2)); + m_sut->attachVideoData(); +} + TEST_F(GstGenericPlayerPrivateTest, shouldCancelVideoUnderflowAndResume) { GstBuffer buffer{}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index a55fb0372..a4b09a556 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -320,8 +320,19 @@ TEST_F(GstGenericPlayerTest, shouldFlush) constexpr bool kResetTime{true}; std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFlush(_, _, MediaSourceType::AUDIO, kResetTime)) + EXPECT_CALL(m_taskFactoryMock, createFlush(_, MediaSourceType::AUDIO, kResetTime)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->flush(MediaSourceType::AUDIO, kResetTime); } + +TEST_F(GstGenericPlayerTest, shouldSetSourcePosition) +{ + constexpr int64_t kPosition{1234}; + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createSetSourcePosition(_, MediaSourceType::AUDIO, kPosition)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->setSourcePosition(MediaSourceType::AUDIO, kPosition); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index 32581f023..8a07d11a5 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -42,6 +42,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetVideoGeometry.h" #include "tasks/generic/SetVolume.h" #include "tasks/generic/SetupElement.h" @@ -246,6 +247,11 @@ void GenericTasksTestsBase::setContextNeedDataAudioOnly() testContext->m_context.audioNeedData = true; } +void GenericTasksTestsBase::setContextSetupSourceFinished() +{ + testContext->m_context.setupSourceFinished = true; +} + void GenericTasksTestsBase::expectSetupVideoElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); @@ -1776,6 +1782,16 @@ void GenericTasksTestsBase::checkSourcesAttached() EXPECT_TRUE(testContext->m_context.wereAllSourcesAttached); } +void GenericTasksTestsBase::checkSetupSourceFinished() +{ + EXPECT_TRUE(testContext->m_context.setupSourceFinished); +} + +void GenericTasksTestsBase::checkSetupSourceUnfinished() +{ + EXPECT_FALSE(testContext->m_context.setupSourceFinished); +} + void GenericTasksTestsBase::triggerNeedDataAudio() { firebolt::rialto::server::tasks::generic::NeedData task{testContext->m_context, &testContext->m_gstPlayerClient, @@ -2119,74 +2135,22 @@ void GenericTasksTestsBase::triggerReadShmDataAndAttachSamplesVideo() void GenericTasksTestsBase::shouldFlushAudio() { - EXPECT_CALL(*testContext->m_gstWrapper, gstElementQueryPosition(_, GST_FORMAT_TIME, _)) - .WillOnce(Invoke( - [this](GstElement *element, GstFormat format, gint64 *cur) - { - *cur = kPosition; - return TRUE; - })); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryNewSegment(GST_FORMAT_TIME)).WillOnce(Return(&testContext->m_query)); - EXPECT_CALL(*testContext->m_gstWrapper, gstElementQuery(&testContext->m_pipeline, &testContext->m_query)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryParseSegment(&testContext->m_query, _, _, _, _)); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryUnref(&testContext->m_query)); EXPECT_CALL(*testContext->m_gstWrapper, gstBufferUnref(&testContext->m_audioBuffer)); EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); EXPECT_CALL(testContext->m_gstPlayerClient, notifySourceFlushed(firebolt::rialto::MediaSourceType::AUDIO)); - EXPECT_CALL(testContext->m_gstPlayerClient, notifyNeedMediaData(firebolt::rialto::MediaSourceType::AUDIO)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentNew()).WillOnce(Return(&testContext->m_segment)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentInit(&testContext->m_segment, GST_FORMAT_TIME)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstSegmentDoSeek(&testContext->m_segment, _, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, - kPosition, GST_SEEK_TYPE_SET, _, nullptr)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstBaseSrcNewSeamlessSegment(GST_BASE_SRC(&testContext->m_appSrcAudio), _, _, _)) - .WillOnce(Return(false)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentFree(&testContext->m_segment)); } void GenericTasksTestsBase::shouldFlushVideo() { - EXPECT_CALL(*testContext->m_gstWrapper, gstElementQueryPosition(_, GST_FORMAT_TIME, _)) - .WillOnce(Invoke( - [this](GstElement *element, GstFormat format, gint64 *cur) - { - *cur = kPosition; - return TRUE; - })); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryNewSegment(GST_FORMAT_TIME)).WillOnce(Return(&testContext->m_query)); - EXPECT_CALL(*testContext->m_gstWrapper, gstElementQuery(&testContext->m_pipeline, &testContext->m_query)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryParseSegment(&testContext->m_query, _, _, _, _)); - EXPECT_CALL(*testContext->m_gstWrapper, gstQueryUnref(&testContext->m_query)); EXPECT_CALL(*testContext->m_gstWrapper, gstBufferUnref(&testContext->m_videoBuffer)); EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::VIDEO)); EXPECT_CALL(testContext->m_gstPlayerClient, notifySourceFlushed(firebolt::rialto::MediaSourceType::VIDEO)); - EXPECT_CALL(testContext->m_gstPlayerClient, notifyNeedMediaData(firebolt::rialto::MediaSourceType::VIDEO)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentNew()).WillOnce(Return(&testContext->m_segment)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentInit(&testContext->m_segment, GST_FORMAT_TIME)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstSegmentDoSeek(&testContext->m_segment, _, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, - kPosition, GST_SEEK_TYPE_SET, _, nullptr)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstBaseSrcNewSeamlessSegment(GST_BASE_SRC(&testContext->m_appSrcVideo), _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*testContext->m_gstWrapper, gstSegmentFree(&testContext->m_segment)); } void GenericTasksTestsBase::triggerFlush(firebolt::rialto::MediaSourceType sourceType) { - firebolt::rialto::server::tasks::generic::Flush task{testContext->m_context, - testContext->m_gstPlayer, - &testContext->m_gstPlayerClient, - testContext->m_gstWrapper, - sourceType, - kResetTime}; + firebolt::rialto::server::tasks::generic::Flush task{testContext->m_context, &testContext->m_gstPlayerClient, + testContext->m_gstWrapper, sourceType, kResetTime}; task.execute(); } @@ -2194,20 +2158,30 @@ void GenericTasksTestsBase::checkAudioFlushed() { EXPECT_EQ(testContext->m_context.audioBuffers.size(), 0); EXPECT_EQ(testContext->m_context.videoBuffers.size(), 1); - EXPECT_TRUE(testContext->m_context.audioNeedData); - EXPECT_TRUE(testContext->m_context.audioNeedDataPending); - EXPECT_FALSE(testContext->m_context.videoNeedData); - EXPECT_FALSE(testContext->m_context.videoNeedDataPending); + EXPECT_FALSE(testContext->m_context.audioNeedData); + EXPECT_FALSE(testContext->m_context.audioNeedDataPending); + EXPECT_TRUE(testContext->m_context.videoNeedData); + EXPECT_TRUE(testContext->m_context.videoNeedDataPending); + EXPECT_FALSE(testContext->m_context.eosNotified); + EXPECT_EQ(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::AUDIO), + testContext->m_context.endOfStreamInfo.end()); + EXPECT_NE(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::VIDEO), + testContext->m_context.endOfStreamInfo.end()); } void GenericTasksTestsBase::checkVideoFlushed() { EXPECT_EQ(testContext->m_context.audioBuffers.size(), 1); EXPECT_EQ(testContext->m_context.videoBuffers.size(), 0); - EXPECT_FALSE(testContext->m_context.audioNeedData); - EXPECT_FALSE(testContext->m_context.audioNeedDataPending); - EXPECT_TRUE(testContext->m_context.videoNeedData); - EXPECT_TRUE(testContext->m_context.videoNeedDataPending); + EXPECT_TRUE(testContext->m_context.audioNeedData); + EXPECT_TRUE(testContext->m_context.audioNeedDataPending); + EXPECT_FALSE(testContext->m_context.videoNeedData); + EXPECT_FALSE(testContext->m_context.videoNeedDataPending); + EXPECT_FALSE(testContext->m_context.eosNotified); + EXPECT_NE(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::AUDIO), + testContext->m_context.endOfStreamInfo.end()); + EXPECT_EQ(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::VIDEO), + testContext->m_context.endOfStreamInfo.end()); } void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() @@ -2219,3 +2193,26 @@ void GenericTasksTestsBase::shouldFlushVideoSrcSuccess() EXPECT_CALL(*testContext->m_gstWrapper, gstElementSendEvent(&testContext->m_appSrcVideo, &testContext->m_event2)) .WillOnce(Return(TRUE)); } + +void GenericTasksTestsBase::triggerSetSourcePosition(firebolt::rialto::MediaSourceType sourceType) +{ + firebolt::rialto::server::tasks::generic::SetSourcePosition task{testContext->m_context, + &testContext->m_gstPlayerClient, + testContext->m_gstWrapper, sourceType, kPosition}; + task.execute(); +} + +void GenericTasksTestsBase::checkInitialPositionSet(firebolt::rialto::MediaSourceType sourceType) +{ + GstElement *source = sourceType == firebolt::rialto::MediaSourceType::AUDIO ? &testContext->m_appSrcAudio + : &testContext->m_appSrcVideo; + ASSERT_NE(testContext->m_context.initialPositions.end(), testContext->m_context.initialPositions.find(source)); + EXPECT_EQ(testContext->m_context.initialPositions.at(source), kPosition); +} + +void GenericTasksTestsBase::checkInitialPositionNotSet(firebolt::rialto::MediaSourceType sourceType) +{ + GstElement *source = sourceType == firebolt::rialto::MediaSourceType::AUDIO ? &testContext->m_appSrcAudio + : &testContext->m_appSrcVideo; + EXPECT_EQ(testContext->m_context.initialPositions.end(), testContext->m_context.initialPositions.find(source)); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 3a7c939d2..a9d143f1c 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -72,6 +72,7 @@ class GenericTasksTestsBase : public ::testing::Test void setContextAudioSourceRemoved(); void setContextStreamInfoEmpty(); void setContextNeedDataAudioOnly(); + void setContextSetupSourceFinished(); // SetupElement test methods void shouldSetupVideoElementOnly(); @@ -259,6 +260,8 @@ class GenericTasksTestsBase : public ::testing::Test void triggerAudioCallbackSeekData(); void triggerVideoCallbackSeekData(); void checkSourcesAttached(); + void checkSetupSourceFinished(); + void checkSetupSourceUnfinished(); // NeedData test methods void triggerNeedDataAudio(); @@ -316,6 +319,11 @@ class GenericTasksTestsBase : public ::testing::Test void checkVideoFlushed(); void shouldFlushVideoSrcSuccess(); + // Set Source Position test methods + void triggerSetSourcePosition(firebolt::rialto::MediaSourceType sourceType); + void checkInitialPositionSet(firebolt::rialto::MediaSourceType sourceType); + void checkInitialPositionNotSet(firebolt::rialto::MediaSourceType sourceType); + private: // SetupElement helper methods void expectSetupVideoElement(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp index c4bfb5a0d..0a02b9919 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FinishSetupSourceTest.cpp @@ -33,12 +33,14 @@ TEST_F(FinishSetupSourceTest, shouldFinishSetupSource) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); } TEST_F(FinishSetupSourceTest, shouldScheduleAudioNeedData) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); shouldScheduleNeedMediaDataAudio(); triggerAudioCallbackNeedData(); } @@ -47,6 +49,7 @@ TEST_F(FinishSetupSourceTest, shouldScheduleVideoNeedData) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); shouldScheduleNeedMediaDataVideo(); triggerVideoCallbackNeedData(); } @@ -62,6 +65,7 @@ TEST_F(FinishSetupSourceTest, shouldScheduleVideoEnoughData) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); shouldScheduleEnoughDataVideo(); triggerVideoCallbackEnoughData(); } @@ -70,6 +74,7 @@ TEST_F(FinishSetupSourceTest, shouldScheduleAudioSeekData) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); shouldScheduleEnoughDataAudio(); triggerAudioCallbackSeekData(); } @@ -78,6 +83,7 @@ TEST_F(FinishSetupSourceTest, shouldScheduleVideoSeekData) { shouldFinishSetupSource(); triggerFinishSetupSource(); + checkSetupSourceFinished(); shouldScheduleEnoughDataVideo(); triggerVideoCallbackSeekData(); } @@ -86,4 +92,5 @@ TEST_F(FinishSetupSourceTest, shouldntFinishSetupSourceWhenSourceNotSet) setContextSourceNull(); triggerFinishSetupSource(); checkSourcesAttached(); + checkSetupSourceUnfinished(); } diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp index a98755681..c56e716dd 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FlushTest.cpp @@ -24,10 +24,15 @@ class FlushTest : public GenericTasksTestsBase protected: FlushTest() { + setContextNeedData(true); + setContextNeedDataPending(true); setContextAudioBuffer(); setContextVideoBuffer(); setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); + setContextEndOfStream(firebolt::rialto::MediaSourceType::AUDIO); + setContextEndOfStream(firebolt::rialto::MediaSourceType::VIDEO); + setContextEndOfStreamNotified(); } }; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 9b996a993..7d2d34913 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -45,6 +45,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetVideoGeometry.h" #include "tasks/generic/SetVolume.h" #include "tasks/generic/SetupElement.h" @@ -269,7 +270,14 @@ 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, firebolt::rialto::MediaSourceType::AUDIO, true); EXPECT_NE(task, nullptr); EXPECT_NO_THROW(dynamic_cast(*task)); } + +TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetSourcePosition) +{ + auto task = m_sut.createSetSourcePosition(m_context, firebolt::rialto::MediaSourceType::AUDIO, 0); + EXPECT_NE(task, nullptr); + EXPECT_NO_THROW(dynamic_cast(*task)); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetSourcePositionTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetSourcePositionTest.cpp new file mode 100644 index 000000000..81efacfa3 --- /dev/null +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetSourcePositionTest.cpp @@ -0,0 +1,77 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GenericTasksTestsBase.h" + +class SetSourcePositionTest : public GenericTasksTestsBase +{ +protected: + SetSourcePositionTest() + { + setContextPlaybackRate(); + setContextStreamInfo(firebolt::rialto::MediaSourceType::AUDIO); + setContextStreamInfo(firebolt::rialto::MediaSourceType::VIDEO); + } +}; + +TEST_F(SetSourcePositionTest, ShouldNotSetSourcePositionForUnknownSource) +{ + triggerSetSourcePosition(firebolt::rialto::MediaSourceType::UNKNOWN); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::VIDEO); +} + +TEST_F(SetSourcePositionTest, ShouldNotSetSourcePositionWhenSourceIsNotAccessible) +{ + setContextStreamInfoEmpty(); + triggerSetSourcePosition(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::VIDEO); +} + +TEST_F(SetSourcePositionTest, ShouldSetAudioSourcePositionWithoutNeedData) +{ + triggerSetSourcePosition(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionSet(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::VIDEO); +} + +TEST_F(SetSourcePositionTest, ShouldSetAudioSourcePositionWithNeedData) +{ + setContextEndOfStream(firebolt::rialto::MediaSourceType::AUDIO); + setContextEndOfStreamNotified(); + setContextSetupSourceFinished(); + shouldNotifyNeedAudioDataSuccess(); + triggerSetSourcePosition(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionSet(firebolt::rialto::MediaSourceType::AUDIO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::VIDEO); + checkNoEos(); +} + +TEST_F(SetSourcePositionTest, ShouldSetVideoSourcePositionWithNeedData) +{ + setContextEndOfStream(firebolt::rialto::MediaSourceType::VIDEO); + setContextEndOfStreamNotified(); + setContextSetupSourceFinished(); + shouldNotifyNeedVideoDataSuccess(); + triggerSetSourcePosition(firebolt::rialto::MediaSourceType::VIDEO); + checkInitialPositionSet(firebolt::rialto::MediaSourceType::VIDEO); + checkInitialPositionNotSet(firebolt::rialto::MediaSourceType::AUDIO); + checkNoEos(); +} diff --git a/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp b/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp index a5843b151..c1212d14f 100644 --- a/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp +++ b/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp @@ -107,6 +107,7 @@ class RialtoServerAppSrcGstSrcTest : public ::testing::Test EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_streamInfo.appSrc, StrEq("format"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_streamInfo.appSrc, StrEq("stream-type"))); EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_streamInfo.appSrc, StrEq("min-percent"))); + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_streamInfo.appSrc, StrEq("handle-segment-change"))); EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCallbacks(GST_APP_SRC(m_streamInfo.appSrc), &m_callbacks, this, nullptr)); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp index df797378a..bcc9c371a 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp @@ -356,3 +356,15 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToFlush) mediaPipelineServiceWillFailToFlush(); sendFlushRequestAndReceiveResponse(); } + +TEST_F(MediaPipelineModuleServiceTests, shouldSetSourcePosition) +{ + mediaPipelineServiceWillSetSourcePosition(); + sendSetSourcePositionRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToSetSourcePosition) +{ + mediaPipelineServiceWillFailToSetSourcePosition(); + sendSetSourcePositionRequestAndReceiveResponse(); +} diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index 90d33f8d8..d52527488 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp @@ -488,6 +488,20 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToFlush() EXPECT_CALL(m_mediaPipelineServiceMock, flush(kHardcodedSessionId, kSourceId, kResetTime)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetSourcePosition() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, setSourcePosition(kHardcodedSessionId, kSourceId, kPosition)) + .WillOnce(Return(true)); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToSetSourcePosition() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, setSourcePosition(kHardcodedSessionId, kSourceId, kPosition)) + .WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaClientWillSendPlaybackStateChangedEvent() { EXPECT_CALL(*m_clientMock, sendEvent(PlaybackStateChangeEventMatcher(convertPlaybackState(kPlaybackState)))); @@ -840,6 +854,18 @@ void MediaPipelineModuleServiceTests::sendFlushRequestAndReceiveResponse() m_service->flush(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendSetSourcePositionRequestAndReceiveResponse() +{ + firebolt::rialto::SetSourcePositionRequest request; + firebolt::rialto::SetSourcePositionResponse response; + + request.set_session_id(kHardcodedSessionId); + request.set_source_id(kSourceId); + request.set_position(kPosition); + + m_service->setSourcePosition(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendPlaybackStateChangedEvent() { ASSERT_TRUE(m_mediaPipelineClient); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h index 237a91cba..4279cd1c0 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h @@ -82,6 +82,8 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaPipelineServiceWillFailToGetMute(); void mediaPipelineServiceWillFlush(); void mediaPipelineServiceWillFailToFlush(); + void mediaPipelineServiceWillSetSourcePosition(); + void mediaPipelineServiceWillFailToSetSourcePosition(); void mediaClientWillSendPlaybackStateChangedEvent(); void mediaClientWillSendNetworkStateChangedEvent(); void mediaClientWillSendNeedMediaDataEvent(int sessionId); @@ -117,6 +119,7 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendGetMuteRequestAndReceiveResponse(); void sendGetMuteRequestAndReceiveResponseWithoutMuteMatch(); void sendFlushRequestAndReceiveResponse(); + void sendSetSourcePositionRequestAndReceiveResponse(); void sendPlaybackStateChangedEvent(); void sendNetworkStateChangedEvent(); void sendNeedMediaDataEvent(); diff --git a/tests/unittests/media/server/main/CMakeLists.txt b/tests/unittests/media/server/main/CMakeLists.txt index 672fdf515..c9db8f8a6 100644 --- a/tests/unittests/media/server/main/CMakeLists.txt +++ b/tests/unittests/media/server/main/CMakeLists.txt @@ -39,6 +39,7 @@ add_gtests ( mediaPipeline/MiscellaneousFunctionsTest.cpp mediaPipeline/HaveDataTest.cpp mediaPipeline/FlushTest.cpp + mediaPipeline/SetSourcePositionTest.cpp mediaPipelineCapabilities/MediaPipelineCapabilitiesTest.cpp diff --git a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp index b19278755..7434809b6 100644 --- a/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/FlushTest.cpp @@ -64,7 +64,7 @@ TEST_F(RialtoServerMediaPipelineFlushTest, FlushNoGstPlayerFailure) } /** - * Test that RemoveSource fails if source is not present. + * Test that Flush fails if source is not present. */ TEST_F(RialtoServerMediaPipelineFlushTest, FlushNoSourcePresent) { @@ -73,3 +73,29 @@ TEST_F(RialtoServerMediaPipelineFlushTest, FlushNoSourcePresent) EXPECT_FALSE(m_mediaPipeline->flush(m_kDummySourceId, m_kResetTime)); } + +/** + * Test that Flush resets the Eos flag on success + */ +TEST_F(RialtoServerMediaPipelineFlushTest, FlushResetEos) +{ + std::unique_ptr mediaSource = + std::make_unique(m_kMimeType); + + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_CALL(*m_gstPlayerMock, attachSource(Ref(mediaSource))); + EXPECT_EQ(m_mediaPipeline->attachSource(mediaSource), true); + std::int32_t sourceId{mediaSource->getId()}; + setEos(firebolt::rialto::MediaSourceType::VIDEO); + + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_CALL(*m_gstPlayerMock, flush(m_kType, m_kResetTime)); + EXPECT_TRUE(m_mediaPipeline->flush(sourceId, m_kResetTime)); + + // Expect need data notified to client + expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 3); + m_gstPlayerCallback->notifyNeedMediaData(firebolt::rialto::MediaSourceType::VIDEO); +} diff --git a/tests/unittests/media/server/main/mediaPipeline/SetSourcePositionTest.cpp b/tests/unittests/media/server/main/mediaPipeline/SetSourcePositionTest.cpp new file mode 100644 index 000000000..0dcd074a4 --- /dev/null +++ b/tests/unittests/media/server/main/mediaPipeline/SetSourcePositionTest.cpp @@ -0,0 +1,101 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineTestBase.h" + +using ::testing::Ref; + +class RialtoServerMediaPipelineSetSourcePositionTest : public MediaPipelineTestBase +{ +protected: + const MediaSourceType m_kType{MediaSourceType::VIDEO}; + const char *m_kMimeType{"video/mpeg"}; + const int64_t m_kPosition{4321}; + const int m_kDummySourceId{123}; + + RialtoServerMediaPipelineSetSourcePositionTest() { createMediaPipeline(); } + + ~RialtoServerMediaPipelineSetSourcePositionTest() { destroyMediaPipeline(); } +}; + +/** + * Test that SetSourcePosition returns success if the gstreamer player API succeeds. + */ +TEST_F(RialtoServerMediaPipelineSetSourcePositionTest, SetSourcePositionSuccess) +{ + std::unique_ptr mediaSource = + std::make_unique(m_kMimeType); + + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_CALL(*m_gstPlayerMock, attachSource(Ref(mediaSource))); + EXPECT_EQ(m_mediaPipeline->attachSource(mediaSource), true); + std::int32_t sourceId{mediaSource->getId()}; + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setSourcePosition(m_kType, m_kPosition)); + EXPECT_TRUE(m_mediaPipeline->setSourcePosition(sourceId, m_kPosition)); +} + +/** + * Test that SetSourcePosition fails if load has not been called (no gstreamer player). + */ +TEST_F(RialtoServerMediaPipelineSetSourcePositionTest, SetSourcePositionNoGstPlayerFailure) +{ + mainThreadWillEnqueueTaskAndWait(); + EXPECT_FALSE(m_mediaPipeline->setSourcePosition(m_kDummySourceId, m_kPosition)); +} + +/** + * Test that SetSourcePosition fails if source is not present. + */ +TEST_F(RialtoServerMediaPipelineSetSourcePositionTest, SetSourcePositionNoSourcePresent) +{ + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_FALSE(m_mediaPipeline->setSourcePosition(m_kDummySourceId, m_kPosition)); +} + +/** + * Test that SetSourcePosition resets the Eos flag on success + */ +TEST_F(RialtoServerMediaPipelineSetSourcePositionTest, SetSourcePositionResetEos) +{ + std::unique_ptr mediaSource = + std::make_unique(m_kMimeType); + + loadGstPlayer(); + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_CALL(*m_gstPlayerMock, attachSource(Ref(mediaSource))); + EXPECT_EQ(m_mediaPipeline->attachSource(mediaSource), true); + std::int32_t sourceId{mediaSource->getId()}; + setEos(firebolt::rialto::MediaSourceType::VIDEO); + + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_CALL(*m_gstPlayerMock, setSourcePosition(m_kType, m_kPosition)); + EXPECT_TRUE(m_mediaPipeline->setSourcePosition(sourceId, m_kPosition)); + + // Expect need data notified to client + expectNotifyNeedData(firebolt::rialto::MediaSourceType::VIDEO, sourceId, 3); + m_gstPlayerCallback->notifyNeedMediaData(firebolt::rialto::MediaSourceType::VIDEO); +} diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index a24940ea6..62ed8599f 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -99,8 +99,10 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory MOCK_METHOD(std::unique_ptr, createPing, (std::unique_ptr && heartbeatHandler), (const, override)); MOCK_METHOD(std::unique_ptr, createFlush, - (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, - const firebolt::rialto::MediaSourceType &type, bool resetTime), + (GenericPlayerContext & context, const firebolt::rialto::MediaSourceType &type, bool resetTime), + (const, override)); + MOCK_METHOD(std::unique_ptr, createSetSourcePosition, + (GenericPlayerContext & context, const firebolt::rialto::MediaSourceType &type, std::int64_t position), (const, override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index 84787abfa..7bb0337f3 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -53,6 +53,7 @@ class GstGenericPlayerMock : public IGstGenericPlayer MOCK_METHOD(bool, getMute, (bool &mute), (override)); MOCK_METHOD(void, ping, (std::unique_ptr && heartbeatHandler), (override)); MOCK_METHOD(void, flush, (const MediaSourceType &mediaSourceType, bool resetTime), (override)); + MOCK_METHOD(void, setSourcePosition, (const MediaSourceType &mediaSourceType, int64_t position), (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 87ff0b2d7..cacb64c9f 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -54,6 +54,7 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, getMute, (bool &mute), (override)); MOCK_METHOD(void, ping, (std::unique_ptr && heartbeatHandler), (override)); MOCK_METHOD(bool, flush, (int32_t sourceId, bool resetTime), (override)); + MOCK_METHOD(bool, setSourcePosition, (int32_t sourceId, int64_t position), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index 32bf46ce6..b10e9c32c 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -52,6 +52,7 @@ class MediaPipelineServiceMock : public IMediaPipelineService MOCK_METHOD(bool, setMute, (int sessionId, bool mute), (override)); MOCK_METHOD(bool, getMute, (int sessionId, bool &mute), (override)); MOCK_METHOD(bool, flush, (int, std::int32_t, bool), (override)); + MOCK_METHOD(bool, setSourcePosition, (int sessionId, int32_t sourceId, int64_t position), (override)); MOCK_METHOD(std::vector, getSupportedMimeTypes, (MediaSourceType type), (override)); MOCK_METHOD(bool, isMimeTypeSupported, (const std::string &mimeType), (override)); MOCK_METHOD(void, ping, (const std::shared_ptr &heartbeatProcedure), (override)); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp index e29b7c4e2..a13569b46 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp @@ -454,6 +454,26 @@ TEST_F(MediaPipelineServiceTests, shouldFlush) flushShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToSetSourcePositionForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + setSourcePositionShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToSetSourcePosition) +{ + initSession(); + mediaPipelineWillFailToSetSourcePosition(); + setSourcePositionShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldSetSourcePosition) +{ + initSession(); + mediaPipelineWillSetSourcePosition(); + setSourcePositionShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldPing) { initSession(); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp index 6e84479cb..a736abcae 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -276,6 +276,16 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToFlush() EXPECT_CALL(m_mediaPipelineMock, flush(kSourceId, kResetTime)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillSetSourcePosition() +{ + EXPECT_CALL(m_mediaPipelineMock, setSourcePosition(kSourceId, kPosition)).WillOnce(Return(true)); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToSetSourcePosition() +{ + EXPECT_CALL(m_mediaPipelineMock, setSourcePosition(kSourceId, kPosition)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillPing() { EXPECT_CALL(*m_heartbeatProcedureMock, createHandler()) @@ -566,6 +576,16 @@ void MediaPipelineServiceTests::flushShouldFail() EXPECT_FALSE(m_sut->flush(kSessionId, kSourceId, kResetTime)); } +void MediaPipelineServiceTests::setSourcePositionShouldSucceed() +{ + EXPECT_TRUE(m_sut->setSourcePosition(kSessionId, kSourceId, kPosition)); +} + +void MediaPipelineServiceTests::setSourcePositionShouldFail() +{ + EXPECT_FALSE(m_sut->setSourcePosition(kSessionId, kSourceId, kPosition)); +} + void MediaPipelineServiceTests::clearMediaPipelines() { m_sut->clearMediaPipelines(); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h index c0aab1343..1a34f1428 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h @@ -76,6 +76,8 @@ class MediaPipelineServiceTests : public testing::Test void mediaPipelineWillFailToGetMute(); void mediaPipelineWillFlush(); void mediaPipelineWillFailToFlush(); + void mediaPipelineWillSetSourcePosition(); + void mediaPipelineWillFailToSetSourcePosition(); void mediaPipelineWillPing(); void mediaPipelineFactoryWillCreateMediaPipeline(); @@ -131,6 +133,8 @@ class MediaPipelineServiceTests : public testing::Test void getMuteShouldFail(); void flushShouldSucceed(); void flushShouldFail(); + void setSourcePositionShouldSucceed(); + void setSourcePositionShouldFail(); void clearMediaPipelines(); void initSession(); void triggerPing(); diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 56bf1bc0f..ab47266dc 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -524,21 +524,6 @@ class GstWrapper : public IGstWrapper return gst_object_set_name(object, name); } - GstQuery *gstQueryNewSegment(GstFormat format) const override { return gst_query_new_segment(format); } - - gboolean gstElementQuery(GstElement *element, GstQuery *query) const override - { - return gst_element_query(element, query); - } - - void gstQueryParseSegment(GstQuery *query, gdouble *rate, GstFormat *format, gint64 *startValue, - gint64 *stopValue) const override - { - gst_query_parse_segment(query, rate, format, startValue, stopValue); - } - - void gstQueryUnref(GstQuery *query) const override { gst_query_unref(query); } - gboolean gstSegmentDoSeek(GstSegment *segment, gdouble rate, GstFormat format, GstSeekFlags flags, GstSeekType startType, guint64 start, GstSeekType stopType, guint64 stop, gboolean *update) const override @@ -546,9 +531,16 @@ class GstWrapper : public IGstWrapper return gst_segment_do_seek(segment, rate, format, flags, startType, start, stopType, stop, update); } - gboolean gstBaseSrcNewSeamlessSegment(GstBaseSrc *src, gint64 start, gint64 stop, gint64 time) const override + GstSample *gstSampleNew(GstBuffer *buffer, GstCaps *caps, const GstSegment *segment, GstStructure *info) const override + { + return gst_sample_new(buffer, caps, segment, info); + } + + void gstSampleUnref(GstSample *sample) const override { gst_sample_unref(sample); } + + GstFlowReturn gstAppSrcPushSample(GstAppSrc *appsrc, GstSample *sample) const override { - return gst_base_src_new_seamless_segment(src, start, stop, time); + return gst_app_src_push_sample(appsrc, sample); } GstContext *gstContextNew(const gchar *context_type, gboolean persistent) const override diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index 9d8c74bc5..5b869aec5 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -1212,45 +1212,6 @@ class IGstWrapper */ virtual gboolean gstObjectSetName(GstObject *object, const gchar *name) const = 0; - /** - * @brief Constructs a new segment query object. A segment query is used to discover - * information about the currently configured segment for playback. - * - * @param[in] format : the GstFormat for the new query - * - * @retval a new GstQuery - */ - virtual GstQuery *gstQueryNewSegment(GstFormat format) const = 0; - - /** - * @brief Performs a query on the given element. - * - * @param[in] element : a GstElement to perform the query on. - * @param[in] query : the GstQuery. - * - * @retval TRUE if the query could be performed. MT safe. - */ - virtual gboolean gstElementQuery(GstElement *element, GstQuery *query) const = 0; - - /** - * @brief Parse a segment query answer. - * - * @param[in] query : a GstQuery - * @param[out] rate : the storage for the rate of the segment, or NULL - * @param[out] format : the storage for the GstFormat of the values, or NULL - * @param[out] startValue : the storage for the start value, or NULL - * @param[out] stopValue : the storage for the stop value, or NULL - */ - virtual void gstQueryParseSegment(GstQuery *query, gdouble *rate, GstFormat *format, gint64 *startValue, - gint64 *stopValue) const = 0; - - /** - * @brief Decreases the refcount of the query. If the refcount reaches 0, the query will be freed. - * - * @param[in] query : a GstQuery - */ - virtual void gstQueryUnref(GstQuery *query) const = 0; - /** * @brief Update the segment structure with the field values of a seek event (see gst_event_new_seek). * @@ -1271,16 +1232,36 @@ class IGstWrapper gboolean *update) const = 0; /** - * @brief Prepare a new seamless segment for emission downstream. + * @brief Create a new GstSample with the provided details. * - * @param[in] src : The source - * @param[in] start : The new start value for the segment - * @param[in] stop : Stop value for the new segment - * @param[in] time : The new time value for the start of the new segent + * @param[in] buffer : a GstBuffer, or NULL + * @param[in] caps : a GstCaps, or NULL + * @param[in] segment : a GstSegment, or NULL + * @param[in] info : a GstStructure, or NULL * - * @retval TRUE if the seek could be performed. + * @retval the new GstSample. gst_sample_unref after usage. + */ + virtual GstSample *gstSampleNew(GstBuffer *buffer, GstCaps *caps, const GstSegment *segment, + GstStructure *info) const = 0; + + /** + * @brief Decreases the refcount of the sample. If the refcount reaches 0, the sample will be freed. + * + * @param[in] sample : a GstSample + */ + virtual void gstSampleUnref(GstSample *sample) const = 0; + + /** + * @brief Extract a buffer from the provided sample and adds it to the queue of buffers that the appsrc element will + * push to its source pad. + * + * @param[in] appsrc : a GstAppSrc + * @param[in] sample : a GstSample from which buffer and caps may be extracted + * + * @retval GST_FLOW_OK when the buffer was successfully queued. GST_FLOW_FLUSHING when appsrc is not PAUSED or + * PLAYING. GST_FLOW_EOS when EOS occurred. */ - virtual gboolean gstBaseSrcNewSeamlessSegment(GstBaseSrc *src, gint64 start, gint64 stop, gint64 time) const = 0; + virtual GstFlowReturn gstAppSrcPushSample(GstAppSrc *appsrc, GstSample *sample) const = 0; /** * @brief Create a new GstContext with the given parameters.