Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions media/client/ipc/include/MediaPipelineIpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ class MediaPipelineIpc : public IMediaPipelineIpc, public IpcModule
*/
void onBufferUnderflow(const std::shared_ptr<firebolt::rialto::BufferUnderflowEvent> &event);

/**
* @brief Handler for a first frame received notification from the server.
*
* @param[in] event : The first frame received event structure.
*/
void onFirstFrameReceived(const std::shared_ptr<firebolt::rialto::FirstFrameReceivedEvent> &event);

/**
* @brief Handler for a playback error notification from the server.
*
Expand Down
9 changes: 9 additions & 0 deletions media/client/ipc/interface/IMediaPipelineIpcClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ class IMediaPipelineIpcClient
*/
virtual void notifyBufferUnderflow(int32_t sourceId) = 0;

/**
* @brief Notifies the client that the first frame has been received.
*
* Notification shall be sent whenever a video/audio first frame is received
*
* @param[in] sourceId : The id of the source that received the first frame
*/
virtual void notifyFirstFrameReceived(int32_t sourceId) = 0;

/**
* @brief Notifies the client that a non-fatal error has occurred in the player.
*
Expand Down
16 changes: 16 additions & 0 deletions media/client/ipc/source/MediaPipelineIpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ bool MediaPipelineIpc::subscribeToEvents(const std::shared_ptr<ipc::IChannel> &i
return false;
m_eventTags.push_back(eventTag);

eventTag = ipcChannel->subscribe<firebolt::rialto::FirstFrameReceivedEvent>(
[this](const std::shared_ptr<firebolt::rialto::FirstFrameReceivedEvent> &event)
{ m_eventThread->add(&MediaPipelineIpc::onFirstFrameReceived, this, event); });
if (eventTag < 0)
return false;
m_eventTags.push_back(eventTag);

eventTag = ipcChannel->subscribe<firebolt::rialto::PlaybackErrorEvent>(
[this](const std::shared_ptr<firebolt::rialto::PlaybackErrorEvent> &event)
{ m_eventThread->add(&MediaPipelineIpc::onPlaybackError, this, event); });
Expand Down Expand Up @@ -1533,6 +1540,15 @@ void MediaPipelineIpc::onBufferUnderflow(const std::shared_ptr<firebolt::rialto:
}
}

void MediaPipelineIpc::onFirstFrameReceived(const std::shared_ptr<firebolt::rialto::FirstFrameReceivedEvent> &event)
{
// Ignore event if not for this session
if (event->session_id() == m_sessionId)
{
m_mediaPipelineIpcClient->notifyFirstFrameReceived(event->source_id());
}
}

void MediaPipelineIpc::onPlaybackError(const std::shared_ptr<firebolt::rialto::PlaybackErrorEvent> &event)
{
// Ignore event if not for this session
Expand Down
2 changes: 2 additions & 0 deletions media/client/main/include/MediaPipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ class MediaPipeline : public IMediaPipelineAndIControlClient, public IMediaPipel

void notifyBufferUnderflow(int32_t sourceId) override;

void notifyFirstFrameReceived(int32_t sourceId) override;

void notifyPlaybackError(int32_t sourceId, PlaybackError error) override;

void notifySourceFlushed(int32_t sourceId) override;
Expand Down
11 changes: 11 additions & 0 deletions media/client/main/source/MediaPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,17 @@ void MediaPipeline::notifyBufferUnderflow(int32_t sourceId)
}
}

void MediaPipeline::notifyFirstFrameReceived(int32_t sourceId)
{
RIALTO_CLIENT_LOG_DEBUG("entry:");

std::shared_ptr<IMediaPipelineClient> client = m_mediaPipelineClient.lock();
if (client)
{
client->notifyFirstFrameReceived(sourceId);
}
}

void MediaPipeline::notifyPlaybackError(int32_t sourceId, PlaybackError error)
{
RIALTO_CLIENT_LOG_DEBUG("entry:");
Expand Down
9 changes: 9 additions & 0 deletions media/public/include/IMediaPipelineClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ class IMediaPipelineClient
*/
virtual void notifyBufferUnderflow(int32_t sourceId) = 0;

/**
* @brief Notifies the client that the first frame has been received.
*
* Notification shall be sent whenever a video/audio first frame is received
*
* @param[in] sourceId : The id of the source that received the first frame
*/
virtual void notifyFirstFrameReceived(int32_t sourceId) = 0;

/**
* @brief Notifies the client that a non-fatal error has occurred in the player.
*
Expand Down
1 change: 1 addition & 0 deletions media/server/gstplayer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ add_library(
source/tasks/generic/DeepElementAdded.cpp
source/tasks/generic/EnoughData.cpp
source/tasks/generic/Eos.cpp
source/tasks/generic/FirstFrameReceived.cpp
source/tasks/generic/FinishSetupSource.cpp
source/tasks/generic/Flush.cpp
source/tasks/generic/GenericPlayerTaskFactory.cpp
Expand Down
15 changes: 15 additions & 0 deletions media/server/gstplayer/include/GenericPlayerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,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.
*/
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
5 changes: 5 additions & 0 deletions media/server/gstplayer/include/GstGenericPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva
void scheduleEnoughData(GstAppSrc *src) override;
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
28 changes: 28 additions & 0 deletions media/server/gstplayer/include/IGstGenericPlayerPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,34 @@ class IGstGenericPlayerPrivate
*/
virtual void scheduleVideoUnderflow() = 0;

/**
* @brief Schedules first video frame received task. Called by the worker thread.
*/
virtual void scheduleFirstVideoFrameReceived() = 0;

/**
* @brief Schedules first audio frame received task. Called by the worker thread.
*/
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
2 changes: 2 additions & 0 deletions media/server/gstplayer/include/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ bool isAudio(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstEleme
bool isVideo(const firebolt::rialto::wrappers::IGstWrapper &gstWrapper, GstElement *element);
std::optional<std::string> getUnderflowSignalName(const firebolt::rialto::wrappers::IGlibWrapper &glibWrapper,
GstElement *element);
std::optional<std::string> getFirstFrameSignalName(const firebolt::rialto::wrappers::IGlibWrapper &glibWrapper,
GstElement *element);
GstCaps *createCapsFromMediaSource(const std::shared_ptr<firebolt::rialto::wrappers::IGstWrapper> &gstWrapper,
const std::shared_ptr<firebolt::rialto::wrappers::IGlibWrapper> &glibWrapper,
const std::unique_ptr<IMediaPipeline::MediaSource> &source);
Expand Down
13 changes: 13 additions & 0 deletions media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,19 @@ class IGenericPlayerTaskFactory
virtual std::unique_ptr<IPlayerTask> createUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player,
bool underflowEnabled, MediaSourceType sourceType) const = 0;

/**
* @brief Creates a FirstFrameReceived task.
*
* @param[in] context : The GstGenericPlayer context
* @param[in] player : The GstPlayer instance
* @param[in] sourceType : Source type (audio or video).
*
* @retval the new FirstFrameReceived task instance.
*/
virtual std::unique_ptr<IPlayerTask> createFirstFrameReceived(GenericPlayerContext &context,
IGstGenericPlayerPrivate &player,
MediaSourceType sourceType) const = 0;

/**
* @brief Creates an UpdatePlaybackGroup task.
*
Expand Down
47 changes: 47 additions & 0 deletions media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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_FIRST_FRAME_RECEIVED_H_
#define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_

#include "GenericPlayerContext.h"
#include "IGstGenericPlayerClient.h"
#include "IGstGenericPlayerPrivate.h"
#include "IPlayerTask.h"

namespace firebolt::rialto::server::tasks::generic
{
class FirstFrameReceived : public IPlayerTask
{
public:
FirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, IGstGenericPlayerClient *client,
MediaSourceType sourceType);
~FirstFrameReceived() override;

void execute() const override;

private:
GenericPlayerContext &m_context;
IGstGenericPlayerPrivate &m_player;
IGstGenericPlayerClient *m_gstPlayerClient;
MediaSourceType m_sourceType;
};
} // namespace firebolt::rialto::server::tasks::generic

#endif // FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory
IGstGenericPlayerPrivate &player) const override;
std::unique_ptr<IPlayerTask> createUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player,
bool underflowEnable, MediaSourceType sourceType) const override;
std::unique_ptr<IPlayerTask> createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player,
MediaSourceType sourceType) const override;
std::unique_ptr<IPlayerTask> createUpdatePlaybackGroup(GenericPlayerContext &context,
IGstGenericPlayerPrivate &player, GstElement *typefind,
const GstCaps *caps) const override;
Expand Down
9 changes: 9 additions & 0 deletions media/server/gstplayer/interface/IGstGenericPlayerClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ class IGstGenericPlayerClient
*/
virtual void notifyBufferUnderflow(MediaSourceType mediaSourceType) = 0;

/**
* @brief Notifies the client that the first frame has been received.
*
* Notification shall be sent whenever a video/audio first frame is received
*
* @param[in] mediaSourceType : The type of the source that received the first frame
*/
virtual void notifyFirstFrameReceived(MediaSourceType mediaSourceType) = 0;

/**
* @brief Notifies the client that a non-fatal error has occurred in the player.
*
Expand Down
60 changes: 60 additions & 0 deletions media/server/gstplayer/source/GstGenericPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ void GstGenericPlayer::resetWorkerThread()

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

if (m_finishSourceSetupTimer && m_finishSourceSetupTimer->isActive())
{
m_finishSourceSetupTimer->cancel();
Expand Down Expand Up @@ -502,6 +504,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 @@ -1674,11 +1681,64 @@ void GstGenericPlayer::scheduleVideoUnderflow()
}
}

void GstGenericPlayer::scheduleFirstVideoFrameReceived()
{
if (m_workerThread)
{
m_workerThread->enqueueTask(m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::VIDEO));
}
}

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

m_context.firstAudioFrameReceived = true;

if (m_workerThread)
{
m_workerThread->enqueueTask(m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::AUDIO));
}
}
Comment on lines +1692 to +1705

void GstGenericPlayer::setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id)
{
clearAudioFirstFrameFallbackProbe();

m_context.audioFirstFrameProbePad = pad;
m_context.audioFirstFrameProbeId = id;
}

void GstGenericPlayer::clearAudioFirstFrameFallbackProbe()
{
if (m_context.audioFirstFrameProbePad && m_context.audioFirstFrameProbeId != 0)
{
m_gstWrapper->gstPadRemoveProbe(m_context.audioFirstFrameProbePad, m_context.audioFirstFrameProbeId);
}

clearAudioFirstFrameFallbackProbeState();
}

void GstGenericPlayer::clearAudioFirstFrameFallbackProbeState()
{
if (m_context.audioFirstFrameProbePad)
{
m_gstWrapper->gstObjectUnref(m_context.audioFirstFrameProbePad);
m_context.audioFirstFrameProbePad = nullptr;
}

m_context.audioFirstFrameProbeId = 0;
}

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


void GstGenericPlayer::cancelUnderflow(firebolt::rialto::MediaSourceType mediaSource)
{
auto elem = m_context.streamInfo.find(mediaSource);
Expand Down
Loading
Loading