Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
544ee9a
Add first-video-frame-callback signal implementation.
Apr 29, 2026
d615a27
Group signals for first frame callback and update related connection.
May 11, 2026
bc2f6e4
Added UTs and additional fixes.
May 11, 2026
5cde290
Added more UTs and CTs to provide expected coverage.
May 13, 2026
1547793
Adding fixes for reported issues by github copilot.
May 14, 2026
ea67730
audio_ff on top of video_ff
Jun 1, 2026
60dd7df
Update GstGenericPlayer.cpp
balasaraswathy-n Jun 1, 2026
7ac9cd1
rebasing
Jun 11, 2026
9e21a7c
Fix reattaching audio source at dynamic audio codec change. (#530) (#…
balasaraswathy-n Jun 19, 2026
49b5622
Update FirstFrameReceivedTest.cpp
balasaraswathy-n Jun 19, 2026
57dea1d
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
9cfddfd
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
22e9332
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
47c065d
Merge branch 'master' into feature/aff_on_vff
balasaraswathy-n Jun 19, 2026
9e80e2e
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
5f2b1ac
Update SetupElement.cpp
balasaraswathy-n Jun 19, 2026
2de576f
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
b067062
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
57fe064
Potential fix for pull request finding
balasaraswathy-n Jun 19, 2026
7349215
Memory optimisations in Rialto (#534) (#544)
balasaraswathy-n Jun 26, 2026
cad9eed
Memory optimisations in Rialto (#534) (#549)
balasaraswathy-n Jun 29, 2026
b494b29
Memory optimisations in Rialto (#534) (#550)
balasaraswathy-n Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions media/server/gstplayer/include/GenericPlayerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,21 @@ struct GenericPlayerContext
* @brief Profiler for player pipeline
*/
std::unique_ptr<IGstProfiler> gstProfiler;

/**
* @brief True when first audio frame has already been scheduled for the current audio source lifecycle.
*/
std::atomic_bool firstAudioFrameReceived{false};

/**
* @brief Fallback probe id for first audio frame detection on sink pad.
*/
gulong audioFirstFrameProbeId{0};

/**
* @brief Fallback sink pad that owns audio first frame probe.
*/
GstPad *audioFirstFrameProbePad{nullptr};
};
} // namespace firebolt::rialto::server

Expand Down
4 changes: 4 additions & 0 deletions media/server/gstplayer/include/GstGenericPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva
void scheduleAudioUnderflow() override;
void scheduleVideoUnderflow() override;
void scheduleFirstVideoFrameReceived() override;
void scheduleFirstAudioFrameReceived() override;
void setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) override;
void clearAudioFirstFrameFallbackProbe() override;
void clearAudioFirstFrameFallbackProbeState() override;
void scheduleAllSourcesAttached() override;
bool setVideoSinkRectangle() override;
bool setImmediateOutput() override;
Expand Down
23 changes: 23 additions & 0 deletions media/server/gstplayer/include/IGstGenericPlayerPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,29 @@ class IGstGenericPlayerPrivate
*/
virtual void scheduleFirstVideoFrameReceived() = 0;

/**
* @brief Schedules first audio frame received task. Called by a GStreamer thread callback.
*/
virtual void scheduleFirstAudioFrameReceived() = 0;

/**
* @brief Stores audio first-frame fallback probe state.
*
* @param[in] pad : sink pad with installed probe
* @param[in] id : probe id
*/
virtual void setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) = 0;

/**
* @brief Removes and clears audio first-frame fallback probe state.
*/
virtual void clearAudioFirstFrameFallbackProbe() = 0;

/**
* @brief Clears audio first-frame fallback probe state without removing the probe.
*/
virtual void clearAudioFirstFrameFallbackProbeState() = 0;

/**
* @brief Schedules all sources attached task. Called by the worker thread.
*/
Expand Down
16 changes: 7 additions & 9 deletions media/server/gstplayer/source/GstCapabilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,17 +260,15 @@ std::vector<std::string> GstCapabilities::getSupportedProperties(MediaSourceType
continue;
}

GstElement *elementObj{nullptr};

// We instantiate an object because fetching the class, even after gstPluginFeatureLoad,
// was found to sometimes return a class with no properties. A code branch is
// kept with this feature "supportedPropertiesViaClass"
elementObj = m_gstWrapper->gstElementFactoryCreate(factory, nullptr);
if (elementObj)
GType elementType = m_gstWrapper->gstElementFactoryGetElementType(factory);
if (elementType == G_TYPE_INVALID)
continue;
gpointer elementClass = m_glibWrapper->gTypeClassRef(elementType);
if (elementClass)
{
GParamSpec **props;
guint nProps;
props = m_glibWrapper->gObjectClassListProperties(G_OBJECT_GET_CLASS(elementObj), &nProps);
props = m_glibWrapper->gObjectClassListProperties(G_OBJECT_CLASS(elementClass), &nProps);
if (props)
{
for (guint j = 0; j < nProps && !propertiesToLookFor.empty(); ++j)
Expand All @@ -286,7 +284,7 @@ std::vector<std::string> GstCapabilities::getSupportedProperties(MediaSourceType
}
m_glibWrapper->gFree(props);
}
m_gstWrapper->gstObjectUnref(elementObj);
m_glibWrapper->gTypeClassUnref(elementClass);
}
}

Expand Down
92 changes: 91 additions & 1 deletion media/server/gstplayer/source/GstGenericPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <cinttypes>
#include <cstring>
#include <ctime>
#include <malloc.h>
#include <stdexcept>

#include "FlushWatcher.h"
Expand Down Expand Up @@ -303,6 +304,8 @@ void GstGenericPlayer::resetWorkerThread()

void GstGenericPlayer::termPipeline()
{
clearAudioFirstFrameFallbackProbe();

if (m_finishSourceSetupTimer && m_finishSourceSetupTimer->isActive())
{
m_finishSourceSetupTimer->cancel();
Expand Down Expand Up @@ -355,6 +358,9 @@ void GstGenericPlayer::termPipeline()
// Delete the pipeline
m_gstWrapper->gstObjectUnref(m_context.pipeline);

m_glibWrapper->gThreadPoolStopUnusedThreads();
malloc_trim(0);
Comment on lines +361 to +362

RIALTO_SERVER_LOG_MIL("RialtoServer's pipeline terminated");
}

Expand All @@ -363,7 +369,9 @@ unsigned GstGenericPlayer::getGstPlayFlag(const char *nick)
GFlagsClass *flagsClass =
static_cast<GFlagsClass *>(m_glibWrapper->gTypeClassRef(m_glibWrapper->gTypeFromName("GstPlayFlags")));
GFlagsValue *flag = m_glibWrapper->gFlagsGetValueByNick(flagsClass, nick);
return flag ? flag->value : 0;
unsigned result = flag ? flag->value : 0;
m_glibWrapper->gTypeClassUnref(flagsClass);
return result;
}

void GstGenericPlayer::setupSource(GstElement *pipeline, GstElement *source, GstGenericPlayer *self)
Expand Down Expand Up @@ -532,6 +540,11 @@ GstElement *GstGenericPlayer::getSink(const MediaSourceType &mediaSourceType) co

void GstGenericPlayer::setSourceFlushed(const MediaSourceType &mediaSourceType)
{
if (mediaSourceType == MediaSourceType::AUDIO)
{
m_context.firstAudioFrameReceived = false;
clearAudioFirstFrameFallbackProbe();
}
m_flushWatcher->setFlushed(mediaSourceType);
}

Expand Down Expand Up @@ -1719,11 +1732,88 @@ void GstGenericPlayer::scheduleFirstVideoFrameReceived()
}
}

void GstGenericPlayer::scheduleFirstAudioFrameReceived()
{
if (m_context.firstAudioFrameReceived || !m_workerThread)
{
return;
}

m_context.firstAudioFrameReceived = true;
m_workerThread->enqueueTask(m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::AUDIO));
}
Comment on lines +1735 to +1744

void GstGenericPlayer::setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id)
{
GstPad *oldPad{nullptr};
gulong oldId{0};

{
std::lock_guard<std::mutex> lock{m_context.propertyMutex};
oldPad = m_context.audioFirstFrameProbePad;
oldId = m_context.audioFirstFrameProbeId;
m_context.audioFirstFrameProbePad = pad;
m_context.audioFirstFrameProbeId = id;
}

if (oldPad && oldId != 0)
{
m_gstWrapper->gstPadRemoveProbe(oldPad, oldId);
}

if (oldPad)
{
m_gstWrapper->gstObjectUnref(oldPad);
}
}

void GstGenericPlayer::clearAudioFirstFrameFallbackProbe()
{
GstPad *pad{nullptr};
gulong id{0};

{
std::lock_guard<std::mutex> lock{m_context.propertyMutex};
pad = m_context.audioFirstFrameProbePad;
id = m_context.audioFirstFrameProbeId;
m_context.audioFirstFrameProbePad = nullptr;
m_context.audioFirstFrameProbeId = 0;
}

if (pad && id != 0)
{
m_gstWrapper->gstPadRemoveProbe(pad, id);
}

if (pad)
{
m_gstWrapper->gstObjectUnref(pad);
}
}

void GstGenericPlayer::clearAudioFirstFrameFallbackProbeState()
{
GstPad *pad{nullptr};

{
std::lock_guard<std::mutex> lock{m_context.propertyMutex};
pad = m_context.audioFirstFrameProbePad;
m_context.audioFirstFrameProbePad = nullptr;
m_context.audioFirstFrameProbeId = 0;
}

if (pad)
{
m_gstWrapper->gstObjectUnref(pad);
}
}

void GstGenericPlayer::scheduleAllSourcesAttached()
{
allSourcesAttached();
}


void GstGenericPlayer::cancelUnderflow(firebolt::rialto::MediaSourceType mediaSource)
{
auto elem = m_context.streamInfo.find(mediaSource);
Expand Down
10 changes: 10 additions & 0 deletions media/server/gstplayer/source/GstInitialiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "GstInitialiser.h"
#include "GstLogForwarding.h"
#include "IGlibWrapper.h"
#include "RialtoServerLogging.h"

namespace firebolt::rialto::server
Expand Down Expand Up @@ -68,6 +69,15 @@ void GstInitialiser::initialise(int *argc, char ***argv)

m_gstWrapper->gstInit(argc, argv);

auto glibWrapper = firebolt::rialto::wrappers::IGlibWrapperFactory::getFactory()->getGlibWrapper();
if (!glibWrapper)
{
RIALTO_SERVER_LOG_ERROR("Failed to create the glib wrapper");
return;
}
glibWrapper->gThreadPoolSetMaxUnusedThreads(2);
glibWrapper->gThreadPoolSetMaxIdleTime(5 * 1000); // 5 seconds

// remove rialto sinks from the registry
GstPlugin *rialtoPlugin =
m_gstWrapper->gstRegistryFindPlugin(m_gstWrapper->gstRegistryGet(), "rialtosinks");
Expand Down
2 changes: 1 addition & 1 deletion media/server/gstplayer/source/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
namespace
{
const char *underflowSignals[]{"buffer-underflow-callback", "vidsink-underflow-callback", "underrun-callback"};
const char *firstFrameSignals[]{"first-video-frame-callback"};
const char *firstFrameSignals[]{"first-video-frame-callback", "first-audio-frame", "first-audio-frame-callback"};

bool isType(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element, GstElementFactoryListType type)
{
Expand Down
2 changes: 2 additions & 0 deletions media/server/gstplayer/source/tasks/generic/AttachSamples.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ AttachSamples::AttachSamples(GenericPlayerContext &context,
{
// Catching error, but continuing as best as we can
RIALTO_SERVER_LOG_ERROR("Failed to get the video segment, reason: %s", e.what());
m_gstWrapper->gstBufferUnref(gstBuffer);
}
}
else if (mediaSegment->getType() == firebolt::rialto::MediaSourceType::AUDIO)
Expand All @@ -69,6 +70,7 @@ AttachSamples::AttachSamples(GenericPlayerContext &context,
{
// Catching error, but continuing as best as we can
RIALTO_SERVER_LOG_ERROR("Failed to get the audio segment, reason: %s", e.what());
m_gstWrapper->gstBufferUnref(gstBuffer);
}
}
else if (mediaSegment->getType() == firebolt::rialto::MediaSourceType::SUBTITLE)
Expand Down
3 changes: 3 additions & 0 deletions media/server/gstplayer/source/tasks/generic/AttachSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ void AttachSource::addSource() const
GstElement *appSrc = nullptr;
if (m_attachedSource->getType() == MediaSourceType::AUDIO)
{
m_context.firstAudioFrameReceived = false;
RIALTO_SERVER_LOG_MIL("Adding Audio appsrc with caps %s", capsStr);
appSrc = m_gstWrapper->gstElementFactoryMake("appsrc", "audsrc");
profilerInfo = "audsrc";
Expand Down Expand Up @@ -124,6 +125,8 @@ void AttachSource::addSource() const

void AttachSource::reattachAudioSource() const
{
m_context.firstAudioFrameReceived = false;

if (!m_player.reattachSource(m_attachedSource))
{
RIALTO_SERVER_LOG_ERROR("Reattaching source failed!");
Expand Down
59 changes: 59 additions & 0 deletions media/server/gstplayer/source/tasks/generic/SetupElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,38 @@ void firstVideoFrameCallback(GstElement *object, guint fifoDepth, gpointer queue
player->scheduleFirstVideoFrameReceived();
}

/**
* @brief Callback for first audio frame event from the emitting audio element. Called by the Gstreamer thread.
*
* @param[in] object : the object that emitted the signal
* @param[in] fifoDepth : the fifo depth (may be 0)
* @param[in] queueDepth : the queue depth (may be NULL)
* @param[in] self : The pointer to IGstGenericPlayerPrivate
*/
void firstAudioFrameCallback(GstElement *object, guint fifoDepth, gpointer queueDepth, gpointer self)
{
firebolt::rialto::server::IGstGenericPlayerPrivate *player =
static_cast<firebolt::rialto::server::IGstGenericPlayerPrivate *>(self);
player->scheduleFirstAudioFrameReceived();
}

/**
* @brief Fallback probe callback for first audio frame on sink pad.
*/
GstPadProbeReturn firstAudioFrameProbeCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self)
{
if (!(info->type & GST_PAD_PROBE_TYPE_BUFFER) || !GST_PAD_PROBE_INFO_BUFFER(info))
{
return GST_PAD_PROBE_OK;
}

firebolt::rialto::server::IGstGenericPlayerPrivate *player =
static_cast<firebolt::rialto::server::IGstGenericPlayerPrivate *>(self);
player->clearAudioFirstFrameFallbackProbeState();
player->scheduleFirstAudioFrameReceived();
return GST_PAD_PROBE_REMOVE;
}

/**
* @brief Callback for a autovideosink when a child has been added to the sink.
*
Expand Down Expand Up @@ -295,6 +327,33 @@ void SetupElement::execute() const
m_glibWrapper->gSignalConnect(m_element, firstFrameSignalName.value().c_str(),
G_CALLBACK(firstVideoFrameCallback), &m_player);
}
else if (isAudio(*m_gstWrapper, m_element))
{
Comment on lines 327 to +331
RIALTO_SERVER_LOG_INFO("Connecting first audio frame callback for signal: %s",
firstFrameSignalName.value().c_str());
m_glibWrapper->gSignalConnect(m_element, firstFrameSignalName.value().c_str(),
G_CALLBACK(firstAudioFrameCallback), &m_player);
}
}
else if (isAudioSink(*m_gstWrapper, m_element))
{
GstPad *sinkPad = m_gstWrapper->gstElementGetStaticPad(m_element, "sink");
if (sinkPad)
{
gulong probeId =
m_gstWrapper->gstPadAddProbe(sinkPad, GST_PAD_PROBE_TYPE_BUFFER, firstAudioFrameProbeCallback,
&m_player, nullptr);

if (probeId != 0)
{
RIALTO_SERVER_LOG_INFO("Installed first audio frame fallback probe on sink");
m_player.setAudioFirstFrameFallbackProbe(sinkPad, probeId);
}
else
{
m_gstWrapper->gstObjectUnref(sinkPad);
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions media/server/gstplayer/source/tasks/generic/Stop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Stop::~Stop()
void Stop::execute() const
{
RIALTO_SERVER_LOG_DEBUG("Executing Stop");
m_context.firstAudioFrameReceived = false;
m_player.clearAudioFirstFrameFallbackProbe();
m_player.stopPositionReportingAndCheckAudioUnderflowTimer();
m_player.stopNotifyPlaybackInfoTimer();
m_player.changePipelineState(GST_STATE_NULL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ void SynchroniseSubtitleClock::execute() const
else
{
RIALTO_SERVER_LOG_ERROR("Failed to create current-pts event");
m_gstWrapper->gstStructureFree(structure);
}
}
else
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/audio-first-frame/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-01
Loading
Loading