From 5dd938b98345586718b3f2ced132160a6787efcc Mon Sep 17 00:00:00 2001 From: nrames759 Date: Wed, 15 Apr 2026 09:04:58 +0530 Subject: [PATCH 1/6] gst_app_src_set_max_bytes to gst_app_src_set_max_buffers --- .../gstplayer/include/GstPlayerConfig.h | 119 ++++++++++++++++++ .../gstplayer/include/WebAudioPlayerContext.h | 4 +- .../gstplayer/source/GstGenericPlayer.cpp | 5 + media/server/gstplayer/source/GstSrc.cpp | 30 ++++- .../gstplayer/source/GstWebAudioPlayer.cpp | 9 +- .../source/tasks/webAudio/WriteBuffer.cpp | 7 +- .../externalLibraryMocks/GstWrapperMock.h | 1 + .../server/fixtures/MediaPipelineTest.cpp | 6 + .../mediaPipeline/DualVideoPlaybackTest.cpp | 4 + .../tests/webAudio/WebAudioTestMethods.cpp | 4 + .../server/gstplayer/rialtoSrc/AppSrcTest.cpp | 6 + .../common/GstWebAudioPlayerTestCommon.cpp | 4 + .../common/WebAudioTasksTestsBase.cpp | 2 +- wrappers/include/GstWrapper.h | 10 ++ wrappers/interface/IGstWrapper.h | 9 ++ 15 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 media/server/gstplayer/include/GstPlayerConfig.h diff --git a/media/server/gstplayer/include/GstPlayerConfig.h b/media/server/gstplayer/include/GstPlayerConfig.h new file mode 100644 index 000000000..60d877e47 --- /dev/null +++ b/media/server/gstplayer/include/GstPlayerConfig.h @@ -0,0 +1,119 @@ +/* + * 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_GST_PLAYER_CONFIG_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PLAYER_CONFIG_H_ + +/** + * @file GstPlayerConfig.h + * + * Configuration constants for GStreamer player buffer management. + * + * These constants control the appsrc queue limits for different media types. + * They support both byte-based limits (GStreamer < 1.20) and buffer-based + * limits (GStreamer >= 1.20). + * + * IMPORTANT: The buffer counts are based on estimated average buffer sizes. + * These estimates may need adjustment based on your actual content: + * - Video buffers vary by codec, bitrate, GOP structure, resolution + * - Audio buffers vary by codec, bitrate, sample rate, chunk duration + * - Subtitle buffers vary by format complexity and styling + * + * To validate and tune these values: + * 1. Build with -DRIALTO_ENABLE_BUFFER_SIZE_LOGGING + * 2. Run typical playback scenarios + * 3. Analyze actual buffer sizes in logs + * 4. Adjust these constants if needed + */ + +#include + +namespace firebolt::rialto::server +{ +/** + * @brief Video appsrc max queue size in bytes (for GStreamer < 1.20) + */ +constexpr uint32_t kVideoMaxBytes = 8 * 1024 * 1024; // 8 MB + +/** + * @brief Audio appsrc max queue size in bytes (for GStreamer < 1.20) + */ +constexpr uint32_t kAudioMaxBytes = 512 * 1024; // 512 KB + +/** + * @brief Subtitle appsrc max queue size in bytes (for GStreamer < 1.20) + */ +constexpr uint32_t kSubtitleMaxBytes = 256 * 1024; // 256 KB + +/** + * @brief WebAudio appsrc max queue size in bytes (for GStreamer < 1.20) + */ +constexpr uint32_t kWebAudioMaxBytes = 10 * 1024; // 10 KB + +/** + * @brief Video appsrc max queue size in buffer count (for GStreamer >= 1.20) + * + * Based on: 8MB ÷ ~200KB avg video buffer = 40 buffers + * Estimated avg buffer size: ~200 KB (HD video with mixed I/P/B frames) + * + * Adjust based on your content characteristics: + * - Higher for low bitrate content (smaller buffers) + * - Lower for high bitrate 4K content (larger buffers) + */ +constexpr uint32_t kVideoMaxBuffers = 40; + +/** + * @brief Audio appsrc max queue size in buffer count (for GStreamer >= 1.20) + * + * Based on: 512KB ÷ ~16KB avg audio buffer = 32 buffers + * Estimated avg buffer size: ~16 KB (compressed audio at 128-256 kbps) + * + * Adjust based on your audio format: + * - Higher for lower bitrate audio (smaller buffers) + * - Lower for high bitrate or lossless audio (larger buffers) + */ +constexpr uint32_t kAudioMaxBuffers = 32; + +/** + * @brief Subtitle appsrc max queue size in buffer count (for GStreamer >= 1.20) + * + * Based on: 256KB ÷ ~4KB avg subtitle buffer = 64 buffers + * Estimated avg buffer size: ~4 KB (text subtitles with minimal styling) + * + * Adjust based on subtitle format: + * - Higher for simple text subtitles (smaller buffers) + * - Lower for complex styled or image-based subtitles (larger buffers) + */ +constexpr uint32_t kSubtitleMaxBuffers = 64; + +/** + * @brief WebAudio appsrc max queue size in buffer count (for GStreamer >= 1.20) + * + * Based on: 10KB ÷ ~512 bytes avg buffer = 20 buffers + * Estimated avg buffer size: ~512 bytes (small chunks for low latency) + * + * Adjust based on latency requirements: + * - Higher for lower latency (smaller chunk sizes) + * - Lower if larger chunks acceptable + */ +constexpr uint32_t kWebAudioMaxBuffers = 20; + +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PLAYER_CONFIG_H_ diff --git a/media/server/gstplayer/include/WebAudioPlayerContext.h b/media/server/gstplayer/include/WebAudioPlayerContext.h index 3fda093c7..543174dc6 100644 --- a/media/server/gstplayer/include/WebAudioPlayerContext.h +++ b/media/server/gstplayer/include/WebAudioPlayerContext.h @@ -29,10 +29,12 @@ #include #include "IGstSrc.h" +#include "GstPlayerConfig.h" namespace firebolt::rialto::server { -constexpr uint32_t kMaxWebAudioBytes{10 * 1024}; +// WebAudio buffer limits are now defined in GstPlayerConfig.h +// Use kWebAudioMaxBytes and kWebAudioMaxBuffers constants struct WebAudioPlayerContext { diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index ef9e985b8..8ada9b7a9 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -1275,6 +1275,11 @@ void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaT for (GstBuffer *buffer : streamInfo.buffers) { +#ifdef RIALTO_ENABLE_BUFFER_SIZE_LOGGING + gsize bufferSize = gst_buffer_get_size(buffer); + GST_DEBUG("Pushing %s buffer of size: %zu bytes", + common::convertMediaSourceType(mediaType), bufferSize); +#endif m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(streamInfo.appSrc), buffer); } streamInfo.buffers.clear(); diff --git a/media/server/gstplayer/source/GstSrc.cpp b/media/server/gstplayer/source/GstSrc.cpp index ff7b1e227..7955af23c 100644 --- a/media/server/gstplayer/source/GstSrc.cpp +++ b/media/server/gstplayer/source/GstSrc.cpp @@ -20,6 +20,7 @@ #include #include "GstSrc.h" +#include "GstPlayerConfig.h" #include "GstTextTrackSinkFactory.h" #include "RialtoServerLogging.h" #include @@ -397,19 +398,38 @@ void GstSrc::setupAndAddAppSrc(IDecryptionService *decryptionService, GstElement GST_APP_STREAM_TYPE_STREAM, "min-percent", 20, "handle-segment-change", TRUE, nullptr); m_gstWrapper->gstAppSrcSetCallbacks(GST_APP_SRC(streamInfo.appSrc), callbacks, userData, nullptr); + // Queue size limits: we support both byte-based (GStreamer < 1.20) and buffer-based (>= 1.20) limits. + // The buffer counts are computed to maintain similar memory footprint as the byte limits. + // To validate these assumptions, build with -DRIALTO_ENABLE_BUFFER_SIZE_LOGGING to log actual buffer sizes. + // See GstPlayerConfig.h to adjust these values. + + // Queue size in bytes (for GStreamer < 1.20 fallback) const std::unordered_map queueSize = - {{firebolt::rialto::MediaSourceType::VIDEO, 8 * 1024 * 1024}, - {firebolt::rialto::MediaSourceType::AUDIO, 512 * 1024}, - {firebolt::rialto::MediaSourceType::SUBTITLE, 256 * 1024}}; + {{firebolt::rialto::MediaSourceType::VIDEO, kVideoMaxBytes}, + {firebolt::rialto::MediaSourceType::AUDIO, kAudioMaxBytes}, + {firebolt::rialto::MediaSourceType::SUBTITLE, kSubtitleMaxBytes}}; + + // Queue size in buffer counts (for GStreamer >= 1.20) + // See GstPlayerConfig.h for detailed documentation on these values. + const std::unordered_map queueBuffers = + {{firebolt::rialto::MediaSourceType::VIDEO, kVideoMaxBuffers}, + {firebolt::rialto::MediaSourceType::AUDIO, kAudioMaxBuffers}, + {firebolt::rialto::MediaSourceType::SUBTITLE, kSubtitleMaxBuffers}}; auto sizeIt = queueSize.find(type); - if (sizeIt != queueSize.end()) + auto buffersIt = queueBuffers.find(type); + if (sizeIt != queueSize.end() && buffersIt != queueBuffers.end()) { + // Use buffer-based limits on GStreamer 1.20+, fall back to byte-based on older versions +#if GST_CHECK_VERSION(1, 20, 0) + m_gstWrapper->gstAppSrcSetMaxBuffers(GST_APP_SRC(streamInfo.appSrc), buffersIt->second); +#else m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(streamInfo.appSrc), sizeIt->second); +#endif } else { - GST_WARNING_OBJECT(source, "Could not find max-bytes value for appsrc"); + GST_WARNING_OBJECT(source, "Could not find max-bytes/max-buffers value for appsrc"); } m_gstWrapper->gstAppSrcSetStreamType(GST_APP_SRC(streamInfo.appSrc), GST_APP_STREAM_TYPE_SEEKABLE); diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index 551730e15..f1b3f8c83 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -163,7 +163,14 @@ bool GstWebAudioPlayer::initWebAudioPipeline(const uint32_t priority) RIALTO_SERVER_LOG_ERROR("Failed to create the appsrc"); return false; } - m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(m_context.source), kMaxWebAudioBytes); + + // Set queue limits: Use buffer-based on GStreamer 1.20+, byte-based on older versions. + // See GstPlayerConfig.h to adjust these values. +#if GST_CHECK_VERSION(1, 20, 0) + m_gstWrapper->gstAppSrcSetMaxBuffers(GST_APP_SRC(m_context.source), kWebAudioMaxBuffers); +#else + m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(m_context.source), kWebAudioMaxBytes); +#endif m_glibWrapper->gObjectSet(m_context.source, "format", GST_FORMAT_TIME, nullptr); // Perform sink specific initalisation diff --git a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp index bda9b5685..63914f5d4 100644 --- a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp @@ -44,7 +44,7 @@ void WriteBuffer::execute() const { RIALTO_SERVER_LOG_DEBUG("Executing WriteBuffer"); - uint64_t freeBytes = kMaxWebAudioBytes - m_gstWrapper->gstAppSrcGetCurrentLevelBytes(GST_APP_SRC(m_context.source)); + uint64_t freeBytes = kWebAudioMaxBytes - m_gstWrapper->gstAppSrcGetCurrentLevelBytes(GST_APP_SRC(m_context.source)); uint64_t maxBytesToWrite = std::min(freeBytes, m_mainLength + m_wrapLength); uint64_t bytesToWrite = maxBytesToWrite - (maxBytesToWrite % m_context.bytesPerSample); uint64_t bytesWritten = 0; @@ -80,6 +80,11 @@ void WriteBuffer::execute() const bytesToWrite, bytesWritten); } +#ifdef RIALTO_ENABLE_BUFFER_SIZE_LOGGING + gsize bufferSize = gst_buffer_get_size(gstBuffer); + GST_DEBUG("Pushing WebAudio buffer of size: %zu bytes", bufferSize); +#endif + if (GST_FLOW_OK != m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(m_context.source), gstBuffer)) { RIALTO_SERVER_LOG_ERROR("Failed to push the buffers to the appsrc"); diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index c30acf396..8d6d506ad 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -63,6 +63,7 @@ class GstWrapperMock : public IGstWrapper (GstAppSrc * appsrc, GstAppSrcCallbacks *callbacks, gpointer userData, GDestroyNotify notify), (override)); MOCK_METHOD(void, gstAppSrcSetMaxBytes, (GstAppSrc * appsrc, guint64 max), (override)); + MOCK_METHOD(void, gstAppSrcSetMaxBuffers, (GstAppSrc * appsrc, guint max), (override)); MOCK_METHOD(void, gstAppSrcSetStreamType, (GstAppSrc * appsrc, GstAppStreamType type), (override)); MOCK_METHOD(gboolean, gstBinAdd, (GstBin * bin, GstElement *element), (override)); MOCK_METHOD(void, gstBaseTransformSetInPlace, (GstBaseTransform * trans, gboolean in_place), (override)); diff --git a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp index 34fd4738a..6ae9a6c16 100644 --- a/tests/componenttests/server/fixtures/MediaPipelineTest.cpp +++ b/tests/componenttests/server/fixtures/MediaPipelineTest.cpp @@ -183,7 +183,13 @@ void MediaPipelineTest::willSetupAndAddSource(GstAppSrc *appSrc) 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"))); +#if GST_CHECK_VERSION(1, 20, 0) + // GStreamer 1.20+ uses buffer-based limits + const guint kMaxBuffers = ((appSrc == &m_audioAppSrc) ? 32 : 40); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBuffers(appSrc, kMaxBuffers)); +#else EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(appSrc, kMaxBytes)); +#endif EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetStreamType(appSrc, GST_APP_STREAM_TYPE_SEEKABLE)); EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(m_sourceName.data())).RetiresOnSaturation(); EXPECT_CALL(*m_gstWrapperMock, gstBinAdd(GST_BIN(&m_rialtoSource), GST_ELEMENT(appSrc))); diff --git a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp index ec861b42b..248e1eb90 100644 --- a/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/DualVideoPlaybackTest.cpp @@ -167,7 +167,11 @@ class DualVideoPlaybackTest : public MediaPipelineTest 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"))); +#if GST_CHECK_VERSION(1, 20, 0) + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBuffers(&m_secondaryVideoAppSrc, 40)); +#else EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(&m_secondaryVideoAppSrc, (8 * 1024 * 1024))); +#endif EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetStreamType(&m_secondaryVideoAppSrc, GST_APP_STREAM_TYPE_SEEKABLE)); EXPECT_CALL(*m_glibWrapperMock, gStrdupPrintfStub(_)).WillOnce(Return(m_sourceName.data())).RetiresOnSaturation(); EXPECT_CALL(*m_gstWrapperMock, diff --git a/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp b/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp index 1d34fe7e3..ad8aee05d 100644 --- a/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp +++ b/tests/componenttests/server/tests/webAudio/WebAudioTestMethods.cpp @@ -175,7 +175,11 @@ void WebAudioTestMethods::willCreateWebAudioPlayer() // EXPECTS coming from... // GstWebAudioPlayerTestCommon::expectInitAppSrc() +#if GST_CHECK_VERSION(1, 20, 0) + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBuffers(&m_appSrc, 20)); +#else EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(&m_appSrc, 10 * 1024)); +#endif EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(G_OBJECT(&m_appSrc), StrEq("format"))); // EXPECTS coming from... diff --git a/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp b/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp index 0e35d1e07..7ff1cbe49 100644 --- a/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp +++ b/tests/unittests/media/server/gstplayer/rialtoSrc/AppSrcTest.cpp @@ -114,7 +114,13 @@ class RialtoServerAppSrcGstSrcTest : public ::testing::Test EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetCallbacks(GST_APP_SRC(m_streamInfo.appSrc), &m_callbacks, this, nullptr)); +#if GST_CHECK_VERSION(1, 20, 0) + // GStreamer 1.20+ uses buffer-based limits + guint maxBuffers = (max == 8 * 1024 * 1024) ? 40 : ((max == 512 * 1024) ? 32 : 64); + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBuffers(GST_APP_SRC(m_streamInfo.appSrc), maxBuffers)); +#else EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(GST_APP_SRC(m_streamInfo.appSrc), max)); +#endif EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetStreamType(GST_APP_SRC(m_streamInfo.appSrc), GST_APP_STREAM_TYPE_SEEKABLE)); } diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp index 42e890c8d..dd3097eef 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/GstWebAudioPlayerTestCommon.cpp @@ -119,7 +119,11 @@ void GstWebAudioPlayerTestCommon::expectCreatePipeline() void GstWebAudioPlayerTestCommon::expectInitAppSrc() { EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryMake(StrEq("appsrc"), StrEq("audsrc"))).WillOnce(Return(&m_appSrc)); +#if GST_CHECK_VERSION(1, 20, 0) + EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBuffers(GST_APP_SRC(&m_appSrc), 20)); +#else EXPECT_CALL(*m_gstWrapperMock, gstAppSrcSetMaxBytes(GST_APP_SRC(&m_appSrc), 10 * 1024)); +#endif EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(G_OBJECT(&m_appSrc), StrEq("format"))); } diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp index c4f3cd1f4..727f1421c 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp @@ -386,7 +386,7 @@ void WebAudioTasksTestsBase::shouldNotWriteBufferIfPushBufferFails() void WebAudioTasksTestsBase::shouldNotWriteBufferIfBytesToWriteLessThanBytesPerSample() { EXPECT_CALL(*testContext->m_gstWrapper, gstAppSrcGetCurrentLevelBytes(GST_APP_SRC(&testContext->m_src))) - .WillOnce(Return(firebolt::rialto::server::kMaxWebAudioBytes - testContext->m_context.bytesPerSample + 1)); + .WillOnce(Return(firebolt::rialto::server::kWebAudioMaxBytes - testContext->m_context.bytesPerSample + 1)); } void WebAudioTasksTestsBase::shouldWriteBufferThatNotAlignedWithBytesPerSample() diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 1c109aa74..47a274364 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -157,6 +157,16 @@ class GstWrapper : public IGstWrapper void gstAppSrcSetMaxBytes(GstAppSrc *appsrc, guint64 max) override { gst_app_src_set_max_bytes(appsrc, max); } + void gstAppSrcSetMaxBuffers(GstAppSrc *appsrc, guint max) override + { +#if GST_CHECK_VERSION(1, 20, 0) + gst_app_src_set_max_buffers(appsrc, max); +#else + (void)appsrc; + (void)max; +#endif + } + void gstAppSrcSetStreamType(GstAppSrc *appsrc, GstAppStreamType type) override { gst_app_src_set_stream_type(appsrc, type); diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index a7a184624..36daf4349 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -339,6 +339,15 @@ class IGstWrapper */ virtual void gstAppSrcSetMaxBytes(GstAppSrc *appsrc, guint64 max) = 0; + /** + * @brief Set the maximum buffers that can be set on the appsrc queue. + * Requires GStreamer >= 1.20. + * + * @param[in] appsrc : The appsrc. + * @param[in] max : Max buffers to set. + */ + virtual void gstAppSrcSetMaxBuffers(GstAppSrc *appsrc, guint max) = 0; + /** * @brief Set the stream type on the appsrc. * From a4ad2492bce8c5d8eb7cd6e670a5fcc280880f9d Mon Sep 17 00:00:00 2001 From: nrames759 Date: Wed, 15 Apr 2026 09:19:19 +0530 Subject: [PATCH 2/6] enable buffer size logging --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb66410b7..61ebdc63b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,8 @@ if ( RIALTO_LOG_DEBUG_ENABLED ) add_compile_definitions( RIALTO_LOG_DEBUG_ENABLED ) endif() +add_compile_definitions( RIALTO_ENABLE_BUFFER_SIZE_LOGGING ) + #check if TextTrack plugin exists in Thunder find_path( TEXT_TRACK_INCLUDE_DIR NAMES WPEFramework/interfaces/ITextTrack.h ) if (TEXT_TRACK_INCLUDE_DIR) From 91d2b663282f8667605b592183f79b9abb66e9b6 Mon Sep 17 00:00:00 2001 From: narenr94 <51772499+narenr94@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:03:36 +0530 Subject: [PATCH 3/6] master sync (#490) Co-authored-by: Koky2701 <90915184+Koky2701@users.noreply.github.com> Co-authored-by: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> --- common/source/EventThread.cpp | 12 +- common/source/Timer.cpp | 30 +++- ipc/client/source/IpcChannelImpl.cpp | 70 ++++----- ipc/server/source/IpcServerImpl.cpp | 7 +- media/client/ipc/include/MediaPipelineIpc.h | 6 + .../client/ipc/interface/IMediaPipelineIpc.h | 35 +++++ media/client/ipc/source/MediaPipelineIpc.cpp | 98 ++++++++++++ media/client/main/include/MediaPipeline.h | 6 + .../client/main/include/MediaPipelineProxy.h | 10 ++ media/client/main/source/MediaPipeline.cpp | 17 ++ media/public/include/IMediaPipeline.h | 35 +++++ media/server/gstplayer/CMakeLists.txt | 1 + .../gstplayer/include/GenericPlayerContext.h | 5 + .../gstplayer/include/GstGenericPlayer.h | 4 + .../include/IGstGenericPlayerPrivate.h | 7 + media/server/gstplayer/include/Utils.h | 1 + .../include/tasks/IGenericPlayerTaskFactory.h | 15 ++ .../tasks/generic/GenericPlayerTaskFactory.h | 4 + .../tasks/generic/SetReportDecodeErrors.h | 49 ++++++ .../gstplayer/interface/IGstGenericPlayer.h | 28 ++++ .../gstplayer/source/GstGenericPlayer.cpp | 97 ++++++++++++ media/server/gstplayer/source/Utils.cpp | 5 + .../source/tasks/generic/DeepElementAdded.cpp | 41 +++-- .../generic/GenericPlayerTaskFactory.cpp | 9 ++ .../tasks/generic/SetReportDecodeErrors.cpp | 52 +++++++ .../source/tasks/generic/SetupElement.cpp | 5 +- .../tasks/generic/UpdatePlaybackGroup.cpp | 14 ++ .../ipc/include/MediaPipelineModuleService.h | 10 ++ .../ipc/source/MediaPipelineModuleService.cpp | 53 +++++++ media/server/main/include/ActiveRequests.h | 2 + .../include/MediaPipelineServerInternal.h | 30 ++++ media/server/main/source/ActiveRequests.cpp | 34 ++-- .../source/MediaPipelineServerInternal.cpp | 78 +++++++++- .../service/include/IMediaPipelineService.h | 3 + .../service/source/MediaPipelineService.cpp | 42 +++++ .../service/source/MediaPipelineService.h | 3 + proto/mediapipelinemodule.proto | 74 +++++++++ .../externalLibraryMocks/GstWrapperMock.h | 1 + .../MediaPipelineProtoRequestMatchers.h | 21 +++ .../client/mocks/MediaPipelineModuleMock.h | 30 ++++ .../tests/base/MediaPipelineTestMethods.cpp | 42 +++++ .../tests/base/MediaPipelineTestMethods.h | 6 + .../client/tests/mse/PipelinePropertyTest.cpp | 27 ++++ .../server/common/ActionTraits.h | 8 + .../server/common/MessageBuilders.cpp | 7 + .../server/common/MessageBuilders.h | 1 + .../mediaPipeline/PipelinePropertyTest.cpp | 83 ++++++++-- .../unittests/common/unittests/TimerTests.cpp | 28 ++++ .../unittests/media/client/ipc/CMakeLists.txt | 3 + .../ipc/mediaPipelineIpc/GetDurationTest.cpp | 97 ++++++++++++ .../mediaPipelineIpc/GetQueuedFramesTest.cpp | 100 ++++++++++++ .../SetReportDecodeErrorsTest.cpp | 96 ++++++++++++ .../media/client/main/CMakeLists.txt | 3 + .../main/mediaPipeline/GetDurationTest.cpp | 66 ++++++++ .../mediaPipeline/GetQueuedFramesTest.cpp | 63 ++++++++ .../mediaPipeline/MediaPipelineProxyTest.cpp | 24 +++ .../SetReportDecodeErrorsTest.cpp | 58 +++++++ .../client/mocks/ipc/MediaPipelineIpcMock.h | 3 + .../main/MediaPipelineAndControlClientMock.h | 3 + .../media/server/gstplayer/CMakeLists.txt | 1 + .../GstGenericPlayerPrivateTest.cpp | 31 ++++ .../genericPlayer/GstGenericPlayerTest.cpp | 59 +++++++ .../common/GenericTasksTestsBase.cpp | 93 +++++++++-- .../common/GenericTasksTestsBase.h | 6 + .../common/GstGenericPlayerTestCommon.cpp | 14 ++ .../common/GstGenericPlayerTestCommon.h | 1 + .../GenericPlayerTaskFactoryTest.cpp | 8 + .../tasksTests/SetReportDecodeErrorsTest.cpp | 30 ++++ .../tasksTests/UpdatePlaybackGroupTest.cpp | 16 ++ .../ControlModuleServiceTests.cpp | 9 ++ .../MediaPipelineModuleServiceTests.cpp | 48 ++++++ ...MediaPipelineModuleServiceTestsFixture.cpp | 113 ++++++++++++++ .../MediaPipelineModuleServiceTestsFixture.h | 12 ++ .../activeRequests/ActiveRequestsTests.cpp | 23 +++ .../MiscellaneousFunctionsTest.cpp | 145 ++++++++++++++++++ .../gstplayer/GenericPlayerTaskFactoryMock.h | 4 + .../mocks/gstplayer/GstGenericPlayerMock.h | 4 + .../gstplayer/GstGenericPlayerPrivateMock.h | 1 + .../main/MediaPipelineServerInternalMock.h | 3 + .../mocks/service/MediaPipelineServiceMock.h | 3 + .../MediaPipelineServiceTests.cpp | 60 ++++++++ .../MediaPipelineServiceTestsFixture.cpp | 73 +++++++++ .../MediaPipelineServiceTestsFixture.h | 12 ++ wrappers/include/GstWrapper.h | 5 + wrappers/interface/IGstWrapper.h | 11 ++ 85 files changed, 2437 insertions(+), 110 deletions(-) create mode 100644 media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h create mode 100644 media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp create mode 100644 tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp create mode 100644 tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp create mode 100644 tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp create mode 100644 tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp create mode 100644 tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp create mode 100644 tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp create mode 100644 tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp diff --git a/common/source/EventThread.cpp b/common/source/EventThread.cpp index 1ad60188b..be4bc6d0e 100644 --- a/common/source/EventThread.cpp +++ b/common/source/EventThread.cpp @@ -54,13 +54,11 @@ EventThread::EventThread(std::string threadName) : m_kThreadName(std::move(threa EventThread::~EventThread() { - std::unique_lock locker(m_lock); - - m_shutdown = true; - - m_cond.notify_all(); - - locker.unlock(); + { + std::lock_guard locker(m_lock); + m_shutdown = true; + m_cond.notify_all(); + } if (m_thread.joinable()) m_thread.join(); diff --git a/common/source/Timer.cpp b/common/source/Timer.cpp index ae109a692..8469a4737 100644 --- a/common/source/Timer.cpp +++ b/common/source/Timer.cpp @@ -59,15 +59,22 @@ Timer::Timer(const std::chrono::milliseconds &timeout, const std::function lock{m_mutex}; - if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) + bool shouldExecuteCallback = false; { - if (m_active && m_callback) + std::unique_lock lock{m_mutex}; + if (!m_cv.wait_for(lock, m_timeout, [this]() { return !m_active; })) { - lock.unlock(); - m_callback(); + if (m_active && m_callback) + { + shouldExecuteCallback = true; + } } } + + if (shouldExecuteCallback) + { + m_callback(); + } } while (timerType == TimerType::PERIODIC && m_active); m_active = false; }); @@ -81,10 +88,19 @@ Timer::~Timer() void Timer::cancel() { m_active = false; + m_cv.notify_one(); + + if (std::this_thread::get_id() == m_thread.get_id()) + { + if (m_thread.joinable()) + { + m_thread.detach(); + } + return; + } - if (std::this_thread::get_id() != m_thread.get_id() && m_thread.joinable()) + if (m_thread.joinable()) { - m_cv.notify_one(); m_thread.join(); } } diff --git a/ipc/client/source/IpcChannelImpl.cpp b/ipc/client/source/IpcChannelImpl.cpp index 85c6fea52..f81a01003 100644 --- a/ipc/client/source/IpcChannelImpl.cpp +++ b/ipc/client/source/IpcChannelImpl.cpp @@ -562,37 +562,36 @@ void ChannelImpl::processTimeoutEvent() return; } - // check if any method call has no expired - std::unique_lock locker(m_lock); - // stores the timed-out method calls std::vector timedOuts; - // remove the method calls that have expired - const auto kNow = std::chrono::steady_clock::now(); - auto it = m_methodCalls.begin(); - while (it != m_methodCalls.end()) { - if (kNow >= it->second.timeoutDeadline) + // check if any method call has now expired + std::unique_lock locker(m_lock); + + // remove the method calls that have expired + const auto kNow = std::chrono::steady_clock::now(); + auto it = m_methodCalls.begin(); + while (it != m_methodCalls.end()) { - timedOuts.emplace_back(it->second); - it = m_methodCalls.erase(it); + if (kNow >= it->second.timeoutDeadline) + { + timedOuts.emplace_back(it->second); + it = m_methodCalls.erase(it); + } + else + { + ++it; + } } - else + + // if we still have method calls available, then re-calculate the timer for the next timeout + if (!m_methodCalls.empty()) { - ++it; + updateTimeoutTimer(); } } - // if we still have method calls available, then re-calculate the timer for the next timeout - if (!m_methodCalls.empty()) - { - updateTimeoutTimer(); - } - - // drop the lock and now terminate the timed out method calls - locker.unlock(); - for (auto &call : timedOuts) { completeWithError(&call, "Timed out"); @@ -718,28 +717,25 @@ void ChannelImpl::processReplyFromServer(const transport::MethodCallReply &reply { RIALTO_IPC_LOG_DEBUG("processing reply from server"); - std::unique_lock locker(m_lock); - - // find the original request + MethodCall methodCall; const uint64_t kSerialId = reply.reply_id(); - auto it = m_methodCalls.find(kSerialId); - if (it == m_methodCalls.end()) + { - RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); - return; - } + std::lock_guard locker(m_lock); - // take the method call and remove from the map of outstanding calls - MethodCall methodCall = it->second; - m_methodCalls.erase(it); + auto it = m_methodCalls.find(kSerialId); + if (it == m_methodCalls.end()) + { + RIALTO_IPC_LOG_ERROR("failed to find request for received reply with id %" PRIu64 "", reply.reply_id()); + return; + } - // update the timeout timer now a method call has been processed - updateTimeoutTimer(); + methodCall = it->second; + m_methodCalls.erase(it); - // can now drop the lock - locker.unlock(); + updateTimeoutTimer(); + } - // this is an actual reply so try and read it if (!methodCall.response->ParseFromString(reply.reply_message())) { RIALTO_IPC_LOG_ERROR("failed to parse method reply from server"); diff --git a/ipc/server/source/IpcServerImpl.cpp b/ipc/server/source/IpcServerImpl.cpp index 6536c704a..dea990eb9 100644 --- a/ipc/server/source/IpcServerImpl.cpp +++ b/ipc/server/source/IpcServerImpl.cpp @@ -1303,9 +1303,10 @@ bool ServerImpl::isClientConnected(uint64_t clientId) const */ void ServerImpl::disconnectClient(uint64_t clientId) { - std::unique_lock locker(m_clientsLock); - m_condemnedClients.insert(clientId); - locker.unlock(); + { + std::lock_guard locker(m_clientsLock); + m_condemnedClients.insert(clientId); + } wakeEventLoop(); } diff --git a/media/client/ipc/include/MediaPipelineIpc.h b/media/client/ipc/include/MediaPipelineIpc.h index db45d00d9..759dcc086 100644 --- a/media/client/ipc/include/MediaPipelineIpc.h +++ b/media/client/ipc/include/MediaPipelineIpc.h @@ -93,6 +93,10 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -144,6 +148,8 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + private: /** * @brief The media player client ipc. diff --git a/media/client/ipc/interface/IMediaPipelineIpc.h b/media/client/ipc/interface/IMediaPipelineIpc.h index 782a1931b..f5dfc44b2 100644 --- a/media/client/ipc/interface/IMediaPipelineIpc.h +++ b/media/client/ipc/interface/IMediaPipelineIpc.h @@ -188,6 +188,30 @@ class IMediaPipelineIpc */ virtual bool setImmediateOutput(int32_t sourceId, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous, it will set the "Report Decode Errors" property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * This method is synchronous, it gets the queued frames property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[out] queuedFrames : Get queued frames on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * @@ -453,6 +477,17 @@ class IMediaPipelineIpc * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto::client diff --git a/media/client/ipc/source/MediaPipelineIpc.cpp b/media/client/ipc/source/MediaPipelineIpc.cpp index ff1b09847..0c8f22a2f 100644 --- a/media/client/ipc/source/MediaPipelineIpc.cpp +++ b/media/client/ipc/source/MediaPipelineIpc.cpp @@ -535,6 +535,37 @@ bool MediaPipelineIpc::getPosition(int64_t &position) return true; } +bool MediaPipelineIpc::getDuration(int64_t &duration) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetDurationRequest request; + + request.set_session_id(m_sessionId); + + firebolt::rialto::GetDurationResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->getDuration(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get duration due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + duration = response.duration(); + return true; +} + bool MediaPipelineIpc::setImmediateOutput(int32_t sourceId, bool immediateOutput) { if (!reattachChannelIfRequired()) @@ -567,6 +598,73 @@ bool MediaPipelineIpc::setImmediateOutput(int32_t sourceId, bool immediateOutput return true; } +bool MediaPipelineIpc::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::SetReportDecodeErrorsRequest request; + + request.set_session_id(m_sessionId); + request.set_source_id(sourceId); + request.set_report_decode_errors(reportDecodeErrors); + + firebolt::rialto::SetReportDecodeErrorsResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->setReportDecodeErrors(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to set report decode error due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + + return true; +} + +bool MediaPipelineIpc::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + if (!reattachChannelIfRequired()) + { + RIALTO_CLIENT_LOG_ERROR("Reattachment of the ipc channel failed, ipc disconnected"); + return false; + } + + firebolt::rialto::GetQueuedFramesRequest request; + + request.set_session_id(m_sessionId); + request.set_source_id(sourceId); + + firebolt::rialto::GetQueuedFramesResponse response; + auto ipcController = m_ipc.createRpcController(); + auto blockingClosure = m_ipc.createBlockingClosure(); + m_mediaPipelineStub->getQueuedFrames(ipcController.get(), &request, &response, blockingClosure.get()); + + // wait for the call to complete + blockingClosure->wait(); + + // check the result + if (ipcController->Failed()) + { + RIALTO_CLIENT_LOG_ERROR("failed to get queued frames due to '%s'", ipcController->ErrorText().c_str()); + return false; + } + else + { + queuedFrames = response.queued_frames(); + } + + return true; +} + bool MediaPipelineIpc::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { if (!reattachChannelIfRequired()) diff --git a/media/client/main/include/MediaPipeline.h b/media/client/main/include/MediaPipeline.h index 13075a511..34c08b982 100644 --- a/media/client/main/include/MediaPipeline.h +++ b/media/client/main/include/MediaPipeline.h @@ -132,6 +132,10 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -188,6 +192,8 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel bool getStreamSyncMode(int32_t &streamSyncMode) override; + bool getDuration(int64_t &duration) override; + bool flush(int32_t sourceId, bool resetTime, bool &async) override; bool setSourcePosition(int32_t sourceId, int64_t position, bool resetTime, double appliedRate, diff --git a/media/client/main/include/MediaPipelineProxy.h b/media/client/main/include/MediaPipelineProxy.h index b38371963..992a53e53 100644 --- a/media/client/main/include/MediaPipelineProxy.h +++ b/media/client/main/include/MediaPipelineProxy.h @@ -68,6 +68,14 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient { return m_mediaPipeline->setImmediateOutput(sourceId, immediateOutput); } + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) + { + return m_mediaPipeline->setReportDecodeErrors(sourceId, reportDecodeErrors); + } + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) + { + return m_mediaPipeline->getQueuedFrames(sourceId, queuedFrames); + } bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) { return m_mediaPipeline->getImmediateOutput(sourceId, immediateOutput); @@ -174,6 +182,8 @@ class MediaPipelineProxy : public IMediaPipelineAndIControlClient return m_mediaPipeline->switchSource(source); } + bool getDuration(int64_t &duration) override { return m_mediaPipeline->getDuration(duration); } + void notifyApplicationState(ApplicationState state) override { m_mediaPipeline->notifyApplicationState(state); } private: diff --git a/media/client/main/source/MediaPipeline.cpp b/media/client/main/source/MediaPipeline.cpp index 8e26a036f..d7d3c11a3 100644 --- a/media/client/main/source/MediaPipeline.cpp +++ b/media/client/main/source/MediaPipeline.cpp @@ -313,6 +313,16 @@ bool MediaPipeline::setImmediateOutput(int32_t sourceId, bool immediateOutput) return m_mediaPipelineIpc->setImmediateOutput(sourceId, immediateOutput); } +bool MediaPipeline::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + return m_mediaPipelineIpc->setReportDecodeErrors(sourceId, reportDecodeErrors); +} + +bool MediaPipeline::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + return m_mediaPipelineIpc->getQueuedFrames(sourceId, queuedFrames); +} + bool MediaPipeline::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { return m_mediaPipelineIpc->getImmediateOutput(sourceId, immediateOutput); @@ -626,6 +636,13 @@ bool MediaPipeline::switchSource(const std::unique_ptr &source) return m_mediaPipelineIpc->switchSource(source); } +bool MediaPipeline::getDuration(int64_t &duration) +{ + RIALTO_CLIENT_LOG_DEBUG("entry:"); + + return m_mediaPipelineIpc->getDuration(duration); +} + void MediaPipeline::discardNeedDataRequest(uint32_t needDataRequestId) { // Find the needDataRequest for this needDataRequestId diff --git a/media/public/include/IMediaPipeline.h b/media/public/include/IMediaPipeline.h index e6c68787e..ea04e474a 100644 --- a/media/public/include/IMediaPipeline.h +++ b/media/public/include/IMediaPipeline.h @@ -1227,6 +1227,30 @@ class IMediaPipeline */ virtual bool setImmediateOutput(int32_t sourceId, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous, it will set the "Report Decode Errors" property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * This method is synchronous, it gets the queued frames property + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[out] queuedFrames : Get queued frames on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * @@ -1534,6 +1558,17 @@ class IMediaPipeline * @retval true on success. */ virtual bool switchSource(const std::unique_ptr &source) = 0; + + /** + * @brief Get the playback duration in nanoseconds. + * + * This method is synchronous, it returns current playback duration + * + * @param[out] duration : The playback duration in nanoseconds + * + * @retval true on success. + */ + virtual bool getDuration(int64_t &duration) = 0; }; }; // namespace firebolt::rialto diff --git a/media/server/gstplayer/CMakeLists.txt b/media/server/gstplayer/CMakeLists.txt index 6756184da..58278418a 100644 --- a/media/server/gstplayer/CMakeLists.txt +++ b/media/server/gstplayer/CMakeLists.txt @@ -57,6 +57,7 @@ add_library( source/tasks/generic/SetMute.cpp source/tasks/generic/SetPlaybackRate.cpp source/tasks/generic/SetPosition.cpp + source/tasks/generic/SetReportDecodeErrors.cpp source/tasks/generic/SetSourcePosition.cpp source/tasks/generic/SetSubtitleOffset.cpp source/tasks/generic/SetStreamSyncMode.cpp diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index d97deb4b1..237646ca3 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -155,6 +155,11 @@ struct GenericPlayerContext */ std::optional pendingImmediateOutputForVideo{}; + /** + * @brief Pending report decode errors for MediaSourceType::VIDEO + */ + std::optional pendingReportDecodeErrorsForVideo{}; + /** * @brief Pending low latency */ diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 54476f2ca..93e163f5a 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -118,8 +118,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void setEos(const firebolt::rialto::MediaSourceType &type) override; void setPlaybackRate(double rate) override; bool getPosition(std::int64_t &position) override; + bool getDuration(std::int64_t &duration) override; bool setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutput) override; + bool setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) override; bool getImmediateOutput(const MediaSourceType &mediaSourceType, bool &immediateOutput) override; + bool getQueuedFrames(uint32_t &queuedFrames) override; bool getStats(const MediaSourceType &mediaSourceType, uint64_t &renderedFrames, uint64_t &droppedFrames) override; void setVolume(double targetVolume, uint32_t volumeDuration, firebolt::rialto::EaseType easeType) override; bool getVolume(double &volume) override; @@ -153,6 +156,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void scheduleAllSourcesAttached() override; bool setVideoSinkRectangle() override; bool setImmediateOutput() override; + bool setReportDecodeErrors() override; bool setShowVideoWindow() override; bool setLowLatency() override; bool setSync() override; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index fb48a2816..9b6d54af0 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -80,6 +80,13 @@ class IGstGenericPlayerPrivate */ virtual bool setImmediateOutput() = 0; + /** + * @brief Sets report decode error. Called by the worker thread. + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors() = 0; + /** * @brief Sets the low latency property. Called by the worker thread. * diff --git a/media/server/gstplayer/include/Utils.h b/media/server/gstplayer/include/Utils.h index 81037266d..7351ec903 100644 --- a/media/server/gstplayer/include/Utils.h +++ b/media/server/gstplayer/include/Utils.h @@ -32,6 +32,7 @@ namespace firebolt::rialto::server bool isVideoDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioDecoder(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isAudioSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); bool isSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element); diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index f8de67cb4..54b8fc621 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -482,6 +482,21 @@ class IGenericPlayerTaskFactory const firebolt::rialto::MediaSourceType &type, bool immediateOutput) const = 0; + /** + * @brief Creates a SetReportDecodeErrors task. + * + * @param[in] context : The GstPlayer context + * @param[in] player : The GstPlayer instance + * @param[in] type : The media source type + * @param[in] reportDecodeErrors : the value to set for report decode error + * + * @retval the new SetReportDecodeErrors task instance. + */ + virtual std::unique_ptr createSetReportDecodeErrors(GenericPlayerContext &context, + IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const = 0; + /** * @brief Creates a SetBufferingLimit task. * diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index aa9a06e55..79e31838b 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -120,6 +120,10 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory std::unique_ptr createSetImmediateOutput(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, bool immediateOutput) const override; + std::unique_ptr createSetReportDecodeErrors(GenericPlayerContext &context, + IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const override; std::unique_ptr createSetBufferingLimit(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, std::uint32_t limit) const override; std::unique_ptr createSetUseBuffering(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, diff --git a/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h b/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h new file mode 100644 index 000000000..9cf30b310 --- /dev/null +++ b/media/server/gstplayer/include/tasks/generic/SetReportDecodeErrors.h @@ -0,0 +1,49 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ +#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ + +#include "GenericPlayerContext.h" +#include "IGlibWrapper.h" +#include "IGstGenericPlayerPrivate.h" +#include "IGstWrapper.h" +#include "IPlayerTask.h" + +#include + +namespace firebolt::rialto::server::tasks::generic +{ +class SetReportDecodeErrors : public IPlayerTask +{ +public: + explicit SetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const MediaSourceType &type, bool reportDecodeErrors); + ~SetReportDecodeErrors() override; + void execute() const override; + +private: + GenericPlayerContext &m_context; + IGstGenericPlayerPrivate &m_player; + const MediaSourceType m_type; + bool m_reportDecodeErrors; +}; +} // namespace firebolt::rialto::server::tasks::generic + +#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_SET_REPORT_DECODE_ERRORS_H_ diff --git a/media/server/gstplayer/interface/IGstGenericPlayer.h b/media/server/gstplayer/interface/IGstGenericPlayer.h index 5e6842926..3f1c877e7 100644 --- a/media/server/gstplayer/interface/IGstGenericPlayer.h +++ b/media/server/gstplayer/interface/IGstGenericPlayer.h @@ -186,6 +186,15 @@ class IGstGenericPlayer */ virtual bool getPosition(std::int64_t &position) = 0; + /** + * @brief Get the playback duration in nanoseconds. + * + * @param[out] duration : The playback duration in nanoseconds. + * + * @retval True on success + */ + virtual bool getDuration(std::int64_t &duration) = 0; + /** * @brief Sets the "Immediate Output" property for this source. * @@ -196,6 +205,25 @@ class IGstGenericPlayer */ virtual bool setImmediateOutput(const MediaSourceType &mediaSourceType, bool immediateOutput) = 0; + /** + * @brief Sets the "Report Decode Error" property for this source. + * + * @param[in] mediaSourceType : The media source type + * @param[in] reportDecodeErrors : Set report decode error + * + * @retval true on success. + */ + virtual bool setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) = 0; + + /** + * @brief Gets the queued frames for this source. + * + * @param[out] queuedFrames : Get queued frames mode on the decoder + * + * @retval true on success. + */ + virtual bool getQueuedFrames(uint32_t &queuedFrames) = 0; + /** * @brief Gets the "Immediate Output" property for this source. * diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 8ada9b7a9..3336f7d40 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -301,6 +301,11 @@ void GstGenericPlayer::termPipeline() m_gstWrapper->gstObjectUnref(m_context.videoSink); m_context.videoSink = nullptr; } + if (m_context.playbackGroup.m_curAudioPlaysinkBin) + { + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); + m_context.playbackGroup.m_curAudioPlaysinkBin = nullptr; + } // Delete the pipeline m_gstWrapper->gstObjectUnref(m_context.pipeline); @@ -406,6 +411,18 @@ bool GstGenericPlayer::getPosition(std::int64_t &position) return true; } +bool GstGenericPlayer::getDuration(std::int64_t &duration) +{ + // We are on main thread here, but m_context.pipeline can be used, because it's modified only in GstGenericPlayer + // constructor and destructor. GstGenericPlayer is created/destructed on main thread, so we won't have a crash here. + if (!m_context.pipeline || !m_gstWrapper->gstElementQueryDuration(m_context.pipeline, GST_FORMAT_TIME, &duration)) + { + RIALTO_SERVER_LOG_WARN("Failed to query duration"); + return false; + } + return true; +} + GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) const { const char *kSinkName{nullptr}; @@ -1094,6 +1111,41 @@ bool GstGenericPlayer::setImmediateOutput(const MediaSourceType &mediaSourceType return true; } +bool GstGenericPlayer::setReportDecodeErrors(const MediaSourceType &mediaSourceType, bool reportDecodeErrors) +{ + if (!m_workerThread) + return false; + + m_workerThread->enqueueTask( + m_taskFactory->createSetReportDecodeErrors(m_context, *this, mediaSourceType, reportDecodeErrors)); + return true; +} + +bool GstGenericPlayer::getQueuedFrames(uint32_t &queuedFrames) +{ + bool returnValue{false}; + GstElement *decoder{getDecoder(MediaSourceType::VIDEO)}; + if (decoder) + { + if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(decoder), "queued-frames")) + { + m_glibWrapper->gObjectGet(decoder, "queued-frames", &queuedFrames, nullptr); + returnValue = true; + } + else + { + RIALTO_SERVER_LOG_ERROR("queued-frames not supported in element %s", GST_ELEMENT_NAME(decoder)); + } + m_gstWrapper->gstObjectUnref(decoder); + } + else + { + RIALTO_SERVER_LOG_ERROR("Failed to get queued-frames property, decoder is NULL"); + } + + return returnValue; +} + bool GstGenericPlayer::getImmediateOutput(const MediaSourceType &mediaSourceType, bool &immediateOutputRef) { bool returnValue{false}; @@ -1833,6 +1885,51 @@ bool GstGenericPlayer::setImmediateOutput() return result; } +bool GstGenericPlayer::setReportDecodeErrors() +{ + bool result{false}; + bool reportDecodeErrors{false}; + + { + std::unique_lock lock{m_context.propertyMutex}; + if (!m_context.pendingReportDecodeErrorsForVideo.has_value()) + { + return false; + } + reportDecodeErrors = m_context.pendingReportDecodeErrorsForVideo.value(); + } + + GstElement *decoder = getDecoder(MediaSourceType::VIDEO); + if (decoder) + { + RIALTO_SERVER_LOG_DEBUG("Set report decode errors to %s", reportDecodeErrors ? "TRUE" : "FALSE"); + + if (m_glibWrapper->gObjectClassFindProperty(G_OBJECT_GET_CLASS(decoder), "report-decode-errors")) + { + gboolean reportDecodeErrorsGboolean{reportDecodeErrors ? TRUE : FALSE}; + m_glibWrapper->gObjectSet(decoder, "report-decode-errors", reportDecodeErrorsGboolean, nullptr); + result = true; + } + else + { + RIALTO_SERVER_LOG_ERROR("Failed to set report-decode-errors property on decoder '%s'", + GST_ELEMENT_NAME(decoder)); + } + + m_gstWrapper->gstObjectUnref(decoder); + + { + std::unique_lock lock{m_context.propertyMutex}; + m_context.pendingReportDecodeErrorsForVideo.reset(); + } + } + else + { + RIALTO_SERVER_LOG_DEBUG("Pending report-decode-errors, decoder is NULL"); + } + return result; +} + bool GstGenericPlayer::setShowVideoWindow() { if (!m_context.pendingShowVideoWindow.has_value()) diff --git a/media/server/gstplayer/source/Utils.cpp b/media/server/gstplayer/source/Utils.cpp index 243aa060d..520778de4 100644 --- a/media/server/gstplayer/source/Utils.cpp +++ b/media/server/gstplayer/source/Utils.cpp @@ -64,6 +64,11 @@ bool isVideoParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, Gs return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); } +bool isAudioParser(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) +{ + return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO); +} + bool isVideoSink(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element) { return isType(gstWrapper, element, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO); diff --git a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp index 83e6bef12..4eb337ead 100644 --- a/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp +++ b/media/server/gstplayer/source/tasks/generic/DeepElementAdded.cpp @@ -19,6 +19,7 @@ #include "tasks/generic/DeepElementAdded.h" #include "RialtoServerLogging.h" +#include "Utils.h" namespace { @@ -69,42 +70,48 @@ void DeepElementAdded::execute() const m_context.playbackGroup.m_gstPipeline = GST_ELEMENT(m_pipeline); RIALTO_SERVER_LOG_DEBUG("Element = %p Bin = %p Pipeline = %p", m_element, m_bin, m_pipeline); - if (m_callbackRegistered) - { - m_context.playbackGroup.m_curAudioTypefind = m_element; - } if (m_elementName) { if (m_gstWrapper->gstObjectCast(m_bin) == m_gstWrapper->gstObjectCast(m_context.playbackGroup.m_curAudioDecodeBin)) { - if (m_glibWrapper->gStrrstr(m_elementName, "parse")) + if (isAudioParser(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioParse = %s", m_elementName); m_context.playbackGroup.m_curAudioParse = m_element; } - else if (m_glibWrapper->gStrrstr(m_elementName, "dec")) + else if (isAudioDecoder(*m_gstWrapper, m_element)) { RIALTO_SERVER_LOG_DEBUG("curAudioDecoder = %s", m_elementName); m_context.playbackGroup.m_curAudioDecoder = m_element; } + else if (m_callbackRegistered && m_glibWrapper->gStrrstr(m_elementName, "typefind")) + { + RIALTO_SERVER_LOG_DEBUG("curAudioTypefind = %s", m_elementName); + m_context.playbackGroup.m_curAudioTypefind = m_element; + } } - else + else if (isAudioSink(*m_gstWrapper, m_element)) { - if (m_glibWrapper->gStrrstr(m_elementName, "audiosink")) + GstElement *audioSinkParent = reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); + if (audioSinkParent) { - GstElement *audioSinkParent = - reinterpret_cast(m_gstWrapper->gstElementGetParent(m_element)); - if (audioSinkParent) + gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); + RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); + if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) { - gchar *audioSinkParentName = m_gstWrapper->gstElementGetName(audioSinkParent); - RIALTO_SERVER_LOG_DEBUG("audioSinkParentName = %s", audioSinkParentName); - if (audioSinkParentName && m_glibWrapper->gStrrstr(audioSinkParentName, "bin")) + RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); + if (m_context.playbackGroup.m_curAudioPlaysinkBin) { - RIALTO_SERVER_LOG_DEBUG("curAudioPlaysinkBin = %s", audioSinkParentName); - m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + // Unref previous audio playsink bin, if exists + m_gstWrapper->gstObjectUnref(m_context.playbackGroup.m_curAudioPlaysinkBin); } - m_glibWrapper->gFree(audioSinkParentName); + m_context.playbackGroup.m_curAudioPlaysinkBin = audioSinkParent; + } + else + { + m_gstWrapper->gstObjectUnref(audioSinkParent); } + m_glibWrapper->gFree(audioSinkParentName); } } } diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index 318f04379..a7886a335 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -41,6 +41,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSubtitleOffset.h" @@ -326,6 +327,14 @@ GenericPlayerTaskFactory::createSetImmediateOutput(GenericPlayerContext &context return std::make_unique(context, player, type, immediateOutput); } +std::unique_ptr +GenericPlayerTaskFactory::createSetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, + bool reportDecodeErrors) const +{ + return std::make_unique(context, player, type, reportDecodeErrors); +} + std::unique_ptr GenericPlayerTaskFactory::createSetBufferingLimit(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, std::uint32_t limit) const diff --git a/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp b/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp new file mode 100644 index 000000000..6815df0ce --- /dev/null +++ b/media/server/gstplayer/source/tasks/generic/SetReportDecodeErrors.cpp @@ -0,0 +1,52 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SetReportDecodeErrors.h" +#include "RialtoServerLogging.h" +#include "TypeConverters.h" + +namespace firebolt::rialto::server::tasks::generic +{ +SetReportDecodeErrors::SetReportDecodeErrors(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, + const MediaSourceType &type, bool reportDecodeErrors) + : m_context{context}, m_player(player), m_type{type}, m_reportDecodeErrors{reportDecodeErrors} +{ + RIALTO_SERVER_LOG_DEBUG("Constructing SetReportDecodeErrors"); +} + +SetReportDecodeErrors::~SetReportDecodeErrors() +{ + RIALTO_SERVER_LOG_DEBUG("SetReportDecodeErrors finished"); +} + +void SetReportDecodeErrors::execute() const +{ + RIALTO_SERVER_LOG_DEBUG("Executing SetReportDecodeErrors for %s source", common::convertMediaSourceType(m_type)); + + m_context.pendingReportDecodeErrorsForVideo = m_reportDecodeErrors; + + if (!m_context.pipeline) + { + RIALTO_SERVER_LOG_WARN("Pipeline not available yet - cannot apply report-decode-errors setting"); + return; + } + + m_player.setReportDecodeErrors(); +} +} // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index 16484c9a6..00541a00e 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -246,6 +246,10 @@ void SetupElement::execute() const RIALTO_SERVER_LOG_INFO("Setting video decoder handle for subtitle sink: %p", m_element); m_context.isVideoHandleSet = true; } + if (m_context.pendingReportDecodeErrorsForVideo.has_value()) + { + m_player.setReportDecodeErrors(); + } } } @@ -333,7 +337,6 @@ void SetupElement::execute() const { m_gstWrapper->gstBaseParseSetPtsInterpolation(GST_BASE_PARSE(m_element), FALSE); } - m_gstWrapper->gstObjectUnref(m_element); } } // namespace firebolt::rialto::server::tasks::generic diff --git a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp index 5946d6899..489ccf047 100644 --- a/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp +++ b/media/server/gstplayer/source/tasks/generic/UpdatePlaybackGroup.cpp @@ -68,6 +68,20 @@ void UpdatePlaybackGroup::execute() const { m_player.setUseBuffering(); } + if (m_context.playbackGroup.m_linkTypefindParser && m_context.playbackGroup.m_curAudioTypefind && + m_context.playbackGroup.m_curAudioParse) + { + if (m_gstWrapper->gstElementLink(m_context.playbackGroup.m_curAudioTypefind, + m_context.playbackGroup.m_curAudioParse)) + { + RIALTO_SERVER_LOG_DEBUG("Linked typefind to parser"); + m_context.playbackGroup.m_linkTypefindParser = false; + } + else + { + RIALTO_SERVER_LOG_DEBUG("Failed to link typefind to parser"); + } + } } m_glibWrapper->gFree(elementName); m_gstWrapper->gstObjectUnref(typeFindParent); diff --git a/media/server/ipc/include/MediaPipelineModuleService.h b/media/server/ipc/include/MediaPipelineModuleService.h index f0321827b..91787419a 100644 --- a/media/server/ipc/include/MediaPipelineModuleService.h +++ b/media/server/ipc/include/MediaPipelineModuleService.h @@ -84,10 +84,20 @@ class MediaPipelineModuleService : public IMediaPipelineModuleService ::google::protobuf::Closure *done) override; void getPosition(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done) override; + void getDuration(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done) override; void setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, ::google::protobuf::Closure *done) override; + void setReportDecodeErrors(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, + ::google::protobuf::Closure *done) override; + void getQueuedFrames(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, + ::google::protobuf::Closure *done) override; void getImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, diff --git a/media/server/ipc/source/MediaPipelineModuleService.cpp b/media/server/ipc/source/MediaPipelineModuleService.cpp index 3853f6605..a05f52576 100644 --- a/media/server/ipc/source/MediaPipelineModuleService.cpp +++ b/media/server/ipc/source/MediaPipelineModuleService.cpp @@ -665,6 +665,25 @@ void MediaPipelineModuleService::getPosition(::google::protobuf::RpcController * done->Run(); } +void MediaPipelineModuleService::getDuration(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + int64_t duration{}; + if (!m_mediaPipelineService.getDuration(request->session_id(), duration)) + { + RIALTO_SERVER_LOG_ERROR("Get duration failed"); + controller->SetFailed("Operation failed"); + } + else + { + response->set_duration(duration); + } + done->Run(); +} + void MediaPipelineModuleService::setImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, ::firebolt::rialto::SetImmediateOutputResponse *response, @@ -680,6 +699,40 @@ void MediaPipelineModuleService::setImmediateOutput(::google::protobuf::RpcContr done->Run(); } +void MediaPipelineModuleService::setReportDecodeErrors(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + if (!m_mediaPipelineService.setReportDecodeErrors(request->session_id(), request->source_id(), + request->report_decode_errors())) + { + RIALTO_SERVER_LOG_ERROR("Set Report Decode Error failed"); + controller->SetFailed("Operation failed"); + } + done->Run(); +} + +void MediaPipelineModuleService::getQueuedFrames(::google::protobuf::RpcController *controller, + const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, + ::google::protobuf::Closure *done) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + uint32_t queuedFramesNumber; + if (!m_mediaPipelineService.getQueuedFrames(request->session_id(), request->source_id(), queuedFramesNumber)) + { + RIALTO_SERVER_LOG_ERROR("Get queued frames failed"); + controller->SetFailed("Operation failed"); + } + else + { + response->set_queued_frames(queuedFramesNumber); + } + done->Run(); +} + void MediaPipelineModuleService::getImmediateOutput(::google::protobuf::RpcController *controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, diff --git a/media/server/main/include/ActiveRequests.h b/media/server/main/include/ActiveRequests.h index b90ef73a7..8e4af1644 100644 --- a/media/server/main/include/ActiveRequests.h +++ b/media/server/main/include/ActiveRequests.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace firebolt::rialto::server { @@ -54,6 +55,7 @@ class ActiveRequests : public IActiveRequests std::uint32_t m_bytesWritten; std::uint32_t m_maxMediaBytes; IMediaPipeline::MediaSegmentVector m_segments; + std::vector> m_segmentBuffers; }; ActiveRequests(); diff --git a/media/server/main/include/MediaPipelineServerInternal.h b/media/server/main/include/MediaPipelineServerInternal.h index 0c92f63c0..5df68e454 100644 --- a/media/server/main/include/MediaPipelineServerInternal.h +++ b/media/server/main/include/MediaPipelineServerInternal.h @@ -114,6 +114,10 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool setImmediateOutput(int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) override; + + bool getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) override; + bool getImmediateOutput(int32_t sourceId, bool &immediateOutput) override; bool getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; @@ -171,6 +175,8 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public bool switchSource(const std::unique_ptr &source) override; + bool getDuration(int64_t &duration) override; + AddSegmentStatus addSegment(uint32_t needDataRequestId, const std::unique_ptr &mediaSegment) override; std::weak_ptr getClient() override; @@ -397,6 +403,30 @@ class MediaPipelineServerInternal : public IMediaPipelineServerInternal, public */ bool setImmediateOutputInternal(int32_t sourceId, bool immediateOutput); + /** + * @brief Sets the "Report Decode Errors" property for this source. + * + * This method is asynchronous + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] reportDecodeErrors : The desired Set Report Decode Errors mode on the sink + * + * @retval true on success. + */ + bool setReportDecodeErrorsInternal(int32_t sourceId, bool reportDecodeErrors); + + /** + * @brief Gets the queued frames for this source. + * + * This method is asynchronous + * + * @param[in] sourceId : The source id. Value should be set to the MediaSource.id returned after attachSource() + * @param[in] queuedFrames : Number of queued frames + * + * @retval true on success. + */ + bool getQueuedFramesInternal(int32_t sourceId, uint32_t &queuedFrames); + /** * @brief Gets the "Immediate Output" property for this source. * diff --git a/media/server/main/source/ActiveRequests.cpp b/media/server/main/source/ActiveRequests.cpp index 3431fc672..343adcd1c 100644 --- a/media/server/main/source/ActiveRequests.cpp +++ b/media/server/main/source/ActiveRequests.cpp @@ -18,18 +18,14 @@ */ #include "ActiveRequests.h" +#include "RialtoServerLogging.h" #include +#include #include namespace firebolt::rialto::server { -ActiveRequests::ActiveRequestsData::~ActiveRequestsData() -{ - for (std::unique_ptr &segment : m_segments) - { - delete[] segment->getData(); - } -} +ActiveRequests::ActiveRequestsData::~ActiveRequestsData() {} AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::unique_ptr &segment) { @@ -38,10 +34,10 @@ AddSegmentStatus ActiveRequests::ActiveRequestsData::addSegment(const std::uniqu std::unique_ptr copiedSegment = segment->copy(); - uint8_t *data = new uint8_t[segment->getDataLength()]; - std::memcpy(data, segment->getData(), segment->getDataLength()); + m_segmentBuffers.emplace_back(segment->getDataLength()); + std::memcpy(m_segmentBuffers.back().data(), segment->getData(), segment->getDataLength()); - copiedSegment->setData(segment->getDataLength(), data); + copiedSegment->setData(m_segmentBuffers.back().size(), m_segmentBuffers.back().data()); m_segments.push_back(std::move(copiedSegment)); m_bytesWritten += segment->getDataLength(); @@ -53,7 +49,23 @@ ActiveRequests::ActiveRequests() : m_currentId{0} {} std::uint32_t ActiveRequests::insert(const MediaSourceType &mediaSourceType, std::uint32_t maxMediaBytes) { std::unique_lock lock{m_mutex}; - m_requestMap.insert(std::make_pair(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes))); + + if (m_currentId == std::numeric_limits::max()) + { + m_currentId = 1; + } + + auto [it, inserted] = m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); + if (!inserted) + { + do + { + ++m_currentId; + } while (m_requestMap.find(m_currentId) != m_requestMap.end()); + + m_requestMap.emplace(m_currentId, ActiveRequestsData(mediaSourceType, maxMediaBytes)); + } + return m_currentId++; } diff --git a/media/server/main/source/MediaPipelineServerInternal.cpp b/media/server/main/source/MediaPipelineServerInternal.cpp index 22fa5ca53..26bf4ceb3 100644 --- a/media/server/main/source/MediaPipelineServerInternal.cpp +++ b/media/server/main/source/MediaPipelineServerInternal.cpp @@ -299,7 +299,12 @@ bool MediaPipelineServerInternal::removeSourceInternal(int32_t id) return false; } - m_needMediaDataTimers.erase(sourceIter->first); + MediaSourceType type = sourceIter->first; + + m_needMediaDataTimers.erase(type); + m_noAvailableSamplesCounter.erase(type); + m_isMediaTypeEosMap.erase(type); + m_attachedSources.erase(sourceIter); return true; } @@ -476,6 +481,20 @@ bool MediaPipelineServerInternal::getPosition(int64_t &position) return m_gstPlayer->getPosition(position); } +bool MediaPipelineServerInternal::getDuration(int64_t &duration) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + std::shared_lock lock{m_getPropertyMutex}; + + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed to get duration - Gstreamer player has not been loaded"); + return false; + } + return m_gstPlayer->getDuration(duration); +} + bool MediaPipelineServerInternal::getStats(int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) { RIALTO_SERVER_LOG_DEBUG("entry:"); @@ -534,6 +553,63 @@ bool MediaPipelineServerInternal::setImmediateOutputInternal(int32_t sourceId, b return m_gstPlayer->setImmediateOutput(sourceIter->first, immediateOutput); } +bool MediaPipelineServerInternal::setReportDecodeErrors(int32_t sourceId, bool reportDecodeErrors) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + bool result; + auto task = [&]() { result = setReportDecodeErrorsInternal(sourceId, reportDecodeErrors); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; +} + +bool MediaPipelineServerInternal::setReportDecodeErrorsInternal(int32_t sourceId, bool reportDecodeErrors) +{ + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed - Gstreamer player has not been loaded"); + return false; + } + auto sourceIter = std::find_if(m_attachedSources.begin(), m_attachedSources.end(), + [sourceId](const auto &src) { return src.second == sourceId; }); + if (sourceIter == m_attachedSources.end()) + { + RIALTO_SERVER_LOG_ERROR("Failed - Source not found"); + return false; + } + + return m_gstPlayer->setReportDecodeErrors(sourceIter->first, reportDecodeErrors); +} + +bool MediaPipelineServerInternal::getQueuedFrames(int32_t sourceId, uint32_t &queuedFrames) +{ + RIALTO_SERVER_LOG_DEBUG("entry:"); + + bool result; + auto task = [&]() { result = getQueuedFramesInternal(sourceId, queuedFrames); }; + + m_mainThread->enqueueTaskAndWait(m_mainThreadClientId, task); + return result; +} + +bool MediaPipelineServerInternal::getQueuedFramesInternal(int32_t sourceId, uint32_t &queuedFrames) +{ + if (!m_gstPlayer) + { + RIALTO_SERVER_LOG_ERROR("Failed - Gstreamer player has not been loaded"); + return false; + } + auto sourceIter = std::find_if(m_attachedSources.begin(), m_attachedSources.end(), + [sourceId](const auto &src) { return src.second == sourceId; }); + if (sourceIter == m_attachedSources.end()) + { + RIALTO_SERVER_LOG_ERROR("Failed - Source not found"); + return false; + } + return m_gstPlayer->getQueuedFrames(queuedFrames); +} + bool MediaPipelineServerInternal::getImmediateOutput(int32_t sourceId, bool &immediateOutput) { RIALTO_SERVER_LOG_DEBUG("entry:"); diff --git a/media/server/service/include/IMediaPipelineService.h b/media/server/service/include/IMediaPipelineService.h index ae1fc5e04..c4658b097 100644 --- a/media/server/service/include/IMediaPipelineService.h +++ b/media/server/service/include/IMediaPipelineService.h @@ -55,6 +55,8 @@ class IMediaPipelineService virtual bool setPosition(int sessionId, std::int64_t position) = 0; virtual bool getPosition(int sessionId, std::int64_t &position) = 0; virtual bool setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) = 0; + virtual bool setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) = 0; + virtual bool getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) = 0; virtual bool getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) = 0; virtual bool getStats(int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) = 0; virtual bool setVideoWindow(int sessionId, std::uint32_t x, std::uint32_t y, std::uint32_t width, @@ -91,6 +93,7 @@ class IMediaPipelineService virtual void ping(const std::shared_ptr &heartbeatProcedure) = 0; virtual bool switchSource(int sessionId, const std::unique_ptr &source) = 0; virtual bool isVideoMaster(bool &isVideoMaster) = 0; + virtual bool getDuration(int sessionId, std::int64_t &duration) = 0; }; } // namespace firebolt::rialto::server::service diff --git a/media/server/service/source/MediaPipelineService.cpp b/media/server/service/source/MediaPipelineService.cpp index e977c296f..37e2ad34c 100644 --- a/media/server/service/source/MediaPipelineService.cpp +++ b/media/server/service/source/MediaPipelineService.cpp @@ -253,6 +253,20 @@ bool MediaPipelineService::getPosition(int sessionId, std::int64_t &position) return mediaPipelineIter->second->getPosition(position); } +bool MediaPipelineService::getDuration(int sessionId, std::int64_t &duration) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to get duration, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exist", sessionId); + return false; + } + return mediaPipelineIter->second->getDuration(duration); +} + bool MediaPipelineService::setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to setImmediateOutput, session id: %d", sessionId); @@ -267,6 +281,34 @@ bool MediaPipelineService::setImmediateOutput(int sessionId, int32_t sourceId, b return mediaPipelineIter->second->setImmediateOutput(sourceId, immediateOutput); } +bool MediaPipelineService::setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to setReportDecodeErrors, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); + return false; + } + return mediaPipelineIter->second->setReportDecodeErrors(sourceId, reportDecodeErrors); +} + +bool MediaPipelineService::getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) +{ + RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to getQueuedFrames, session id: %d", sessionId); + + std::lock_guard lock{m_mediaPipelineMutex}; + auto mediaPipelineIter = m_mediaPipelines.find(sessionId); + if (mediaPipelineIter == m_mediaPipelines.end()) + { + RIALTO_SERVER_LOG_ERROR("Session with id: %d does not exists", sessionId); + return false; + } + return mediaPipelineIter->second->getQueuedFrames(sourceId, queuedFrames); +} + bool MediaPipelineService::getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) { RIALTO_SERVER_LOG_INFO("MediaPipelineService requested to getImmediateOutput, session id: %d", sessionId); diff --git a/media/server/service/source/MediaPipelineService.h b/media/server/service/source/MediaPipelineService.h index bf9ec234f..1c03670e8 100644 --- a/media/server/service/source/MediaPipelineService.h +++ b/media/server/service/source/MediaPipelineService.h @@ -65,7 +65,10 @@ class MediaPipelineService : public IMediaPipelineService bool setPlaybackRate(int sessionId, double rate) override; bool setPosition(int sessionId, std::int64_t position) override; bool getPosition(int sessionId, std::int64_t &position) override; + bool getDuration(int sessionId, std::int64_t &duration) override; bool setImmediateOutput(int sessionId, int32_t sourceId, bool immediateOutput) override; + bool setReportDecodeErrors(int sessionId, int32_t sourceId, bool reportDecodeErrors) override; + bool getQueuedFrames(int sessionId, int32_t sourceId, uint32_t &queuedFrames) override; bool getImmediateOutput(int sessionId, int32_t sourceId, bool &immediateOutput) override; bool getStats(int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames) override; bool setVideoWindow(int sessionId, std::uint32_t x, std::uint32_t y, std::uint32_t width, diff --git a/proto/mediapipelinemodule.proto b/proto/mediapipelinemodule.proto index 1807810de..534343676 100644 --- a/proto/mediapipelinemodule.proto +++ b/proto/mediapipelinemodule.proto @@ -345,6 +345,22 @@ message GetPositionResponse { optional int64 position = 1 [default = -1]; } +/** + * @fn void getDuration(int session_id) + * @brief Get the playback Duration in nanoseconds. + * + * @param[in] session_id The id of the A/V session. + * + * This method is considered to be synchronous, it returns current playback duration + * + */ +message GetDurationRequest { + optional int32 session_id = 1 [default = -1]; +} +message GetDurationResponse { + optional int64 duration = 1 [default = -1]; +} + /** * @fn void setPlaybackRate(int session_id, double rate) * @brief Set the playback position in nanoseconds. @@ -678,6 +694,24 @@ message SetImmediateOutputRequest { message SetImmediateOutputResponse { } +/** + * @fn void setReportDecodeErrors(int session_id, int source_id, bool report_decode_errors) + * @brief Enables or disables decode error reporting for this source. + * + * @param[in] session_id The id of the A/V session. + * @param[in] source_id The id of the media source. + * @param[in] report_decode_errors Enable decode error reporting. + */ +message SetReportDecodeErrorsRequest { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; + optional bool report_decode_errors = 3 [default = false]; +} + +message SetReportDecodeErrorsResponse { +} + + /** * @fn void getImmediateOutput(int session_id, int source_id, bool &immediate_output) * @brief Gets the "Immediate Output" property for this source. @@ -695,6 +729,23 @@ message GetImmediateOutputResponse { optional bool immediate_output = 1 [default = false]; } +/** + * @fn void getQueuedFrames(int session_id, int source_id, uint32_t &queued_frames) + * @brief Gets the "Queued Frames" property for this source. + * + * @param[in] session_id The id of the A/V session. + * @param[in] source_id The id of the media source. + * @param[out] queued_frames The number of queued frames on the decoder + * + */ +message GetQueuedFramesRequest { + optional int32 session_id = 1 [default = -1]; + optional int32 source_id = 2 [default = -1]; +} +message GetQueuedFramesResponse { + optional uint32 queued_frames = 1 [default = 0]; +} + /** * @fn void getStats(int session_id, int source_id, uint64 &rendered_frames, uint64 &dropped_frames) * @brief Get various stats from the source. @@ -1078,6 +1129,14 @@ service MediaPipelineModule { rpc setImmediateOutput(SetImmediateOutputRequest) returns (SetImmediateOutputResponse) { } + /** + * @brief Sets the "Report Decode Errors" property for this source. + * @see SetReportDecodeErrorsRequest + */ + rpc setReportDecodeErrors(SetReportDecodeErrorsRequest) + returns (SetReportDecodeErrorsResponse) { + } + /** * @brief Gets the "Immediate Output" property for this source. * @see GetImmediateOutputRequest @@ -1085,6 +1144,13 @@ service MediaPipelineModule { rpc getImmediateOutput(GetImmediateOutputRequest) returns (GetImmediateOutputResponse) { } + /** + * @brief Gets the "Queued Frames" property for this source. + * @see GetQueuedFramesRequest + */ + rpc getQueuedFrames(GetQueuedFramesRequest) returns (GetQueuedFramesResponse) { + } + /** * @brief Gets the current stats property * @see GetStatsRequest @@ -1253,4 +1319,12 @@ service MediaPipelineModule { */ rpc getUseBuffering(GetUseBufferingRequest) returns (GetUseBufferingResponse) { } + + /** + * @brief Gets the current duration of the playback + * @see GetDurationRequest + */ + rpc getDuration(GetDurationRequest) returns (GetDurationResponse) { + } + } diff --git a/tests/common/externalLibraryMocks/GstWrapperMock.h b/tests/common/externalLibraryMocks/GstWrapperMock.h index 8d6d506ad..aff90722e 100644 --- a/tests/common/externalLibraryMocks/GstWrapperMock.h +++ b/tests/common/externalLibraryMocks/GstWrapperMock.h @@ -88,6 +88,7 @@ class GstWrapperMock : public IGstWrapper MOCK_METHOD(void, gstBusSetSyncHandler, (GstBus *, GstBusSyncHandler, gpointer, GDestroyNotify), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcEndOfStream, (GstAppSrc *), (override)); MOCK_METHOD(gboolean, gstElementQueryPosition, (GstElement *, GstFormat, gint64 *), (override)); + MOCK_METHOD(gboolean, gstElementQueryDuration, (GstElement *, GstFormat, gint64 *), (override)); MOCK_METHOD(GstFlowReturn, gstAppSrcPushBuffer, (GstAppSrc *, GstBuffer *), (override)); MOCK_METHOD(GstBuffer *, gstBufferNew, (), (override)); MOCK_METHOD(GstBuffer *, gstBufferNewAllocate, (GstAllocator *, gsize, GstAllocationParams *), (override)); diff --git a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h index 04c48ad78..c98b56c1a 100644 --- a/tests/common/matchers/MediaPipelineProtoRequestMatchers.h +++ b/tests/common/matchers/MediaPipelineProtoRequestMatchers.h @@ -352,6 +352,13 @@ MATCHER_P(getPositionRequestMatcher, sessionId, "") return ((kRequest->session_id() == sessionId)); } +MATCHER_P(getDurationRequestMatcher, sessionId, "") +{ + const ::firebolt::rialto::GetDurationRequest *kRequest = + dynamic_cast(arg); + return ((kRequest->session_id() == sessionId)); +} + MATCHER_P2(setImmediateOutputRequestMatcher, sessionId, sourceId, "") { const ::firebolt::rialto::SetImmediateOutputRequest *kRequest = @@ -366,6 +373,20 @@ MATCHER_P2(getImmediateOutputRequestMatcher, sessionId, sourceId, "") return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); } +MATCHER_P2(setReportDecodeErrorsRequestMatcher, sessionId, sourceId, "") +{ + const ::firebolt::rialto::SetReportDecodeErrorsRequest *kRequest = + dynamic_cast(arg); + return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); +} + +MATCHER_P2(getQueuedFramesRequestMatcher, sessionId, sourceId, "") +{ + const ::firebolt::rialto::GetQueuedFramesRequest *kRequest = + dynamic_cast(arg); + return (kRequest->session_id() == sessionId) && (kRequest->source_id() == sourceId); +} + MATCHER_P2(getStatsRequestMatcher, sessionId, sourceId, "") { const ::firebolt::rialto::GetStatsRequest *kRequest = dynamic_cast(arg); diff --git a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h index a6fc772c0..6988528f4 100644 --- a/tests/componenttests/client/mocks/MediaPipelineModuleMock.h +++ b/tests/componenttests/client/mocks/MediaPipelineModuleMock.h @@ -64,6 +64,9 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule MOCK_METHOD(void, getPosition, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetPositionRequest *request, ::firebolt::rialto::GetPositionResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getDuration, + (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetDurationRequest *request, + ::firebolt::rialto::GetDurationResponse *response, ::google::protobuf::Closure *done)); MOCK_METHOD(void, setImmediateOutput, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::SetImmediateOutputRequest *request, @@ -72,6 +75,13 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetImmediateOutputRequest *request, ::firebolt::rialto::GetImmediateOutputResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, setReportDecodeErrors, + (::google::protobuf::RpcController * controller, + const ::firebolt::rialto::SetReportDecodeErrorsRequest *request, + ::firebolt::rialto::SetReportDecodeErrorsResponse *response, ::google::protobuf::Closure *done)); + MOCK_METHOD(void, getQueuedFrames, + (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetQueuedFramesRequest *request, + ::firebolt::rialto::GetQueuedFramesResponse *response, ::google::protobuf::Closure *done)); MOCK_METHOD(void, getStats, (::google::protobuf::RpcController * controller, const ::firebolt::rialto::GetStatsRequest *request, ::firebolt::rialto::GetStatsResponse *response, ::google::protobuf::Closure *done)); @@ -218,6 +228,13 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule return response; } + ::firebolt::rialto::GetDurationResponse getDurationResponse(const int64_t duration) + { + firebolt::rialto::GetDurationResponse response; + response.set_duration(duration); + return response; + } + ::firebolt::rialto::SetImmediateOutputResponse setImmediateOutputResponse() { firebolt::rialto::SetImmediateOutputResponse response; @@ -231,6 +248,19 @@ class MediaPipelineModuleMock : public ::firebolt::rialto::MediaPipelineModule return response; } + ::firebolt::rialto::SetReportDecodeErrorsResponse SetReportDecodeErrorsResponse() + { + firebolt::rialto::SetReportDecodeErrorsResponse response; + return response; + } + + ::firebolt::rialto::GetQueuedFramesResponse getQueuedFramesResponse(uint32_t queuedFramesResponse) + { + firebolt::rialto::GetQueuedFramesResponse response; + response.set_queued_frames(queuedFramesResponse); + return response; + } + ::firebolt::rialto::GetStatsResponse getStatsResponse(const uint64_t renderedFrames, const uint64_t droppedFrames) { firebolt::rialto::GetStatsResponse response; diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index e91d67bbb..ca86e1963 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -1424,6 +1424,20 @@ void MediaPipelineTestMethods::getPosition(const int64_t expectedPosition) EXPECT_EQ(returnPosition, expectedPosition); } +void MediaPipelineTestMethods::shouldGetDuration(const int64_t duration) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, getDuration(_, getDurationRequestMatcher(kSessionId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->getDurationResponse(duration)), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::getDuration(const int64_t expectedDuration) +{ + int64_t returnDuration; + EXPECT_EQ(m_mediaPipeline->getDuration(returnDuration), true); + EXPECT_EQ(returnDuration, expectedDuration); +} + void MediaPipelineTestMethods::shouldSetImmediateOutput(bool immediateOutput) { EXPECT_CALL(*m_mediaPipelineModuleMock, @@ -1450,6 +1464,34 @@ void MediaPipelineTestMethods::getImmediateOutput(bool immediateOutput) EXPECT_TRUE(m_mediaPipeline->getImmediateOutput(kVideoSourceId, immediateOutput)); } +void MediaPipelineTestMethods::shouldSetReportDecodeErrors(bool reportDecodeErrors) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + setReportDecodeErrors(_, setReportDecodeErrorsRequestMatcher(kSessionId, kVideoSourceId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->SetReportDecodeErrorsResponse()), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::setReportDecodeErrors(bool reportDecodeErrors) +{ + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(kVideoSourceId, reportDecodeErrors)); +} + +void MediaPipelineTestMethods::shouldGetQueuedFrames(uint32_t queuedFrames) +{ + EXPECT_CALL(*m_mediaPipelineModuleMock, + getQueuedFrames(_, getQueuedFramesRequestMatcher(kSessionId, kVideoSourceId), _, _)) + .WillOnce(DoAll(SetArgPointee<2>(m_mediaPipelineModuleMock->getQueuedFramesResponse(queuedFrames)), + WithArgs<0, 3>(Invoke(&(*m_mediaPipelineModuleMock), &MediaPipelineModuleMock::defaultReturn)))); +} + +void MediaPipelineTestMethods::getQueuedFrames(uint32_t queuedFrames) +{ + uint32_t returnQueuedFrames; + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(kVideoSourceId, returnQueuedFrames)); + EXPECT_EQ(returnQueuedFrames, queuedFrames); +} + void MediaPipelineTestMethods::shouldGetStats(uint64_t renderedFrames, uint64_t droppedFrames) { EXPECT_CALL(*m_mediaPipelineModuleMock, getStats(_, getStatsRequestMatcher(kSessionId, kVideoSourceId), _, _)) diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index c6b9d7f74..a3a245fe6 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -128,8 +128,11 @@ class MediaPipelineTestMethods void shouldRenderFrame(); void shouldRenderFrameFailure(); void shouldGetPosition(const int64_t position); + void shouldGetDuration(const int64_t duration); void shouldSetImmediateOutput(bool immediateOutput); void shouldGetImmediateOutput(bool immediateOutput); + void shouldSetReportDecodeErrors(bool reportDecodeErrors); + void shouldGetQueuedFrames(uint32_t queuedFrames); void shouldGetStats(uint64_t renderedFrames, uint64_t droppedFrames); void shouldFlush(); void shouldFailToFlush(); @@ -246,8 +249,11 @@ class MediaPipelineTestMethods void renderFrame(); void renderFrameFailure(); void getPosition(const int64_t expectedPosition); + void getDuration(const int64_t expectedDuration); void setImmediateOutput(bool immediateOutput); void getImmediateOutput(bool immediateOutput); + void setReportDecodeErrors(bool reportDecodeErrors); + void getQueuedFrames(uint32_t queuedFrames); void getStats(uint64_t expectedFrames, uint64_t expectedDropped); void createMediaPipelineCapabilitiesObject(); void destroyMediaPipelineCapabilitiesObject(); diff --git a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp index a64aeadd7..dd6164c1f 100644 --- a/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp +++ b/tests/componenttests/client/tests/mse/PipelinePropertyTest.cpp @@ -108,6 +108,18 @@ class PipelinePropertyTest : public ClientComponentTest * GetUseBuffering * Expect that GetUseBuffering propagated to the server and gets the property * + * Step 13: Set Report Decode Errors + * Set report decode errors + * Expect that Set report decode errors propagated to the server and sets the property + * + * Step 14: Get Queued Frames + * GetQueuedFrames + * Expect that GetQueuedFrames propagated to the server and gets the number of queued frames + * + * Step 15: Get Duration + * GetDuration + * Expect that GetDuration propagated to the server and gets the duration + * * Test Teardown: * Terminate the media session. * Memory region created for the shared buffer is closed. @@ -174,5 +186,20 @@ TEST_F(PipelinePropertyTest, setAndGetPipelineProperties) // Step 12: Get UseBuffering MediaPipelineTestMethods::shouldGetUseBuffering(useBuffering); MediaPipelineTestMethods::getUseBuffering(useBuffering); + + // Step 13: Set Report Decode Errors + bool reportDecodeErrors{true}; + MediaPipelineTestMethods::shouldSetReportDecodeErrors(reportDecodeErrors); + MediaPipelineTestMethods::setReportDecodeErrors(reportDecodeErrors); + + // Step 14: Get Queued Frames + uint32_t queuedFrames{123}; + MediaPipelineTestMethods::shouldGetQueuedFrames(queuedFrames); + MediaPipelineTestMethods::getQueuedFrames(queuedFrames); + + // Step 15: Get Duration + constexpr int64_t duration{123456789}; + MediaPipelineTestMethods::shouldGetDuration(duration); + MediaPipelineTestMethods::getDuration(duration); } } // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/server/common/ActionTraits.h b/tests/componenttests/server/common/ActionTraits.h index d601e8d9f..e0326bb82 100644 --- a/tests/componenttests/server/common/ActionTraits.h +++ b/tests/componenttests/server/common/ActionTraits.h @@ -344,6 +344,14 @@ struct ProcessAudioGap static constexpr auto m_kFunction{&Stub::processAudioGap}; }; +struct GetDuration +{ + using RequestType = ::firebolt::rialto::GetDurationRequest; + using ResponseType = ::firebolt::rialto::GetDurationResponse; + using Stub = ::firebolt::rialto::MediaPipelineModule_Stub; + static constexpr auto m_kFunction{&Stub::getDuration}; +}; + // mediakeys module struct CreateMediaKeys { diff --git a/tests/componenttests/server/common/MessageBuilders.cpp b/tests/componenttests/server/common/MessageBuilders.cpp index a1ae941e1..bdfb44a01 100644 --- a/tests/componenttests/server/common/MessageBuilders.cpp +++ b/tests/componenttests/server/common/MessageBuilders.cpp @@ -407,6 +407,13 @@ ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sess return request; } +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId) +{ + ::firebolt::rialto::GetDurationRequest request; + request.set_session_id(sessionId); + return request; +} + ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine() { ::firebolt::rialto::CreateMediaKeysRequest request; diff --git a/tests/componenttests/server/common/MessageBuilders.h b/tests/componenttests/server/common/MessageBuilders.h index 815266713..0c255bb5f 100644 --- a/tests/componenttests/server/common/MessageBuilders.h +++ b/tests/componenttests/server/common/MessageBuilders.h @@ -83,6 +83,7 @@ ::firebolt::rialto::SetSourcePositionRequest createSetSourcePositionRequest(int ::firebolt::rialto::ProcessAudioGapRequest createProcessAudioGapRequest(int sessionId, std::int64_t position, unsigned duration, std::int64_t discontinuityGap, bool audioAac); +::firebolt::rialto::GetDurationRequest createGetDurationRequest(int sessionId); // media keys module ::firebolt::rialto::CreateMediaKeysRequest createCreateMediaKeysRequestWidevine(); diff --git a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index 2d11ee4f6..3b04393d2 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -39,6 +39,7 @@ constexpr bool kSyncOff{true}; constexpr int32_t kStreamSyncMode{1}; constexpr int32_t kBufferingLimit{4321}; constexpr bool kUseBuffering{true}; +constexpr int64_t kDuration{434523241}; } // namespace namespace firebolt::rialto::server::ct @@ -159,6 +160,16 @@ class PipelinePropertyTest : public MediaPipelineTest *returnVal = value ? TRUE : FALSE; })); } + else if constexpr (std::is_same_v) + { + EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(propertyName.c_str()), _)) + .WillOnce(Invoke( + [&](gpointer, const gchar *, void *val) + { + guint *returnVal = reinterpret_cast(val); + *returnVal = value; + })); + } else if constexpr (std::is_same_v) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(propertyName.c_str()), _)) @@ -215,6 +226,24 @@ class PipelinePropertyTest : public MediaPipelineTest EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); } + void willGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *duration) + { + *duration = kDuration; + workerFinished(); + return TRUE; + })); + } + + void willFailToGetDuration() + { + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(&m_pipeline, GST_FORMAT_TIME, _)) + .WillOnce(DoAll(Invoke(this, &MediaPipelineTest::workerFinished), Return(FALSE))); + } + void setImmediateOutput() { auto req{createSetImmediateOutputRequest(m_sessionId, m_videoSourceId, kImmediateOutput)}; @@ -395,6 +424,23 @@ class PipelinePropertyTest : public MediaPipelineTest ConfigureAction(m_clientStub).send(req).expectFailure(); } + void getDurationSuccess() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub) + .send(req) + .expectSuccess() + .matchResponse([&](const auto &resp) { EXPECT_EQ(resp.duration(), kDuration); }); + waitWorker(); + } + + void getDurationFailure() + { + auto req{createGetDurationRequest(m_sessionId)}; + ConfigureAction(m_clientStub).send(req).expectFailure(); + waitWorker(); + } + private: GstElement *m_element{nullptr}; GstStructure m_testStructure; @@ -488,18 +534,21 @@ class PipelinePropertyTest : public MediaPipelineTest * Step 15: Get Use Buffering * Will get the UseBuffering property of the decodebin on the Rialto Server * - * Step 16: Remove sources + * Step 16: Get Duration + * Will get the duration of the playback on the Rialto Server + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -579,7 +628,11 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) // Step 15: Get Use Buffering getUseBuffering(); - // Step 16: Remove sources + // Step 16: Get Duration + willGetDuration(); + getDurationSuccess(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); @@ -587,7 +640,7 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) willStop(); stop(); - // Step 19: Destroy media session + // Step 18: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } @@ -693,18 +746,22 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetSuccess) * Rialto client sends UseBufferingRequest and waits for response * UseBufferingResponse is false because the sessionId is wrong * - * Step 16: Remove sources + * Step 16: Fail to Get Duration + * Rialto client sends GetDurationRequest and waits for response + * GetDurationResponse is false because the server couldn't process it + * + * Step 17: Remove sources * Remove the audio source. * Expect that audio source is removed. * Remove the video source. * Expect that video source is removed. * - * Step 17: Stop + * Step 18: Stop * Stop the playback. * Expect that stop propagated to the gstreamer pipeline. * Expect that server notifies the client that the Playback state has changed to STOPPED. * - * Step 18: Destroy media session + * Step 19: Destroy media session * Send DestroySessionRequest. * Expect that the session is destroyed on the server. * @@ -784,15 +841,19 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) // Step 15: Fail to Set Use Buffering setUseBufferingFailure(); - // Step 16: Remove sources + // Step 16: Fail to Get Duration + willFailToGetDuration(); + getDurationFailure(); + + // Step 17: Remove sources removeSource(m_audioSourceId); removeSource(m_videoSourceId); - // Step 17: Stop + // Step 18: Stop willStop(); stop(); - // Step 18: Destroy media session + // Step 19: Destroy media session gstPlayerWillBeDestructed(); destroySession(); } diff --git a/tests/unittests/common/unittests/TimerTests.cpp b/tests/unittests/common/unittests/TimerTests.cpp index f6f8aa1bb..dfc86c8f2 100644 --- a/tests/unittests/common/unittests/TimerTests.cpp +++ b/tests/unittests/common/unittests/TimerTests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "ITimer.h" @@ -64,6 +65,33 @@ TEST(TimerTests, ShouldCancelTimer) EXPECT_FALSE(callFlag); } +TEST(TimerTests, ShouldCancelFromSameThread) +{ + std::mutex mtx; + std::condition_variable cv; + bool cancelled = false; + + std::shared_ptr timer; + timer = ITimerFactory::getFactory()->createTimer( + std::chrono::milliseconds{50}, + [&]() + { + timer->cancel(); + { + std::lock_guard lock{mtx}; + cancelled = true; + } + cv.notify_one(); + }, + TimerType::ONE_SHOT); + + std::unique_lock lock{mtx}; + cv.wait_for(lock, kEnoughTimeForTestToComplete, [&] { return cancelled; }); + + EXPECT_TRUE(cancelled); + EXPECT_FALSE(timer->isActive()); +} + TEST(TimerTests, ShouldTimeoutPeriodicTimer) { std::mutex mtx; diff --git a/tests/unittests/media/client/ipc/CMakeLists.txt b/tests/unittests/media/client/ipc/CMakeLists.txt index c4018151c..54347d4df 100644 --- a/tests/unittests/media/client/ipc/CMakeLists.txt +++ b/tests/unittests/media/client/ipc/CMakeLists.txt @@ -35,8 +35,11 @@ add_gtests ( mediaPipelineIpc/SetPositionTest.cpp mediaPipelineIpc/SetPlaybackRateTest.cpp mediaPipelineIpc/GetPositionTest.cpp + mediaPipelineIpc/GetDurationTest.cpp mediaPipelineIpc/SetImmediateOutputTest.cpp mediaPipelineIpc/GetImmediateOutputTest.cpp + mediaPipelineIpc/SetReportDecodeErrorsTest.cpp + mediaPipelineIpc/GetQueuedFramesTest.cpp mediaPipelineIpc/GetStatsTest.cpp mediaPipelineIpc/RenderFrameTest.cpp mediaPipelineIpc/GetVolumeTest.cpp diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp new file mode 100644 index 000000000..12d83b993 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetDurationTest.cpp @@ -0,0 +1,97 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcGetDurationTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } +}; + +/** + * Test that getDuration can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), m_controllerMock.get(), + getDurationRequestMatcher(m_sessionId), _, m_blockingClosureMock.get())); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that getDuration fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_TRUE(m_mediaPipelineIpc->getDuration(duration)); +} + +/** + * Test that getDuration fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcGetDurationTest, GetDurationFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getDuration"), _, _, _, _)); + + int64_t duration; + EXPECT_FALSE(m_mediaPipelineIpc->getDuration(duration)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp new file mode 100644 index 000000000..17bdd5c84 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/GetQueuedFramesTest.cpp @@ -0,0 +1,100 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcGetQueuedFramesTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } + + const int32_t m_kSourceId{1}; +}; + +/** + * Test that getQueuedFrames can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, + CallMethod(methodMatcher("getQueuedFrames"), m_controllerMock.get(), + getQueuedFramesRequestMatcher(m_sessionId, m_kSourceId), _, m_blockingClosureMock.get())); + + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} + +/** + * Test that getQueuedFrames fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that getQueuedFrames fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getQueuedFrames"), _, _, _, _)); + + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} + +/** + * Test that getQueuedFrames fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcGetQueuedFramesTest, GetQueuedFramesFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("getQueuedFrames"), _, _, _, _)); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipelineIpc->getQueuedFrames(m_kSourceId, queuedFrames)); +} diff --git a/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..d7e5fb8d2 --- /dev/null +++ b/tests/unittests/media/client/ipc/mediaPipelineIpc/SetReportDecodeErrorsTest.cpp @@ -0,0 +1,96 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineIpcTestBase.h" +#include "MediaPipelineProtoRequestMatchers.h" + +class RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest : public MediaPipelineIpcTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineIpcTestBase::SetUp(); + + createMediaPipelineIpc(); + } + + virtual void TearDown() + { + destroyMediaPipelineIpc(); + + MediaPipelineIpcTestBase::TearDown(); + } + + const int32_t m_kSourceId{1}; +}; + +/** + * Test that setReportDecodeErrors can be called successfully. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, Success) +{ + expectIpcApiCallSuccess(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), m_controllerMock.get(), + setReportDecodeErrorsRequestMatcher(m_sessionId, m_kSourceId), _, + m_blockingClosureMock.get())); + + EXPECT_TRUE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors fails if the ipc channel disconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, ChannelDisconnected) +{ + expectIpcApiCallDisconnected(); + expectUnsubscribeEvents(); + + EXPECT_FALSE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); + + // Reattach channel on destroySession + EXPECT_CALL(*m_ipcClientMock, getChannel()).WillOnce(Return(m_channelMock)).RetiresOnSaturation(); + expectSubscribeEvents(); +} + +/** + * Test that setReportDecodeErrors fails if the ipc channel disconnected and succeeds if the channel is reconnected. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, ReconnectChannel) +{ + expectIpcApiCallReconnected(); + expectUnsubscribeEvents(); + expectSubscribeEvents(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), _, _, _, _)); + + EXPECT_TRUE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors fails when ipc fails. + */ +TEST_F(RialtoClientMediaPipelineIpcSetReportDecodeErrorsTest, SetReportDecodeErrorsFailure) +{ + expectIpcApiCallFailure(); + + EXPECT_CALL(*m_channelMock, CallMethod(methodMatcher("setReportDecodeErrors"), _, _, _, _)); + + EXPECT_FALSE(m_mediaPipelineIpc->setReportDecodeErrors(m_kSourceId, true)); +} diff --git a/tests/unittests/media/client/main/CMakeLists.txt b/tests/unittests/media/client/main/CMakeLists.txt index 17a5b0d95..36ef5cd7e 100644 --- a/tests/unittests/media/client/main/CMakeLists.txt +++ b/tests/unittests/media/client/main/CMakeLists.txt @@ -32,8 +32,11 @@ add_gtests ( mediaPipeline/SetPositionTest.cpp mediaPipeline/SetPlaybackRateTest.cpp mediaPipeline/GetPositionTest.cpp + mediaPipeline/GetDurationTest.cpp mediaPipeline/SetImmediateOutputTest.cpp mediaPipeline/GetImmediateOutputTest.cpp + mediaPipeline/SetReportDecodeErrorsTest.cpp + mediaPipeline/GetQueuedFramesTest.cpp mediaPipeline/GetStatsTest.cpp mediaPipeline/RenderFrameTest.cpp mediaPipeline/SetVolumeTest.cpp diff --git a/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp new file mode 100644 index 000000000..64122f833 --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/GetDurationTest.cpp @@ -0,0 +1,66 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineGetDurationTest : public MediaPipelineTestBase +{ +protected: + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that GetDuration returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationSuccess) +{ + constexpr int64_t kExpectedDuration{123}; + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)) + .WillOnce(Invoke( + [&](int64_t &duration) + { + duration = kExpectedDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(resultDuration)); + EXPECT_EQ(resultDuration, kExpectedDuration); +} + +/** + * Test that GetDuration returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineGetDurationTest, GetDurationFailure) +{ + int64_t resultDuration{}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getDuration(resultDuration)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(resultDuration)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp b/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp new file mode 100644 index 000000000..277b46872 --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/GetQueuedFramesTest.cpp @@ -0,0 +1,63 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineGetQueuedFramesTest : public MediaPipelineTestBase +{ +protected: + const int32_t m_kSourceId{1}; + + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that getQueuedFrames returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineGetQueuedFramesTest, GetQueuedFramesSuccess) +{ + constexpr uint32_t kExpectedQueuedFrames{123}; + EXPECT_CALL(*m_mediaPipelineIpcMock, getQueuedFrames(m_kSourceId, _)) + .WillOnce(DoAll(SetArgReferee<1>(123), Return(true))); + uint32_t queuedFrames; + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(m_kSourceId, queuedFrames)); + EXPECT_EQ(kExpectedQueuedFrames, queuedFrames); +} + +/** + * Test that getQueuedFrames returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineGetQueuedFramesTest, GetQueuedFramesFailure) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, getQueuedFrames(m_kSourceId, _)).WillOnce(Return(false)); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kSourceId, queuedFrames)); +} diff --git a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp index 6e22bdf4d..0585e0141 100644 --- a/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp +++ b/tests/unittests/media/client/main/mediaPipeline/MediaPipelineProxyTest.cpp @@ -135,6 +135,15 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getDuration(_)).WillOnce(DoAll(SetArgReferee<0>(kDuration), Return(true))); + { + int64_t duration; + EXPECT_TRUE(proxy->getDuration(duration)); + EXPECT_EQ(duration, kDuration); + } + + ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, getStats(_, _, _)) .WillOnce(DoAll(SetArgReferee<1>(kRenderedFrames), SetArgReferee<2>(kDroppedFrames), Return(true))); { @@ -163,6 +172,21 @@ TEST_F(RialtoClientMediaPipelineProxyTest, TestPassthrough) ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, setReportDecodeErrors(kSourceId, true)).WillOnce(Return(true)); + EXPECT_TRUE(proxy->setReportDecodeErrors(kSourceId, true)); + + ///////////////////////////////////////////// + + { + const uint32_t kQueuedFrames{123}; + uint32_t returnQueuedFrames; + EXPECT_CALL(*mediaPipelineMock, getQueuedFrames(kSourceId, _)) + .WillOnce(DoAll(SetArgReferee<1>(kQueuedFrames), Return(true))); + EXPECT_TRUE(proxy->getQueuedFrames(kSourceId, returnQueuedFrames)); + EXPECT_EQ(returnQueuedFrames, kQueuedFrames); + } + ///////////////////////////////////////////// + EXPECT_CALL(*mediaPipelineMock, setVideoWindow(1, 2, 3, 4)).WillOnce(Return(true)); EXPECT_TRUE(proxy->setVideoWindow(1, 2, 3, 4)); diff --git a/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..29f10533d --- /dev/null +++ b/tests/unittests/media/client/main/mediaPipeline/SetReportDecodeErrorsTest.cpp @@ -0,0 +1,58 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MediaPipelineTestBase.h" + +class RialtoClientMediaPipelineSetReportDecodeErrorsTest : public MediaPipelineTestBase +{ +protected: + const int32_t m_kSourceId{1}; + + virtual void SetUp() + { + MediaPipelineTestBase::SetUp(); + + createMediaPipeline(); + } + + virtual void TearDown() + { + destroyMediaPipeline(); + + MediaPipelineTestBase::TearDown(); + } +}; + +/** + * Test that setReportDecodeErrors returns success if the IPC API succeeds. + */ +TEST_F(RialtoClientMediaPipelineSetReportDecodeErrorsTest, SetReportDecodeErrorsSuccess) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setReportDecodeErrors(m_kSourceId, _)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(m_kSourceId, true)); +} + +/** + * Test that setReportDecodeErrors returns failure if the IPC API fails. + */ +TEST_F(RialtoClientMediaPipelineSetReportDecodeErrorsTest, SetReportDecodeErrorsFailure) +{ + EXPECT_CALL(*m_mediaPipelineIpcMock, setReportDecodeErrors(m_kSourceId, _)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kSourceId, true)); +} diff --git a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h index 32e72ba2c..73956accf 100644 --- a/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h +++ b/tests/unittests/media/client/mocks/ipc/MediaPipelineIpcMock.h @@ -47,6 +47,8 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, getPosition, (int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setPlaybackRate, (double rate), (override)); MOCK_METHOD(bool, renderFrame, (), (override)); @@ -74,6 +76,7 @@ class MediaPipelineIpcMock : public IMediaPipelineIpc MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h index c69140210..04b5dfbe5 100644 --- a/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h +++ b/tests/unittests/media/client/mocks/main/MediaPipelineAndControlClientMock.h @@ -50,6 +50,8 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, getPosition, (int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); @@ -89,6 +91,7 @@ class MediaPipelineAndControlClientMock : public IMediaPipelineAndIControlClient MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); }; } // namespace firebolt::rialto::client diff --git a/tests/unittests/media/server/gstplayer/CMakeLists.txt b/tests/unittests/media/server/gstplayer/CMakeLists.txt index ed6635e3b..00a647f56 100644 --- a/tests/unittests/media/server/gstplayer/CMakeLists.txt +++ b/tests/unittests/media/server/gstplayer/CMakeLists.txt @@ -49,6 +49,7 @@ add_gtests(RialtoServerGstPlayerUnitTests genericPlayer/tasksTests/SetBufferingLimitTest.cpp genericPlayer/tasksTests/SetLowLatencyTest.cpp genericPlayer/tasksTests/SetImmediateOutputTest.cpp + genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp genericPlayer/tasksTests/SetMuteTest.cpp genericPlayer/tasksTests/SetPlaybackRateTest.cpp genericPlayer/tasksTests/SetPositionTest.cpp diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 5f079f930..9db981f26 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -72,6 +72,7 @@ const std::string kAutoVideoSinkTypeName{"GstAutoVideoSink"}; const std::string kAutoAudioSinkTypeName{"GstAutoAudioSink"}; constexpr bool kResetTime{true}; const std::string kImmediateOutputStr{"immediate-output"}; +const std::string kReportDecodeErrorsStr{"report-decode-errors"}; const std::string kLowLatencyStr{"low-latency"}; const std::string kSyncStr{"sync"}; const std::string kSyncOffStr{"sync-off"}; @@ -355,6 +356,35 @@ TEST_F(GstGenericPlayerPrivateTest, shouldSetImmediateOutput) EXPECT_TRUE(m_sut->setImmediateOutput()); } +TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetReportDecodeErrorsIfPropertyDoesntExist) +{ + modifyContext([&](GenericPlayerContext &context) { context.pendingReportDecodeErrorsForVideo = true; }); + + expectGetVideoDecoder(m_realElement); + + expectPropertyDoesntExist(m_glibWrapperMock, m_gstWrapperMock, m_realElement, kReportDecodeErrorsStr); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); + EXPECT_FALSE(m_sut->setReportDecodeErrors()); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldSetReportDecodeErrors) +{ + modifyContext([&](GenericPlayerContext &context) { context.pendingReportDecodeErrorsForVideo = true; }); + + expectGetVideoDecoder(m_realElement); + + GParamSpec gParamSpec{}; + + EXPECT_CALL(*m_glibWrapperMock, + gObjectClassFindProperty(G_OBJECT_GET_CLASS(m_realElement), StrEq(kReportDecodeErrorsStr))) + .WillOnce(Return(&gParamSpec)); + + EXPECT_CALL(*m_glibWrapperMock, gObjectSetStub(m_realElement, StrEq(kReportDecodeErrorsStr))).Times(1); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_realElement)).Times(1); + EXPECT_TRUE(m_sut->setReportDecodeErrors()); +} + TEST_F(GstGenericPlayerPrivateTest, shouldFailToSetLowLatencyIfSinkIsNull) { modifyContext([&](GenericPlayerContext &context) { context.pendingLowLatency = true; }); @@ -2276,6 +2306,7 @@ TEST_F(GstGenericPlayerPrivateTest, shouldReattachAmlhalasinkAudioSourceWithFirs EXPECT_CALL(*m_gstWrapperMock, gstElementSyncStateWithParent(&decodeBin)).WillOnce(Return(TRUE)); // end of reattachSource: caps was updated to configCaps inside performAudioTrackCodecChannelSwitch EXPECT_CALL(*m_gstWrapperMock, gstCapsUnref(&configCaps)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&playsinkBin)); std::unique_ptr source = std::make_unique("audio/aac", false); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp index 138fbcecf..c658a080d 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerTest.cpp @@ -452,6 +452,43 @@ TEST_F(GstGenericPlayerTest, shouldFailToGetImmediateOutputInPlayingStateIfPrope EXPECT_FALSE(m_sut->getImmediateOutput(MediaSourceType::VIDEO, immediateOutputState)); } +TEST_F(GstGenericPlayerTest, shouldSetReportDecodeErrors) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createSetReportDecodeErrors(_, _, MediaSourceType::VIDEO, true)) + .WillOnce(Return(ByMove(std::move(task)))); + + EXPECT_TRUE(m_sut->setReportDecodeErrors(MediaSourceType::VIDEO, true)); +} + +TEST_F(GstGenericPlayerTest, shouldGetQueuedFramesInPlayingState) +{ + setPipelineState(GST_STATE_PLAYING); + const uint32_t kTestQueuedFramesValue{123}; + const std::string kPropertyStr{"queued-frames"}; + + expectGetVideoDecoder(m_element); + willGetElementProperty(kPropertyStr, kTestQueuedFramesValue); + + uint32_t queuedFrames; + EXPECT_TRUE(m_sut->getQueuedFrames(queuedFrames)); + EXPECT_EQ(queuedFrames, kTestQueuedFramesValue); +} + +TEST_F(GstGenericPlayerTest, shouldFailToGetQueuedFramesInPlayingStateIfPropertyDoesntExist) +{ + setPipelineState(GST_STATE_PLAYING); + + expectGetVideoDecoder(m_element); + + EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, StrEq("queued-frames"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).Times(1); + + uint32_t queuedFrames; + EXPECT_FALSE(m_sut->getQueuedFrames(queuedFrames)); +} + TEST_F(GstGenericPlayerTest, shouldGetStatsInPlayingState) { constexpr guint64 kRenderedFrames{1234}; @@ -1126,3 +1163,25 @@ TEST_F(GstGenericPlayerTest, shouldSwitchSource) m_sut->switchSource(source); } + +TEST_F(GstGenericPlayerTest, shouldReturnInvalidDurationWhenQueryFails) +{ + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)).WillOnce(Return(FALSE)); + EXPECT_FALSE(m_sut->getDuration(targetDuration)); +} + +TEST_F(GstGenericPlayerTest, shouldReturnDuration) +{ + constexpr gint64 kExpectedDuration{123}; + int64_t targetDuration{}; + EXPECT_CALL(*m_gstWrapperMock, gstElementQueryDuration(_, GST_FORMAT_TIME, _)) + .WillOnce(Invoke( + [&](GstElement *element, GstFormat format, gint64 *cur) + { + *cur = kExpectedDuration; + return TRUE; + })); + EXPECT_TRUE(m_sut->getDuration(targetDuration)); + EXPECT_EQ(kExpectedDuration, targetDuration); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index f379b5884..13da7fecc 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -46,6 +46,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSubtitleOffset.h" @@ -1941,7 +1942,7 @@ void GenericTasksTestsBase::shouldNotRegisterCallbackWhenElementNameIsNotTypefin EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("typefind"))) - .WillOnce(Return(nullptr)); + .WillRepeatedly(Return(nullptr)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } @@ -1952,7 +1953,7 @@ void GenericTasksTestsBase::shouldRegisterCallbackForTypefindElement() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(testContext->m_element)) .WillOnce(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("typefind"))) - .WillOnce(Return(testContext->m_typefindElementName)); + .WillRepeatedly(Return(testContext->m_typefindElementName)); EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(G_OBJECT(testContext->m_element), StrEq("have-type"), _, &testContext->m_gstPlayer)) .WillOnce(Return(kSignalId)); @@ -1982,9 +1983,19 @@ void GenericTasksTestsBase::shouldUpdatePlaybackGroupWhenCallbackIsCalled() void GenericTasksTestsBase::shouldSetTypefindElement() { - EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_typefindElementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) + .WillOnce(Return(&testContext->m_obj1)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::triggerDeepElementAdded() @@ -2030,8 +2041,11 @@ void GenericTasksTestsBase::shouldSetParseElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_parseElementName, StrEq("parse"))) - .WillOnce(Return(testContext->m_parseElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkParsePlaybackGroupAdded() @@ -2056,10 +2070,15 @@ void GenericTasksTestsBase::shouldSetDecoderElement() testContext->m_context.playbackGroup.m_curAudioDecodeBin = &testContext->m_audioDecodeBin; EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(&testContext->m_audioDecodeBin)) .WillOnce(Return(&testContext->m_obj1)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("parse"))) - .WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_decoderElementName, StrEq("dec"))) - .WillOnce(Return(testContext->m_decoderElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() @@ -2074,8 +2093,11 @@ void GenericTasksTestsBase::checkDecoderPlaybackGroupAdded() void GenericTasksTestsBase::shouldSetGenericElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("audiosink"))) - .WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(FALSE)); } void GenericTasksTestsBase::shouldSetAudioSinkElement() @@ -2089,8 +2111,11 @@ void GenericTasksTestsBase::shouldSetAudioSinkElement() EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_audioSinkElementName)); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectCast(nullptr)).WillOnce(Return(nullptr)); - EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_audioSinkElementName, StrEq("audiosink"))) - .WillOnce(Return(testContext->m_audioSinkElementName)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillOnce(Return(TRUE)); } void GenericTasksTestsBase::shouldHaveNullParentSink() @@ -2098,6 +2123,7 @@ void GenericTasksTestsBase::shouldHaveNullParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(nullptr)); } @@ -2108,11 +2134,16 @@ void GenericTasksTestsBase::shouldHaveNonBinParentSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) .WillOnce(Return(testContext->m_elementName)); EXPECT_CALL(*testContext->m_glibWrapper, gStrrstr(testContext->m_elementName, StrEq("bin"))).WillOnce(Return(nullptr)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_audioParentSink)); EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_elementName)); } void GenericTasksTestsBase::shouldHaveBinParentSink() { + // Previous bin should be unrefed + testContext->m_context.playbackGroup.m_curAudioPlaysinkBin = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(&testContext->m_childElement)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetParent(testContext->m_element)) .WillOnce(Return(GST_OBJECT(&testContext->m_audioParentSink))); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetName(&testContext->m_audioParentSink)) @@ -2235,6 +2266,24 @@ void GenericTasksTestsBase::shouldTriggerSetUseBuffering() EXPECT_CALL(testContext->m_gstPlayer, setUseBuffering()).WillOnce(Return(true)); } +void GenericTasksTestsBase::shouldLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(TRUE)); +} + +void GenericTasksTestsBase::shouldFailToLinkTypefindAndParser() +{ + testContext->m_context.playbackGroup.m_linkTypefindParser = true; + testContext->m_context.playbackGroup.m_curAudioParse = &testContext->m_childElement; + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementLink(testContext->m_element, testContext->m_context.playbackGroup.m_curAudioParse)) + .WillOnce(Return(FALSE)); +} + void GenericTasksTestsBase::shouldStopGstPlayer() { auto audioStreamIt{testContext->m_context.streamInfo.find(firebolt::rialto::MediaSourceType::AUDIO)}; @@ -3294,6 +3343,20 @@ void GenericTasksTestsBase::triggerSetImmediateOutput() EXPECT_EQ(testContext->m_context.pendingImmediateOutputForVideo, true); } +void GenericTasksTestsBase::shouldSetReportDecodeErrors() +{ + EXPECT_CALL(testContext->m_gstPlayer, setReportDecodeErrors()).WillOnce(Return(true)); +} + +void GenericTasksTestsBase::triggerSetReportDecodeErrors() +{ + firebolt::rialto::server::tasks::generic::SetReportDecodeErrors task{testContext->m_context, testContext->m_gstPlayer, + MediaSourceType::VIDEO, true}; + task.execute(); + + EXPECT_EQ(testContext->m_context.pendingReportDecodeErrorsForVideo, true); +} + void GenericTasksTestsBase::shouldSetLowLatency() { EXPECT_CALL(testContext->m_gstPlayer, setLowLatency()).WillOnce(Return(true)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 29f64e16a..fc875a400 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -235,6 +235,8 @@ class GenericTasksTestsBase : public ::testing::Test void checkPlaybackGroupAdded(); void setUseBufferingPending(); void shouldTriggerSetUseBuffering(); + void shouldLinkTypefindAndParser(); + void shouldFailToLinkTypefindAndParser(); // Stop test methods void shouldStopGstPlayer(); @@ -277,6 +279,10 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetVideoMute(); void shouldSetSubtitleMute(); + // report-decode-errors decoder property test method + void shouldSetReportDecodeErrors(); + void triggerSetReportDecodeErrors(); + // immediate-output sink property test methods void shouldSetImmediateOutput(); void triggerSetImmediateOutput(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp index ef78956a1..c4d5d2741 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.cpp @@ -217,6 +217,20 @@ void GstGenericPlayerTestCommon::expectGetDecoder(GstElement *element) EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); } +void GstGenericPlayerTestCommon::expectGetVideoDecoder(GstElement *element) +{ + EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); + EXPECT_CALL(*m_gstWrapperMock, gstIteratorNext(&m_it, _)).WillOnce(Return(GST_ITERATOR_OK)); + EXPECT_CALL(*m_glibWrapperMock, gValueGetObject(_)).WillOnce(Return(element)); + EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(element)).WillOnce(Return(m_factory)); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_factory, (GST_ELEMENT_FACTORY_TYPE_DECODER | + GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO))) + .WillOnce(Return(TRUE)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(element)).WillOnce(Return(element)); + EXPECT_CALL(*m_glibWrapperMock, gValueUnset(_)); + EXPECT_CALL(*m_gstWrapperMock, gstIteratorFree(&m_it)); +} + void GstGenericPlayerTestCommon::expectGetVideoParser(GstElement *element) { EXPECT_CALL(*m_gstWrapperMock, gstBinIterateRecurse(GST_BIN(&m_pipeline))).WillOnce(Return(&m_it)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h index aaae1fc5c..d8e72efaf 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GstGenericPlayerTestCommon.h @@ -119,6 +119,7 @@ class GstGenericPlayerTestCommon : public ::testing::Test void expectCheckPlaySink(); void expectSetMessageCallback(); void expectGetDecoder(GstElement *element); + void expectGetVideoDecoder(GstElement *element); void expectGetVideoParser(GstElement *element); void expectGetAVSink(const std::string &sinkName, GstElement *elementObj); void expectGetSink(const std::string &sinkName, GstElement *elementObj); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp index 70c305e22..d25627cda 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/GenericPlayerTaskFactoryTest.cpp @@ -53,6 +53,7 @@ #include "tasks/generic/SetMute.h" #include "tasks/generic/SetPlaybackRate.h" #include "tasks/generic/SetPosition.h" +#include "tasks/generic/SetReportDecodeErrors.h" #include "tasks/generic/SetSourcePosition.h" #include "tasks/generic/SetStreamSyncMode.h" #include "tasks/generic/SetSync.h" @@ -344,6 +345,13 @@ TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetImmediateOutput) EXPECT_NO_THROW(dynamic_cast(*task)); } +TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetReportDecodeErrors) +{ + auto task = m_sut.createSetReportDecodeErrors(m_context, m_gstPlayer, firebolt::rialto::MediaSourceType::VIDEO, true); + EXPECT_NE(task, nullptr); + EXPECT_NO_THROW(dynamic_cast(*task)); +} + TEST_F(GenericPlayerTaskFactoryTest, ShouldCreateSetTextTrackIdentifier) { auto task = m_sut.createSetTextTrackIdentifier(m_context, ""); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp new file mode 100644 index 000000000..3d3f89f96 --- /dev/null +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetReportDecodeErrorsTest.cpp @@ -0,0 +1,30 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Sky UK + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GenericTasksTestsBase.h" + +class SetReportDecodeErrorsTest : public GenericTasksTestsBase +{ +}; + +TEST_F(SetReportDecodeErrorsTest, shouldSetReportDecodeErrors) +{ + shouldSetReportDecodeErrors(); + triggerSetReportDecodeErrors(); +} diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp index dfed351b4..74312e50f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/UpdatePlaybackGroupTest.cpp @@ -72,3 +72,19 @@ TEST_F(UpdatePlaybackGroupTest, shouldTriggerUseBuffering) triggerUpdatePlaybackGroup(); checkPlaybackGroupAdded(); } + +TEST_F(UpdatePlaybackGroupTest, shouldLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} + +TEST_F(UpdatePlaybackGroupTest, shouldFailToLinkTypefindWithParser) +{ + shouldSuccessfullyFindTypefindAndParent(); + shouldFailToLinkTypefindAndParser(); + triggerUpdatePlaybackGroup(); + checkPlaybackGroupAdded(); +} diff --git a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp index 89d61c1e7..d1b79780f 100644 --- a/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/controlModuleService/ControlModuleServiceTests.cpp @@ -106,3 +106,12 @@ TEST_F(ControlModuleServiceTests, FactoryCreatesObject) { testFactoryCreatesObject(); } + +TEST_F(ControlModuleServiceTests, shouldRegisterClientWithoutSchemaVersion) +{ + clientWillConnect(); + sendClientConnected(); + controlServiceWillRegisterClient(); + sendRegisterClientRequestAndReceiveResponse(std::nullopt); + controlServiceWillRemoveControl(); +} diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp index dcad505bb..e4776c7c3 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTests.cpp @@ -249,6 +249,22 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetPosition) sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); } +TEST_F(MediaPipelineModuleServiceTests, shouldGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillGetDuration(); + sendGetDurationRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetDuration) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToGetDuration(); + sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldSetImmediateOutput) { mediaPipelineServiceWillCreateSession(); @@ -281,6 +297,38 @@ TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetImmediateOutput) sendGetImmediateOutputRequestAndReceiveFail(); } +TEST_F(MediaPipelineModuleServiceTests, shouldSetReportDecodeErrors) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillSetReportDecodeErrors(); + sendSetReportDecodeErrorsRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToSetReportDecodeErrors) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToSetReportDecodeErrors(); + sendSetReportDecodeErrorsRequestAndReceiveFail(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldGetQueuedFrames) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillGetQueuedFrames(); + sendGetQueuedFramesRequestAndReceiveResponse(); +} + +TEST_F(MediaPipelineModuleServiceTests, shouldFailToGetQueuedFrames) +{ + mediaPipelineServiceWillCreateSession(); + sendCreateSessionRequestAndReceiveResponse(); + mediaPipelineServiceWillFailToGetQueuedFrames(); + sendGetQueuedFramesRequestAndReceiveFail(); +} + TEST_F(MediaPipelineModuleServiceTests, shouldGetStats) { mediaPipelineServiceWillCreateSession(); diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp index 31ec98768..b51b9ac40 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.cpp @@ -86,6 +86,8 @@ constexpr uint64_t kDroppedFrames{321}; constexpr uint32_t kDuration{30}; constexpr bool kImmediateOutputVal1{false}; constexpr bool kImmediateOutputVal2{true}; +constexpr bool kReportDecodeErrorsVal{false}; +constexpr uint32_t kQueuedFramesVal{123}; constexpr int64_t kDiscontinuityGap{1}; constexpr bool kIsAudioAac{false}; constexpr uint32_t kBufferingLimit{12341}; @@ -491,6 +493,24 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetPosition( EXPECT_CALL(m_mediaPipelineServiceMock, getPosition(kHardcodedSessionId, _)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetDuration() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)) + .WillOnce(Invoke( + [&](int, std::int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetDuration() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, getDuration(kHardcodedSessionId, _)).WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetImmediateOutput() { expectRequestSuccess(); @@ -518,6 +538,33 @@ void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetImmediate EXPECT_CALL(m_mediaPipelineServiceMock, getImmediateOutput(kHardcodedSessionId, _, _)).WillOnce(Return(false)); } +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillSetReportDecodeErrors() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, setReportDecodeErrors(kHardcodedSessionId, _, kReportDecodeErrorsVal)) + .WillOnce(Return(true)); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToSetReportDecodeErrors() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, setReportDecodeErrors(kHardcodedSessionId, _, kReportDecodeErrorsVal)) + .WillOnce(Return(false)); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetQueuedFrames() +{ + expectRequestSuccess(); + EXPECT_CALL(m_mediaPipelineServiceMock, getQueuedFrames(kHardcodedSessionId, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(kQueuedFramesVal), Return(true))); +} + +void MediaPipelineModuleServiceTests::mediaPipelineServiceWillFailToGetQueuedFrames() +{ + expectRequestFailure(); + EXPECT_CALL(m_mediaPipelineServiceMock, getQueuedFrames(kHardcodedSessionId, _, _)).WillOnce(Return(false)); +} + void MediaPipelineModuleServiceTests::mediaPipelineServiceWillGetStats() { expectRequestSuccess(); @@ -1104,6 +1151,28 @@ void MediaPipelineModuleServiceTests::sendGetPositionRequestAndReceiveResponseWi m_service->getPosition(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponse() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); + + EXPECT_EQ(response.duration(), kDuration); +} + +void MediaPipelineModuleServiceTests::sendGetDurationRequestAndReceiveResponseWithoutDurationMatch() +{ + firebolt::rialto::GetDurationRequest request; + firebolt::rialto::GetDurationResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getDuration(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendSetImmediateOutputRequestAndReceiveResponse() { firebolt::rialto::SetImmediateOutputRequest request; @@ -1148,6 +1217,50 @@ void MediaPipelineModuleServiceTests::sendGetImmediateOutputRequestAndReceiveFai m_service->getImmediateOutput(m_controllerMock.get(), &request, &response, m_closureMock.get()); } +void MediaPipelineModuleServiceTests::sendSetReportDecodeErrorsRequestAndReceiveResponse() +{ + firebolt::rialto::SetReportDecodeErrorsRequest request; + firebolt::rialto::SetReportDecodeErrorsResponse response; + + request.set_session_id(kHardcodedSessionId); + request.set_report_decode_errors(kReportDecodeErrorsVal); + + m_service->setReportDecodeErrors(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + +void MediaPipelineModuleServiceTests::sendSetReportDecodeErrorsRequestAndReceiveFail() +{ + firebolt::rialto::SetReportDecodeErrorsRequest request; + firebolt::rialto::SetReportDecodeErrorsResponse response; + + request.set_session_id(kHardcodedSessionId); + request.set_report_decode_errors(kReportDecodeErrorsVal); + + m_service->setReportDecodeErrors(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + +void MediaPipelineModuleServiceTests::sendGetQueuedFramesRequestAndReceiveResponse() +{ + firebolt::rialto::GetQueuedFramesRequest request; + firebolt::rialto::GetQueuedFramesResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getQueuedFrames(m_controllerMock.get(), &request, &response, m_closureMock.get()); + + EXPECT_EQ(response.queued_frames(), kQueuedFramesVal); +} + +void MediaPipelineModuleServiceTests::sendGetQueuedFramesRequestAndReceiveFail() +{ + firebolt::rialto::GetQueuedFramesRequest request; + firebolt::rialto::GetQueuedFramesResponse response; + + request.set_session_id(kHardcodedSessionId); + + m_service->getQueuedFrames(m_controllerMock.get(), &request, &response, m_closureMock.get()); +} + void MediaPipelineModuleServiceTests::sendGetStatsRequestAndReceiveResponse() { firebolt::rialto::GetStatsRequest request; diff --git a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h index 90f02fb4a..f71a7f8c5 100644 --- a/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h +++ b/tests/unittests/media/server/ipc/mediaPipelineModuleService/MediaPipelineModuleServiceTestsFixture.h @@ -74,10 +74,16 @@ class MediaPipelineModuleServiceTests : public testing::Test void mediaPipelineServiceWillFailToSetPlaybackRate(); void mediaPipelineServiceWillGetPosition(); void mediaPipelineServiceWillFailToGetPosition(); + void mediaPipelineServiceWillGetDuration(); + void mediaPipelineServiceWillFailToGetDuration(); void mediaPipelineServiceWillSetImmediateOutput(); void mediaPipelineServiceWillFailToSetImmediateOutput(); void mediaPipelineServiceWillGetImmediateOutput(); void mediaPipelineServiceWillFailToGetImmediateOutput(); + void mediaPipelineServiceWillSetReportDecodeErrors(); + void mediaPipelineServiceWillFailToSetReportDecodeErrors(); + void mediaPipelineServiceWillGetQueuedFrames(); + void mediaPipelineServiceWillFailToGetQueuedFrames(); void mediaPipelineServiceWillGetStats(); void mediaPipelineServiceWillFailToGetStats(); void mediaPipelineServiceWillRenderFrame(); @@ -150,10 +156,16 @@ class MediaPipelineModuleServiceTests : public testing::Test void sendSetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponse(); void sendGetPositionRequestAndReceiveResponseWithoutPositionMatch(); + void sendGetDurationRequestAndReceiveResponse(); + void sendGetDurationRequestAndReceiveResponseWithoutDurationMatch(); void sendSetImmediateOutputRequestAndReceiveResponse(); void sendSetImmediateOutputRequestAndReceiveFail(); void sendGetImmediateOutputRequestAndReceiveResponse(); void sendGetImmediateOutputRequestAndReceiveFail(); + void sendSetReportDecodeErrorsRequestAndReceiveResponse(); + void sendSetReportDecodeErrorsRequestAndReceiveFail(); + void sendGetQueuedFramesRequestAndReceiveResponse(); + void sendGetQueuedFramesRequestAndReceiveFail(); void sendGetStatsRequestAndReceiveResponse(); void sendGetStatsRequestAndReceiveResponseWithoutStatsMatch(); void sendHaveDataRequestAndReceiveResponse(); diff --git a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp index 171480c5c..89188d929 100644 --- a/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp +++ b/tests/unittests/media/server/main/activeRequests/ActiveRequestsTests.cpp @@ -149,6 +149,29 @@ TEST_F(ActiveRequestsTests, shouldAddAndGetSegments) EXPECT_EQ(kSegments[0]->getType(), firebolt::rialto::MediaSourceType::AUDIO); } +TEST_F(ActiveRequestsTests, insertShouldHandleDuplicateId) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + EXPECT_EQ(1, m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::VIDEO, m_sut.getType(1)); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + +TEST_F(ActiveRequestsTests, insertShouldResolveDuplicateIdCollision) +{ + EXPECT_EQ(0, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(0)); + + m_sut.insert(firebolt::rialto::MediaSourceType::VIDEO, 100); + + EXPECT_EQ(2, m_sut.insert(firebolt::rialto::MediaSourceType::AUDIO, 100)); + EXPECT_EQ(firebolt::rialto::MediaSourceType::AUDIO, m_sut.getType(2)); +} + TEST_F(ActiveRequestsTests, shouldAddAndRemoveSegments) { std::vector data{'T', 'E', 'S', 'T'}; diff --git a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp index aa4f3e2f3..c503ddb0f 100644 --- a/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp +++ b/tests/unittests/media/server/main/mediaPipeline/MiscellaneousFunctionsTest.cpp @@ -28,6 +28,7 @@ class RialtoServerMediaPipelineMiscellaneousFunctionsTest : public MediaPipeline { protected: const int64_t m_kPosition{4028596027}; + const int64_t m_kDuration{8057192054}; const double m_kPlaybackRate{1.5}; uint64_t m_kRenderedFrames{3141}; uint64_t m_kDroppedFrames{95}; @@ -221,6 +222,44 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetPositionSuccess) EXPECT_EQ(targetPosition, m_kPosition); } +/** + * Test that GetDuration returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailureDueToUninitializedPlayer) +{ + int64_t targetDuration{}; + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationFailure) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->getDuration(targetDuration)); +} + +/** + * Test that GetDuration returns success if the gstreamer API succeeds and gets the duration + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetDurationSuccess) +{ + loadGstPlayer(); + int64_t targetDuration{}; + EXPECT_CALL(*m_gstPlayerMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = m_kDuration; + return true; + })); + EXPECT_TRUE(m_mediaPipeline->getDuration(targetDuration)); + EXPECT_EQ(targetDuration, m_kDuration); +} + /** * Test that SetImmediateOutput returns failure if the gstreamer player is not initialized */ @@ -327,6 +366,112 @@ TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetImmediateOutputSu EXPECT_EQ(immediateOutputState, false); } +/** + * Test that SetReportDecodeErrors returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsFailureDueToUninitializedPlayer) +{ + mainThreadWillEnqueueTaskAndWait(); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kDummySourceId, true)); +} + +/** + * Test that SetReportDecodeErrors returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsFailure) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, _)).WillOnce(Return(false)); + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, true)); +} + +/** + * Test that SetReportDecodeErrors fails if source is not present. + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsNoSourcePresent) +{ + loadGstPlayer(); + // No attachment of source + mainThreadWillEnqueueTaskAndWait(); + + EXPECT_FALSE(m_mediaPipeline->setReportDecodeErrors(m_kDummySourceId, true)); +} + +/** + * Test that SetReportDecodeErrors returns success if the gstreamer API succeeds + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, SetReportDecodeErrorsSuccess) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, true)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, true)); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, setReportDecodeErrors(_, false)).WillOnce(Return(true)); + EXPECT_TRUE(m_mediaPipeline->setReportDecodeErrors(videoSourceId, false)); +} + +/** + * Test that GetQueuedFrames returns failure if the gstreamer player is not initialized + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesFailureDueToUninitializedPlayer) +{ + mainThreadWillEnqueueTaskAndWait(); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kDummySourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames returns failure if the gstreamer API fails + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesFailure) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(Return(false)); + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames fails if source is not present. + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesNoSourcePresent) +{ + loadGstPlayer(); + // No attachment of source + mainThreadWillEnqueueTaskAndWait(); + + uint32_t queuedFrames; + EXPECT_FALSE(m_mediaPipeline->getQueuedFrames(m_kDummySourceId, queuedFrames)); +} + +/** + * Test that GetQueuedFrames returns success if the gstreamer API succeeds + */ +TEST_F(RialtoServerMediaPipelineMiscellaneousFunctionsTest, GetQueuedFramesSuccess) +{ + loadGstPlayer(); + int videoSourceId = attachSource(firebolt::rialto::MediaSourceType::VIDEO, "video/h264"); + + uint32_t queuedFrames; + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(DoAll(SetArgReferee<0>(123), Return(true))); + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); + EXPECT_EQ(queuedFrames, 123); + + mainThreadWillEnqueueTaskAndWait(); + EXPECT_CALL(*m_gstPlayerMock, getQueuedFrames(_)).WillOnce(DoAll(SetArgReferee<0>(456), Return(true))); + EXPECT_TRUE(m_mediaPipeline->getQueuedFrames(videoSourceId, queuedFrames)); + EXPECT_EQ(queuedFrames, 456); +} + /** * Test that GetStats returns failure if the gstreamer player is not initialized */ diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 2cb7f0a35..b3fca43e0 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -133,6 +133,10 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, const firebolt::rialto::MediaSourceType &type, bool immediateOutput), (const, override)); + MOCK_METHOD(std::unique_ptr, createSetReportDecodeErrors, + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, + const firebolt::rialto::MediaSourceType &type, bool reportDecodeErrors), + (const, override)); MOCK_METHOD(std::unique_ptr, createSetBufferingLimit, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, std::uint32_t limit), (const, override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h index c38c1d9eb..087cd452e 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerMock.h @@ -42,8 +42,12 @@ class GstGenericPlayerMock : public IGstGenericPlayer MOCK_METHOD(void, attachSamples, (const std::shared_ptr &dataReader), (override)); MOCK_METHOD(void, setPosition, (std::int64_t position), (override)); MOCK_METHOD(bool, getPosition, (std::int64_t & position), (override)); + MOCK_METHOD(bool, getDuration, (std::int64_t & duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (const MediaSourceType &mediaSourceType, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (const MediaSourceType &mediaSourceType, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (const MediaSourceType &mediaSourceType, bool reportDecodeErrors), + (override)); + MOCK_METHOD(bool, getQueuedFrames, (uint32_t & queuedFrames), (override)); MOCK_METHOD(bool, getStats, (const MediaSourceType &mediaSourceType, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(void, setVideoGeometry, (int x, int y, int width, int height), (override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index e28c70d8d..d0b018a87 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -39,6 +39,7 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, scheduleAllSourcesAttached, (), (override)); MOCK_METHOD(bool, setVideoSinkRectangle, (), (override)); MOCK_METHOD(bool, setImmediateOutput, (), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (), (override)); MOCK_METHOD(bool, setLowLatency, (), (override)); MOCK_METHOD(bool, setSync, (), (override)); MOCK_METHOD(bool, setSyncOff, (), (override)); diff --git a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h index 27a12a686..397672c4c 100644 --- a/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h +++ b/tests/unittests/media/server/mocks/main/MediaPipelineServerInternalMock.h @@ -44,6 +44,8 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, getPosition, (std::int64_t & position), (override)); MOCK_METHOD(bool, setImmediateOutput, (int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (uint32_t x, uint32_t y, uint32_t width, uint32_t height), (override)); MOCK_METHOD(bool, haveData, (MediaSourceStatus status, uint32_t numFrames, uint32_t needDataRequestId), (override)); @@ -75,6 +77,7 @@ class MediaPipelineServerInternalMock : public IMediaPipelineServerInternal MOCK_METHOD(bool, setUseBuffering, (bool useBuffering), (override)); MOCK_METHOD(bool, getUseBuffering, (bool &useBuffering), (override)); MOCK_METHOD(bool, switchSource, (const std::unique_ptr &source), (override)); + MOCK_METHOD(bool, getDuration, (int64_t & duration), (override)); MOCK_METHOD(bool, setSubtitleOffset, (int32_t sourceId, int64_t position), (override)); }; } // namespace firebolt::rialto::server diff --git a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h index f7b226777..950b7d0bd 100644 --- a/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h +++ b/tests/unittests/media/server/mocks/service/MediaPipelineServiceMock.h @@ -44,8 +44,11 @@ class MediaPipelineServiceMock : public IMediaPipelineService MOCK_METHOD(bool, setPlaybackRate, (int, double), (override)); MOCK_METHOD(bool, setPosition, (int, int64_t), (override)); MOCK_METHOD(bool, getPosition, (int sessionId, int64_t &position), (override)); + MOCK_METHOD(bool, getDuration, (int sessionId, int64_t &duration), (override)); MOCK_METHOD(bool, setImmediateOutput, (int sessionId, int32_t sourceId, bool immediateOutput), (override)); MOCK_METHOD(bool, getImmediateOutput, (int sessionId, int32_t sourceId, bool &immediateOutput), (override)); + MOCK_METHOD(bool, setReportDecodeErrors, (int sessionId, int32_t sourceId, bool reportDecodeErrors), (override)); + MOCK_METHOD(bool, getQueuedFrames, (int sessionId, int32_t sourceId, uint32_t &queuedFrames), (override)); MOCK_METHOD(bool, getStats, (int sessionId, int32_t sourceId, uint64_t &renderedFrames, uint64_t &droppedFrames), (override)); MOCK_METHOD(bool, setVideoWindow, (int, std::uint32_t, std::uint32_t, std::uint32_t, std::uint32_t), (override)); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp index 4cd1f95d1..838928736 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTests.cpp @@ -322,6 +322,26 @@ TEST_F(MediaPipelineServiceTests, shouldGetPosition) getPositionShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToGetDurationForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetDuration) +{ + initSession(); + mediaPipelineWillFailToGetDuration(); + getDurationShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldGetDuration) +{ + initSession(); + mediaPipelineWillGetDuration(); + getDurationShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldFailToSetImmediateOutputForNotExistingSession) { createMediaPipelineShouldSuccess(); @@ -362,6 +382,46 @@ TEST_F(MediaPipelineServiceTests, shouldGetImmediateOutput) getImmediateOutputShouldSucceed(); } +TEST_F(MediaPipelineServiceTests, shouldFailToSetReportDecodeErrorsForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + setReportDecodeErrorsShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetQueuedFramesForNotExistingSession) +{ + createMediaPipelineShouldSuccess(); + getQueuedFramesShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToSetReportDecodeErrors) +{ + initSession(); + mediaPipelineWillFailToSetReportDecodeErrors(); + setReportDecodeErrorsShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldFailToGetQueuedFrames) +{ + initSession(); + mediaPipelineWillFailToGetQueuedFrames(); + getQueuedFramesShouldFail(); +} + +TEST_F(MediaPipelineServiceTests, shouldSetReportDecodeErrors) +{ + initSession(); + mediaPipelineWillSetReportDecodeErrors(); + setReportDecodeErrorsShouldSucceed(); +} + +TEST_F(MediaPipelineServiceTests, shouldGetQueuedFrames) +{ + initSession(); + mediaPipelineWillGetQueuedFrames(); + getQueuedFramesShouldSucceed(); +} + TEST_F(MediaPipelineServiceTests, shouldFailToGetStatsForNotExistingSession) { createMediaPipelineShouldSuccess(); diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp index 421cb1d8b..73f5cc3d5 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.cpp @@ -69,6 +69,7 @@ const std::string kTextTrackIdentifier{"TextTrackIdentifier"}; constexpr uint32_t kBufferingLimit{4324}; constexpr bool kUseBuffering{true}; constexpr uint64_t kStopPosition{23412}; +constexpr uint32_t kQueuedFrames{123}; } // namespace namespace firebolt::rialto @@ -222,6 +223,22 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToGetPosition() EXPECT_CALL(m_mediaPipelineMock, getPosition(_)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)) + .WillOnce(Invoke( + [&](int64_t &pos) + { + pos = kDuration; + return true; + })); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToGetDuration() +{ + EXPECT_CALL(m_mediaPipelineMock, getDuration(_)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillSetImmediateOutput() { EXPECT_CALL(m_mediaPipelineMock, setImmediateOutput(_, _)).WillOnce(Return(true)); @@ -242,6 +259,26 @@ void MediaPipelineServiceTests::mediaPipelineWillFailToGetImmediateOutput() EXPECT_CALL(m_mediaPipelineMock, getImmediateOutput(_, _)).WillOnce(Return(false)); } +void MediaPipelineServiceTests::mediaPipelineWillSetReportDecodeErrors() +{ + EXPECT_CALL(m_mediaPipelineMock, setReportDecodeErrors(_, _)).WillOnce(Return(true)); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToSetReportDecodeErrors() +{ + EXPECT_CALL(m_mediaPipelineMock, setReportDecodeErrors(_, _)).WillOnce(Return(false)); +} + +void MediaPipelineServiceTests::mediaPipelineWillGetQueuedFrames() +{ + EXPECT_CALL(m_mediaPipelineMock, getQueuedFrames(_, _)).WillOnce(DoAll(SetArgReferee<1>(123), Return(true))); +} + +void MediaPipelineServiceTests::mediaPipelineWillFailToGetQueuedFrames() +{ + EXPECT_CALL(m_mediaPipelineMock, getQueuedFrames(_, _)).WillOnce(Return(false)); +} + void MediaPipelineServiceTests::mediaPipelineWillGetStats() { EXPECT_CALL(m_mediaPipelineMock, getStats(_, _, _)) @@ -707,6 +744,19 @@ void MediaPipelineServiceTests::getPositionShouldFail() EXPECT_FALSE(m_sut->getPosition(kSessionId, targetPosition)); } +void MediaPipelineServiceTests::getDurationShouldSucceed() +{ + std::int64_t targetDuration{}; + EXPECT_TRUE(m_sut->getDuration(kSessionId, targetDuration)); + EXPECT_EQ(targetDuration, kDuration); +} + +void MediaPipelineServiceTests::getDurationShouldFail() +{ + std::int64_t targetDuration{}; + EXPECT_FALSE(m_sut->getDuration(kSessionId, targetDuration)); +} + void MediaPipelineServiceTests::getStatsShouldSucceed() { std::uint64_t renderedFrames; @@ -746,6 +796,29 @@ void MediaPipelineServiceTests::getImmediateOutputShouldFail() EXPECT_FALSE(m_sut->getImmediateOutput(kSessionId, kSourceId, immOp)); } +void MediaPipelineServiceTests::setReportDecodeErrorsShouldSucceed() +{ + EXPECT_TRUE(m_sut->setReportDecodeErrors(kSessionId, kSourceId, true)); +} + +void MediaPipelineServiceTests::setReportDecodeErrorsShouldFail() +{ + EXPECT_FALSE(m_sut->setReportDecodeErrors(kSessionId, kSourceId, true)); +} + +void MediaPipelineServiceTests::getQueuedFramesShouldSucceed() +{ + uint32_t queuedFr; + EXPECT_TRUE(m_sut->getQueuedFrames(kSessionId, kSourceId, queuedFr)); + EXPECT_EQ(queuedFr, kQueuedFrames); +} + +void MediaPipelineServiceTests::getQueuedFramesShouldFail() +{ + uint32_t queuedFr; + EXPECT_FALSE(m_sut->getQueuedFrames(kSessionId, kSourceId, queuedFr)); +} + void MediaPipelineServiceTests::getSupportedMimeTypesSucceed() { MediaSourceType type = MediaSourceType::VIDEO; diff --git a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h index ead87a642..d76ca55a3 100644 --- a/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h +++ b/tests/unittests/media/server/service/mediaPipelineService/MediaPipelineServiceTestsFixture.h @@ -64,10 +64,16 @@ class MediaPipelineServiceTests : public testing::Test void mediaPipelineWillFailToHaveData(); void mediaPipelineWillGetPosition(); void mediaPipelineWillFailToGetPosition(); + void mediaPipelineWillGetDuration(); + void mediaPipelineWillFailToGetDuration(); void mediaPipelineWillSetImmediateOutput(); void mediaPipelineWillFailToSetImmediateOutput(); + void mediaPipelineWillSetReportDecodeErrors(); + void mediaPipelineWillFailToSetReportDecodeErrors(); void mediaPipelineWillGetImmediateOutput(); void mediaPipelineWillFailToGetImmediateOutput(); + void mediaPipelineWillGetQueuedFrames(); + void mediaPipelineWillFailToGetQueuedFrames(); void mediaPipelineWillGetStats(); void mediaPipelineWillFailToGetStats(); void mediaPipelineWillRenderFrame(); @@ -156,10 +162,16 @@ class MediaPipelineServiceTests : public testing::Test void haveDataShouldFail(); void getPositionShouldSucceed(); void getPositionShouldFail(); + void getDurationShouldSucceed(); + void getDurationShouldFail(); + void setReportDecodeErrorsShouldSucceed(); + void setReportDecodeErrorsShouldFail(); void setImmediateOutputShouldSucceed(); void setImmediateOutputShouldFail(); void getImmediateOutputShouldSucceed(); void getImmediateOutputShouldFail(); + void getQueuedFramesShouldSucceed(); + void getQueuedFramesShouldFail(); void getStatsShouldSucceed(); void getStatsShouldFail(); void getSupportedMimeTypesSucceed(); diff --git a/wrappers/include/GstWrapper.h b/wrappers/include/GstWrapper.h index 47a274364..688c35e9c 100644 --- a/wrappers/include/GstWrapper.h +++ b/wrappers/include/GstWrapper.h @@ -198,6 +198,11 @@ class GstWrapper : public IGstWrapper return gst_element_query_position(element, format, cur); } + gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) override + { + return gst_element_query_duration(element, format, duration); + } + GstPad *gstGhostPadNew(const gchar *name, GstPad *target) override { return gst_ghost_pad_new(name, target); } void gstPadSetQueryFunction(GstPad *pad, GstPadQueryFunction query) override diff --git a/wrappers/interface/IGstWrapper.h b/wrappers/interface/IGstWrapper.h index 36daf4349..75fe01081 100644 --- a/wrappers/interface/IGstWrapper.h +++ b/wrappers/interface/IGstWrapper.h @@ -421,6 +421,17 @@ class IGstWrapper */ virtual gboolean gstElementQueryPosition(GstElement *element, GstFormat format, gint64 *cur) = 0; + /** + * @brief Queries an element (usually top-level pipeline or playbin element) for the total stream duration in nanoseconds. + * + * @param[in] element : a GstElement to invoke the duration query on. + * @param[in] format : the GstFormat requested + * @param[out] duration : A location in which to store the total duration, or NULL. + * + * @retval TRUE on success, FALSE otherwise. + */ + virtual gboolean gstElementQueryDuration(GstElement *element, GstFormat format, gint64 *duration) = 0; + /** * @brief Create a new ghost pad. * From f0be8d690393171d8da239343d4906d140e8f382 Mon Sep 17 00:00:00 2001 From: narenr94 <51772499+narenr94@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:42:35 +0530 Subject: [PATCH 4/6] master sync (#491) Co-authored-by: Koky2701 <90915184+Koky2701@users.noreply.github.com> Co-authored-by: Marcin Wojciechowski <105790697+skywojciechowskim@users.noreply.github.com> From 753224abba9e64a1552431d5a91a3c35b2e61749 Mon Sep 17 00:00:00 2001 From: nrames759 Date: Wed, 29 Apr 2026 13:45:16 +0530 Subject: [PATCH 5/6] copilot review address --- .../gstplayer/source/GstGenericPlayer.cpp | 2 +- media/server/gstplayer/source/GstSrc.cpp | 30 +++++++++++-------- .../gstplayer/source/GstWebAudioPlayer.cpp | 2 ++ .../source/tasks/webAudio/WriteBuffer.cpp | 5 ++-- .../common/WebAudioTasksTestsBase.cpp | 2 +- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 3336f7d40..975c2d516 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -1329,7 +1329,7 @@ void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaT { #ifdef RIALTO_ENABLE_BUFFER_SIZE_LOGGING gsize bufferSize = gst_buffer_get_size(buffer); - GST_DEBUG("Pushing %s buffer of size: %zu bytes", + RIALTO_SERVER_LOG_DEBUG("Pushing %s buffer of size: %zu bytes", common::convertMediaSourceType(mediaType), bufferSize); #endif m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(streamInfo.appSrc), buffer); diff --git a/media/server/gstplayer/source/GstSrc.cpp b/media/server/gstplayer/source/GstSrc.cpp index 7955af23c..5d33c26a6 100644 --- a/media/server/gstplayer/source/GstSrc.cpp +++ b/media/server/gstplayer/source/GstSrc.cpp @@ -403,12 +403,7 @@ void GstSrc::setupAndAddAppSrc(IDecryptionService *decryptionService, GstElement // To validate these assumptions, build with -DRIALTO_ENABLE_BUFFER_SIZE_LOGGING to log actual buffer sizes. // See GstPlayerConfig.h to adjust these values. - // Queue size in bytes (for GStreamer < 1.20 fallback) - const std::unordered_map queueSize = - {{firebolt::rialto::MediaSourceType::VIDEO, kVideoMaxBytes}, - {firebolt::rialto::MediaSourceType::AUDIO, kAudioMaxBytes}, - {firebolt::rialto::MediaSourceType::SUBTITLE, kSubtitleMaxBytes}}; - +#if GST_CHECK_VERSION(1, 20, 0) // Queue size in buffer counts (for GStreamer >= 1.20) // See GstPlayerConfig.h for detailed documentation on these values. const std::unordered_map queueBuffers = @@ -416,21 +411,32 @@ void GstSrc::setupAndAddAppSrc(IDecryptionService *decryptionService, GstElement {firebolt::rialto::MediaSourceType::AUDIO, kAudioMaxBuffers}, {firebolt::rialto::MediaSourceType::SUBTITLE, kSubtitleMaxBuffers}}; - auto sizeIt = queueSize.find(type); auto buffersIt = queueBuffers.find(type); - if (sizeIt != queueSize.end() && buffersIt != queueBuffers.end()) + if (buffersIt != queueBuffers.end()) { - // Use buffer-based limits on GStreamer 1.20+, fall back to byte-based on older versions -#if GST_CHECK_VERSION(1, 20, 0) m_gstWrapper->gstAppSrcSetMaxBuffers(GST_APP_SRC(streamInfo.appSrc), buffersIt->second); + } + else + { + GST_WARNING_OBJECT(source, "Could not find max-buffers value for appsrc"); + } #else + // Queue size in bytes (for GStreamer < 1.20) + const std::unordered_map queueSize = + {{firebolt::rialto::MediaSourceType::VIDEO, kVideoMaxBytes}, + {firebolt::rialto::MediaSourceType::AUDIO, kAudioMaxBytes}, + {firebolt::rialto::MediaSourceType::SUBTITLE, kSubtitleMaxBytes}}; + + auto sizeIt = queueSize.find(type); + if (sizeIt != queueSize.end()) + { m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(streamInfo.appSrc), sizeIt->second); -#endif } else { - GST_WARNING_OBJECT(source, "Could not find max-bytes/max-buffers value for appsrc"); + GST_WARNING_OBJECT(source, "Could not find max-bytes value for appsrc"); } +#endif m_gstWrapper->gstAppSrcSetStreamType(GST_APP_SRC(streamInfo.appSrc), GST_APP_STREAM_TYPE_SEEKABLE); diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index f1b3f8c83..4176a6ec9 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -168,6 +168,8 @@ bool GstWebAudioPlayer::initWebAudioPipeline(const uint32_t priority) // See GstPlayerConfig.h to adjust these values. #if GST_CHECK_VERSION(1, 20, 0) m_gstWrapper->gstAppSrcSetMaxBuffers(GST_APP_SRC(m_context.source), kWebAudioMaxBuffers); + // Also set max-bytes on 1.20+ to keep current-level-bytes bounded and prevent underflow in WriteBuffer + m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(m_context.source), kWebAudioMaxBytes); #else m_gstWrapper->gstAppSrcSetMaxBytes(GST_APP_SRC(m_context.source), kWebAudioMaxBytes); #endif diff --git a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp index 63914f5d4..3a16cc481 100644 --- a/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp +++ b/media/server/gstplayer/source/tasks/webAudio/WriteBuffer.cpp @@ -44,7 +44,8 @@ void WriteBuffer::execute() const { RIALTO_SERVER_LOG_DEBUG("Executing WriteBuffer"); - uint64_t freeBytes = kWebAudioMaxBytes - m_gstWrapper->gstAppSrcGetCurrentLevelBytes(GST_APP_SRC(m_context.source)); + uint64_t currentLevelBytes = m_gstWrapper->gstAppSrcGetCurrentLevelBytes(GST_APP_SRC(m_context.source)); + uint64_t freeBytes = (currentLevelBytes >= kWebAudioMaxBytes) ? 0 : (kWebAudioMaxBytes - currentLevelBytes); uint64_t maxBytesToWrite = std::min(freeBytes, m_mainLength + m_wrapLength); uint64_t bytesToWrite = maxBytesToWrite - (maxBytesToWrite % m_context.bytesPerSample); uint64_t bytesWritten = 0; @@ -82,7 +83,7 @@ void WriteBuffer::execute() const #ifdef RIALTO_ENABLE_BUFFER_SIZE_LOGGING gsize bufferSize = gst_buffer_get_size(gstBuffer); - GST_DEBUG("Pushing WebAudio buffer of size: %zu bytes", bufferSize); + RIALTO_SERVER_LOG_DEBUG("Pushing WebAudio buffer of size: %zu bytes", bufferSize); #endif if (GST_FLOW_OK != m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(m_context.source), gstBuffer)) diff --git a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp index 727f1421c..86e886bd1 100644 --- a/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/webAudioPlayer/common/WebAudioTasksTestsBase.cpp @@ -44,7 +44,7 @@ std::shared_ptr testContext; constexpr double kVolume{0.7}; constexpr uint64_t kChannelMask{5}; constexpr uint32_t kBytesPerSample{4}; -constexpr uint32_t kMainLength = firebolt::rialto::server::kMaxWebAudioBytes - 40; +constexpr uint32_t kMainLength = firebolt::rialto::server::kWebAudioMaxBytes - 40; constexpr uint32_t kWrapLength = 40; constexpr uint32_t kUnalignedWriteSize = kMainLength + kWrapLength - kBytesPerSample; const std::string kAudioMimeType{"audio/x-raw"}; From a6b562a0f6daec78a02217096d847b3e9bed72ec Mon Sep 17 00:00:00 2001 From: nrames759 Date: Wed, 29 Apr 2026 13:56:37 +0530 Subject: [PATCH 6/6] copilot comment addres 2 --- CMakeLists.txt | 2 +- media/server/gstplayer/include/WebAudioPlayerContext.h | 2 +- media/server/gstplayer/source/GstGenericPlayer.cpp | 4 ++-- media/server/gstplayer/source/GstSrc.cpp | 4 ++-- media/server/gstplayer/source/GstWebAudioPlayer.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ebdc63b..bc9ff14ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ if ( RIALTO_LOG_DEBUG_ENABLED ) add_compile_definitions( RIALTO_LOG_DEBUG_ENABLED ) endif() -add_compile_definitions( RIALTO_ENABLE_BUFFER_SIZE_LOGGING ) +# add_compile_definitions( RIALTO_ENABLE_BUFFER_SIZE_LOGGING ) #check if TextTrack plugin exists in Thunder find_path( TEXT_TRACK_INCLUDE_DIR NAMES WPEFramework/interfaces/ITextTrack.h ) diff --git a/media/server/gstplayer/include/WebAudioPlayerContext.h b/media/server/gstplayer/include/WebAudioPlayerContext.h index 543174dc6..6f87b7b95 100644 --- a/media/server/gstplayer/include/WebAudioPlayerContext.h +++ b/media/server/gstplayer/include/WebAudioPlayerContext.h @@ -28,8 +28,8 @@ #include #include -#include "IGstSrc.h" #include "GstPlayerConfig.h" +#include "IGstSrc.h" namespace firebolt::rialto::server { diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 975c2d516..752929b35 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -1329,8 +1329,8 @@ void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaT { #ifdef RIALTO_ENABLE_BUFFER_SIZE_LOGGING gsize bufferSize = gst_buffer_get_size(buffer); - RIALTO_SERVER_LOG_DEBUG("Pushing %s buffer of size: %zu bytes", - common::convertMediaSourceType(mediaType), bufferSize); + RIALTO_SERVER_LOG_DEBUG("Pushing %s buffer of size: %zu bytes", common::convertMediaSourceType(mediaType), + bufferSize); #endif m_gstWrapper->gstAppSrcPushBuffer(GST_APP_SRC(streamInfo.appSrc), buffer); } diff --git a/media/server/gstplayer/source/GstSrc.cpp b/media/server/gstplayer/source/GstSrc.cpp index 5d33c26a6..911f15c39 100644 --- a/media/server/gstplayer/source/GstSrc.cpp +++ b/media/server/gstplayer/source/GstSrc.cpp @@ -19,8 +19,8 @@ #include -#include "GstSrc.h" #include "GstPlayerConfig.h" +#include "GstSrc.h" #include "GstTextTrackSinkFactory.h" #include "RialtoServerLogging.h" #include @@ -402,7 +402,7 @@ void GstSrc::setupAndAddAppSrc(IDecryptionService *decryptionService, GstElement // The buffer counts are computed to maintain similar memory footprint as the byte limits. // To validate these assumptions, build with -DRIALTO_ENABLE_BUFFER_SIZE_LOGGING to log actual buffer sizes. // See GstPlayerConfig.h to adjust these values. - + #if GST_CHECK_VERSION(1, 20, 0) // Queue size in buffer counts (for GStreamer >= 1.20) // See GstPlayerConfig.h for detailed documentation on these values. diff --git a/media/server/gstplayer/source/GstWebAudioPlayer.cpp b/media/server/gstplayer/source/GstWebAudioPlayer.cpp index 4176a6ec9..f61910a63 100644 --- a/media/server/gstplayer/source/GstWebAudioPlayer.cpp +++ b/media/server/gstplayer/source/GstWebAudioPlayer.cpp @@ -163,7 +163,7 @@ bool GstWebAudioPlayer::initWebAudioPipeline(const uint32_t priority) RIALTO_SERVER_LOG_ERROR("Failed to create the appsrc"); return false; } - + // Set queue limits: Use buffer-based on GStreamer 1.20+, byte-based on older versions. // See GstPlayerConfig.h to adjust these values. #if GST_CHECK_VERSION(1, 20, 0)