From a1b76a69177643756514da2008ad11f4c2b16189 Mon Sep 17 00:00:00 2001 From: N Date: Mon, 1 Jun 2026 22:09:43 +0530 Subject: [PATCH 1/3] Add support for 'first-audio-frame-callback' signal Summary: Add support for 'first-audio-frame-callback' signal to Rialto sink Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-17882 --- .../gstplayer/include/GenericPlayerContext.h | 15 ++ .../gstplayer/include/GstGenericPlayer.h | 4 + .../include/IGstGenericPlayerPrivate.h | 23 ++ .../gstplayer/source/GstGenericPlayer.cpp | 51 ++++ media/server/gstplayer/source/Utils.cpp | 2 +- .../source/tasks/generic/AttachSource.cpp | 3 + .../source/tasks/generic/SetupElement.cpp | 59 +++++ .../gstplayer/source/tasks/generic/Stop.cpp | 2 + .../changes/audio-first-frame/.openspec.yaml | 2 + openspec/changes/audio-first-frame/design.md | 86 +++++++ .../changes/audio-first-frame/proposal.md | 26 ++ .../specs/audio-first-frame/spec.md | 47 ++++ openspec/changes/audio-first-frame/tasks.md | 33 +++ .../tests/base/MediaPipelineTestMethods.cpp | 12 + .../tests/base/MediaPipelineTestMethods.h | 2 + .../tests/mse/FirstFrameNotificationTest.cpp | 10 +- .../FirstFrameNotificationTest.cpp | 225 ++++++++++++------ .../mediaPipeline/PipelinePropertyTest.cpp | 15 +- .../tests/mediaPipeline/UnderflowTest.cpp | 4 + .../GstGenericPlayerPrivateTest.cpp | 10 + .../common/GenericTasksTestsBase.cpp | 103 +++----- .../common/GenericTasksTestsContext.h | 1 + .../tasksTests/FirstFrameReceivedTest.cpp | 11 + .../gstplayer/GstGenericPlayerPrivateMock.h | 4 + 24 files changed, 596 insertions(+), 154 deletions(-) create mode 100644 openspec/changes/audio-first-frame/.openspec.yaml create mode 100644 openspec/changes/audio-first-frame/design.md create mode 100644 openspec/changes/audio-first-frame/proposal.md create mode 100644 openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md create mode 100644 openspec/changes/audio-first-frame/tasks.md diff --git a/media/server/gstplayer/include/GenericPlayerContext.h b/media/server/gstplayer/include/GenericPlayerContext.h index 929f58e81..2bbab9f3d 100644 --- a/media/server/gstplayer/include/GenericPlayerContext.h +++ b/media/server/gstplayer/include/GenericPlayerContext.h @@ -289,6 +289,21 @@ struct GenericPlayerContext * @brief Profiler for player pipeline */ std::unique_ptr 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 diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index f11021a12..f2e358167 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -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; diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 139078515..68c8452f2 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -66,6 +66,29 @@ class IGstGenericPlayerPrivate */ 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. */ diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 5c7453580..47ad93a81 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -304,6 +304,8 @@ void GstGenericPlayer::resetWorkerThread() void GstGenericPlayer::termPipeline() { + clearAudioFirstFrameFallbackProbe(); + if (m_finishSourceSetupTimer && m_finishSourceSetupTimer->isActive()) { m_finishSourceSetupTimer->cancel(); @@ -538,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); } @@ -1725,6 +1732,50 @@ void GstGenericPlayer::scheduleFirstVideoFrameReceived() } } +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)); + } +} + +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(); diff --git a/media/server/gstplayer/source/Utils.cpp b/media/server/gstplayer/source/Utils.cpp index fd93a2323..9d8ecdcd5 100644 --- a/media/server/gstplayer/source/Utils.cpp +++ b/media/server/gstplayer/source/Utils.cpp @@ -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) { diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index 1b3f9a03a..6f289e180 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -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"; @@ -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!"); diff --git a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index e2e7efd74..fa2542674 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -76,6 +76,39 @@ 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(self); + player->clearAudioFirstFrameFallbackProbe(); + 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(self); + player->clearAudioFirstFrameFallbackProbeState(); + player->scheduleFirstAudioFrameReceived(); + return GST_PAD_PROBE_REMOVE; +} + /** * @brief Callback for a autovideosink when a child has been added to the sink. * @@ -295,6 +328,32 @@ 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)) + { + 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); + } + } } } diff --git a/media/server/gstplayer/source/tasks/generic/Stop.cpp b/media/server/gstplayer/source/tasks/generic/Stop.cpp index bc566d6a7..7bfaaeaf4 100644 --- a/media/server/gstplayer/source/tasks/generic/Stop.cpp +++ b/media/server/gstplayer/source/tasks/generic/Stop.cpp @@ -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); diff --git a/openspec/changes/audio-first-frame/.openspec.yaml b/openspec/changes/audio-first-frame/.openspec.yaml new file mode 100644 index 000000000..a2168c37b --- /dev/null +++ b/openspec/changes/audio-first-frame/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-01 diff --git a/openspec/changes/audio-first-frame/design.md b/openspec/changes/audio-first-frame/design.md new file mode 100644 index 000000000..253af7f33 --- /dev/null +++ b/openspec/changes/audio-first-frame/design.md @@ -0,0 +1,86 @@ +## Context + +Rialto already supports first-frame notification for video and already has the downstream plumbing needed to propagate a first-frame event from gstplayer through the worker thread, server, IPC transport, and client callback. Audio playback currently lacks equivalent detection, which creates an observability gap even though the existing notification path is capable of carrying an audio first-frame event once one is detected. + +This change touches multiple layers: gstplayer element setup, first-frame scheduling, server-side forwarding, IPC transport, and client delivery. The design therefore focuses on how audio first-frame detection is introduced without changing the public callback or protobuf contract and without regressing the existing video path. + +## Goals / Non-Goals + +**Goals:** +- Add first-audio-frame detection in gstplayer using capability-based discovery instead of platform branching. +- Reuse the existing first-frame task scheduling and propagation path by routing audio notifications with `MediaSourceType::AUDIO`. +- Support audio sink implementations that do not expose callback-based first-frame detection by using a bounded fallback probe. +- Preserve existing public client callbacks and existing `FirstFrameReceivedEvent` IPC transport. +- Keep video first-frame behavior unchanged. + +**Non-Goals:** +- Redesign the current first-frame architecture. +- Introduce platform- or vendor-specific branches for audio first-frame support. +- Add a new public callback or a new protobuf event type for audio first-frame notification. +- Change behavior outside the first-frame detection and propagation path. + +## Decisions + +### Use role-specific capability detection during element setup + +Audio first-frame support will be discovered when gstplayer configures pipeline elements. Audio decoder elements will be checked for `first-audio-frame`, while audio sink elements will be checked for `first-audio-frame-callback`. + +This keeps detection aligned with the existing setup flow and avoids hard-coding platform names or element allowlists. It also lets decoder and sink behavior diverge cleanly where platform implementations expose different signaling mechanisms. + +Alternatives considered: +- Use platform-specific branching: rejected because it scales poorly across vendors and hard-codes knowledge that should remain capability based. +- Use a probe for all audio elements: rejected because native callback/signal support is cheaper and more precise when available. + +### Use sink-pad probing only as a fallback for audio sinks + +If an audio sink does not expose `first-audio-frame-callback`, Rialto will install a sink pad probe and treat the first valid audio buffer as the first-frame trigger. The probe will be removed immediately after the first valid trigger and also removed during teardown, flush, reset, or pipeline stop. + +This provides coverage for sink implementations that cannot emit a callback while keeping probe usage narrow and bounded. Restricting the fallback to audio sinks avoids adding speculative probe behavior to decoder elements where the expected mechanism is explicit capability detection. + +Alternatives considered: +- No fallback path: rejected because platforms without sink callback capability would remain unsupported. +- Probe both decoders and sinks: rejected because it broadens runtime overhead and increases ambiguity in which stage constitutes first-frame detection. + +### Reuse the existing first-frame scheduling and propagation path + +Once audio first-frame is detected, the implementation will schedule the existing first-frame handling flow and identify the source using `MediaSourceType::AUDIO`. Server-side forwarding, IPC serialization, and client dispatch will continue to use the existing `notifyFirstFrameReceived(...)` and `FirstFrameReceivedEvent` path. + +This minimizes surface area, keeps the client contract stable, and avoids introducing a parallel audio-specific notification stack. + +Alternatives considered: +- Add a dedicated audio-first-frame event type: rejected because it duplicates the current pipeline without adding functional value. +- Dispatch directly from gstplayer to clients: rejected because it bypasses existing threading and session/source mapping behavior. + +### Guard emission so exactly one first-frame event is sent per audio source lifecycle + +Both callback-based and probe-based detection may observe the same source lifecycle. The implementation will keep a one-shot guard for the relevant audio source/session so only one first-frame event is scheduled and propagated. Cleanup paths will remove probe state and clear any temporary tracking during teardown and reset operations. + +This preserves deterministic client behavior and prevents duplicate telemetry or callback delivery. + +Alternatives considered: +- Allow duplicate low-level detections and deduplicate later: rejected because duplicates would still traverse more of the pipeline and complicate server/client behavior. + +## Risks / Trade-offs + +- Duplicate detection from callback and probe paths -> Mitigation: keep a one-shot guard at the first scheduling point and remove fallback probes immediately after the first valid trigger. +- Incorrect trigger on non-audio or non-buffer sink activity -> Mitigation: only treat the first valid audio buffer as a trigger and ignore unrelated pad activity. +- Regression in existing video behavior -> Mitigation: leave the video detection path unchanged and run existing first-frame non-regression coverage alongside new audio tests. +- Threading issues if detection performs too much work on the GStreamer thread -> Mitigation: keep callbacks and probes minimal and schedule work onto the existing worker-thread path. +- Platform variability in element support -> Mitigation: prefer capability-based detection and limit fallback logic to the specific sink case where callback support is absent. + +## Migration Plan + +1. Implement audio capability detection during gstplayer setup for decoders and sinks. +2. Add sink fallback probe handling, including install, one-shot trigger, and cleanup paths. +3. Route detected audio first-frame events through the existing scheduling and propagation flow using `MediaSourceType::AUDIO`. +4. Add or update unit tests for setup-element detection behavior, fallback probing, and single-emission guarantees. +5. Add or update component coverage for end-to-end server and client delivery of audio first-frame notifications. +6. Validate that existing video first-frame tests remain unchanged. + +Rollback is low risk because the change is additive. If regressions are found, audio detection and fallback wiring can be removed while leaving the existing video path and notification contract intact. + +## Open Questions + +- Which exact gstplayer setup abstraction should own the audio sink probe lifecycle so cleanup is guaranteed across all teardown paths? +- Does any supported platform expose both decoder and sink audio first-frame signals simultaneously, and if so, where should the one-shot guard live to keep behavior deterministic? +- Are there existing test fixtures for sink pad probe behavior that can be extended, or does this change require new unit helpers for probe installation and cleanup validation? \ No newline at end of file diff --git a/openspec/changes/audio-first-frame/proposal.md b/openspec/changes/audio-first-frame/proposal.md new file mode 100644 index 000000000..c49fa4f40 --- /dev/null +++ b/openspec/changes/audio-first-frame/proposal.md @@ -0,0 +1,26 @@ +## Why + +Rialto already reports first-frame receipt for video, but it does not provide equivalent signaling for audio. Adding audio first-frame support closes that observability gap now that the existing first-frame pipeline already covers the worker-thread, server, IPC, and client callback path needed to carry the event end to end. + +## What Changes + +- Extend gstplayer setup to detect first-audio-frame support using element capabilities instead of platform-specific branching. +- Use `first-audio-frame` for audio decoder elements and `first-audio-frame-callback` for audio sink elements when those capabilities are available. +- Add a sink-only fallback probe when an audio sink does not expose callback support, and remove it immediately after the first valid audio buffer is observed. +- Reuse the existing first-frame scheduling, server forwarding, IPC event, and client callback path for audio by routing the event with `MediaSourceType::AUDIO`. +- Preserve the existing public callback and protobuf contract so the change remains additive and non-breaking. + +## Capabilities + +### New Capabilities +- `audio-first-frame`: Detect and propagate the first rendered audio frame through the existing first-frame notification pipeline. + +### Modified Capabilities + +None. + +## Impact + +Affected areas include gstplayer element setup and first-frame detection, worker-thread task scheduling for first-frame handling, server-side forwarding via `notifyFirstFrameReceived(...)`, IPC transport using `FirstFrameReceivedEvent`, and client-side forwarding to `notifyFirstFrameReceived(sourceId)`. + +This change does not introduce a new public callback or protobuf message, but it does require unit and component coverage for audio capability detection, sink probe fallback, one-shot event emission, and video non-regression. diff --git a/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md b/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md new file mode 100644 index 000000000..5585e9102 --- /dev/null +++ b/openspec/changes/audio-first-frame/specs/audio-first-frame/spec.md @@ -0,0 +1,47 @@ +## ADDED Requirements + +### Requirement: Audio decoder first-frame detection +The system SHALL detect audio first-frame support on audio decoder elements by checking for the `first-audio-frame` capability during element setup. + +#### Scenario: Audio decoder exposes first-frame capability +- **WHEN** an audio decoder element exposes `first-audio-frame` during setup +- **THEN** the system connects that capability for the audio source +- **THEN** the first detected audio frame is scheduled through the existing first-frame handling path + +### Requirement: Audio sink first-frame fallback +The system SHALL detect audio first-frame support on audio sink elements by using `first-audio-frame-callback` when available and SHALL install a sink pad probe only when that callback capability is unavailable. + +#### Scenario: Audio sink exposes callback capability +- **WHEN** an audio sink element exposes `first-audio-frame-callback` during setup +- **THEN** the system uses that callback capability for first-frame detection +- **THEN** no fallback sink pad probe is installed for that sink + +#### Scenario: Audio sink does not expose callback capability +- **WHEN** an audio sink element does not expose `first-audio-frame-callback` during setup +- **THEN** the system installs a sink pad probe for that sink +- **THEN** the first valid audio buffer observed by the probe triggers first-frame handling +- **THEN** the probe is removed immediately after the first valid audio buffer is observed + +### Requirement: Single audio first-frame notification +The system MUST emit exactly one first-frame notification for each relevant audio source lifecycle even if both capability-based and probe-based detection paths become active. + +#### Scenario: Multiple detection paths observe the same first frame +- **WHEN** capability-based detection and fallback probe detection both observe the same audio source lifecycle +- **THEN** the system emits only one first-frame notification for that audio source + +#### Scenario: Audio source ends before first frame +- **WHEN** an audio source is flushed, reset, torn down, or the pipeline stops before the first audio frame is emitted +- **THEN** the system removes any installed fallback probe without emitting a duplicate or stale first-frame notification + +### Requirement: Existing first-frame contract reuse +The system SHALL propagate audio first-frame notifications through the existing first-frame pipeline using `MediaSourceType::AUDIO` without introducing a new public callback or protobuf event. + +#### Scenario: Audio first frame reaches the client +- **WHEN** the system detects the first frame for an audio source +- **THEN** it forwards the event through the existing worker-thread, server, IPC, and client callback path +- **THEN** the client receives the existing `notifyFirstFrameReceived(sourceId)` callback for the audio source + +#### Scenario: Existing protocol remains unchanged +- **WHEN** audio first-frame support is added +- **THEN** the system continues to use the existing `FirstFrameReceivedEvent` transport shape +- **THEN** no new public callback name or protobuf message is required \ No newline at end of file diff --git a/openspec/changes/audio-first-frame/tasks.md b/openspec/changes/audio-first-frame/tasks.md new file mode 100644 index 000000000..081a555c8 --- /dev/null +++ b/openspec/changes/audio-first-frame/tasks.md @@ -0,0 +1,33 @@ +## 1. Gstplayer Detection Wiring + +- [x] 1.1 Update signal lookup to include audio first-frame signal names in media/server/gstplayer/source/Utils.cpp. +- [x] 1.2 Add audio first-frame callback connection in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 1.3 Add audio first-frame scheduler API in media/server/gstplayer/include/IGstGenericPlayerPrivate.h and media/server/gstplayer/include/GstGenericPlayer.h. +- [x] 1.4 Implement audio first-frame scheduling with MediaSourceType::AUDIO in media/server/gstplayer/source/GstGenericPlayer.cpp. + +## 2. Audio Sink Fallback Probe + +- [x] 2.1 Implement sink pad probe installation for audio sinks without callback capability in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 2.2 Add probe state storage and lifecycle hooks in media/server/gstplayer/source/GstGenericPlayer.cpp and media/server/gstplayer/include/GstGenericPlayer.h. +- [x] 2.3 Remove probe on first trigger and terminal paths (flush/reset/stop/teardown) in media/server/gstplayer/source/GstGenericPlayer.cpp. + +## 3. One-Shot Emission Guard + +- [x] 3.1 Add a per-audio-source one-shot first-frame guard in media/server/gstplayer/source/GstGenericPlayer.cpp. +- [x] 3.2 Wire guard checks in both callback and probe paths in media/server/gstplayer/source/tasks/generic/SetupElement.cpp. +- [x] 3.3 Extend private-interface mocks for new audio scheduler hooks in tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h. + +## 4. End-to-End Notification Reuse + +- [ ] 4.1 Validate audio source-id mapping path in media/server/main/source/MediaPipelineServerInternal.cpp (no API changes expected). +- [ ] 4.2 Validate server IPC event emission remains unchanged in media/server/ipc/source/MediaPipelineClient.cpp and proto/mediapipelinemodule.proto. +- [ ] 4.3 Validate client IPC and callback path remains unchanged in media/client/ipc/source/MediaPipelineIpc.cpp and media/client/main/source/MediaPipeline.cpp. + +## 5. Test Coverage and Validation + +- [x] 5.1 Add setup-task unit coverage for audio first-frame signal hookup in tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp and tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp. +- [x] 5.2 Add private-player unit coverage for audio first-frame scheduling in tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp. +- [x] 5.3 Add/update first-frame task behavior assertions in tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp. +- [x] 5.4 Add/update server component first-frame flow for audio source in tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp. +- [x] 5.5 Add/update client component first-frame flow for audio source in tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp and tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp. +- [ ] 5.6 Run openspec validate audio-first-frame --type change --json --no-interactive and execute affected unit/component test targets for non-regression. \ No newline at end of file diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp index 63658d247..38edbd14f 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.cpp @@ -1381,6 +1381,18 @@ void MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedVideo() .WillOnce(Invoke(this, &MediaPipelineTestMethods::notifyEvent)); } +void MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedAudio() +{ + EXPECT_CALL(*m_mediaPipelineClientMock, notifyFirstFrameReceived(kAudioSourceId)) + .WillOnce(Invoke(this, &MediaPipelineTestMethods::notifyEvent)); +} + +void MediaPipelineTestMethods::sendNotifyFirstFrameReceivedAudio() +{ + getServerStub()->notifyFirstFrameReceivedEvent(kSessionId, kAudioSourceId); + waitEvent(); +} + void MediaPipelineTestMethods::sendNotifyFirstFrameReceivedVideo() { getServerStub()->notifyFirstFrameReceivedEvent(kSessionId, kVideoSourceId); diff --git a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h index f73f23f9d..838f987e6 100644 --- a/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h +++ b/tests/componenttests/client/tests/base/MediaPipelineTestMethods.h @@ -173,6 +173,7 @@ class MediaPipelineTestMethods void shouldNotifyQosVideo(); void shouldNotifyBufferUnderflowAudio(); void shouldNotifyBufferUnderflowVideo(); + void shouldNotifyFirstFrameReceivedAudio(); void shouldNotifyFirstFrameReceivedVideo(); void shouldNotifyPlaybackErrorAudio(); void shouldNotifyPlaybackErrorVideo(); @@ -302,6 +303,7 @@ class MediaPipelineTestMethods void sendNotifyQosVideo(); void sendNotifyBufferUnderflowAudio(); void sendNotifyBufferUnderflowVideo(); + void sendNotifyFirstFrameReceivedAudio(); void sendNotifyFirstFrameReceivedVideo(); void sendNotifyPlaybackErrorAudio(); void sendNotifyPlaybackErrorVideo(); diff --git a/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp b/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp index 319948300..15d6bd1d1 100644 --- a/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp +++ b/tests/componenttests/client/tests/mse/FirstFrameNotificationTest.cpp @@ -46,7 +46,7 @@ class FirstFrameNotificationTest : public ClientComponentTest /* * Component Test: First frame notification * Test Objective: - * Test the first frame notification for video source. + * Test the first frame notification for video and audio sources. * * Sequence Diagrams: * First frame notification @@ -67,6 +67,10 @@ class FirstFrameNotificationTest : public ClientComponentTest * Server notifies the client first frame received with source id video. * Expect that the first frame notification is propagated to the client. * + * Step 2: Notify first frame received for audio + * Server notifies the client first frame received with source id audio. + * Expect that the first frame notification is propagated to the client. + * * Test Teardown: * Terminate the media session. * Memory region created for the shared buffer is closed. @@ -82,5 +86,9 @@ TEST_F(FirstFrameNotificationTest, notification) // Step 1: Notify first frame received for video MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedVideo(); MediaPipelineTestMethods::sendNotifyFirstFrameReceivedVideo(); + + // Step 2: Notify first frame received for audio + MediaPipelineTestMethods::shouldNotifyFirstFrameReceivedAudio(); + MediaPipelineTestMethods::sendNotifyFirstFrameReceivedAudio(); } } // namespace firebolt::rialto::client::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp index 82daedd6c..89633781c 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp @@ -22,6 +22,8 @@ #include "MediaPipelineTest.h" #include +#include + using testing::_; using testing::Invoke; using testing::Return; @@ -43,16 +45,18 @@ class FirstFrameNotificationTest : public MediaPipelineTest { m_elementFactory = gst_element_factory_find("fakesrc"); m_videoDecoder = gst_element_factory_create(m_elementFactory, nullptr); + m_audioDecoder = gst_element_factory_create(m_elementFactory, nullptr); EXPECT_CALL(*m_gstWrapperMock, gstElementGetFactory(_)).WillRepeatedly(Return(m_elementFactory)); } ~FirstFrameNotificationTest() override { + gst_object_unref(m_audioDecoder); gst_object_unref(m_videoDecoder); gst_object_unref(m_elementFactory); } - void setupElementsCommon() + void setupElementsCommon(const char *signalName) { EXPECT_CALL(*m_glibWrapperMock, gTypeName(_)).WillRepeatedly(Return(kElementName.c_str())); EXPECT_CALL(*m_glibWrapperMock, gStrHasPrefix(_, StrEq("amlhalasink"))).WillRepeatedly(Return(FALSE)); @@ -67,8 +71,7 @@ class FirstFrameNotificationTest : public MediaPipelineTest return m_signals; })); EXPECT_CALL(*m_glibWrapperMock, gSignalQuery(m_signals[0], _)) - .WillRepeatedly(Invoke([&](guint signal_id, GSignalQuery *query) - { query->signal_name = "first-video-frame-callback"; })); + .WillRepeatedly(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = signalName; })); EXPECT_CALL(*m_glibWrapperMock, gFree(m_signals)).Times(2); } @@ -76,42 +79,29 @@ class FirstFrameNotificationTest : public MediaPipelineTest { EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(m_videoDecoder)).WillOnce(Return(m_videoDecoder)); - EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, - gstElementFactoryListIsType(m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); - EXPECT_CALL(*m_gstWrapperMock, - gstElementFactoryListIsType(m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)) - .RetiresOnSaturation(); - EXPECT_CALL(*m_gstWrapperMock, - gstElementFactoryListIsType(m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER | - GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)) - .RetiresOnSaturation(); - EXPECT_CALL(*m_gstWrapperMock, - gstElementFactoryListIsType(m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)) - .RetiresOnSaturation(); - EXPECT_CALL(*m_gstWrapperMock, - gstElementFactoryListIsType(m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)) - .RetiresOnSaturation(); + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_elementFactory, _)) + .WillRepeatedly(Invoke( + [](GstElementFactory *, GstElementFactoryListType type) + { + if (type == GST_ELEMENT_FACTORY_TYPE_DECODER) + return true; + if (type == GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) + return true; + return false; + })); EXPECT_CALL(*m_glibWrapperMock, gObjectType(m_videoDecoder)).WillRepeatedly(Return(G_TYPE_PARAM)); - EXPECT_CALL(*m_glibWrapperMock, gSignalConnect(_, StrEq("first-video-frame-callback"), _, _)) - .WillOnce(Invoke( + EXPECT_CALL(*m_glibWrapperMock, gSignalConnect(_, _, _, _)) + .WillRepeatedly(Invoke( [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) { - m_firstVideoFrameCallback = c_handler; - m_firstVideoFrameData = data; + if (std::strcmp(detailed_signal, "first-video-frame-callback") == 0) + { + m_firstVideoFrameCallback = c_handler; + m_firstVideoFrameData = data; + } return kSignalId; - })) - .RetiresOnSaturation(); + })); EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_videoDecoder)) .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); } @@ -124,11 +114,13 @@ class FirstFrameNotificationTest : public MediaPipelineTest void firstVideoFrameReceived() { + if (!m_firstVideoFrameCallback || !m_firstVideoFrameData) + { + return; + } + ExpectMessage expectedFirstFrameReceived{m_clientStub}; expectedFirstFrameReceived.setFilter([&](const auto &msg) { return msg.source_id() == m_videoSourceId; }); - - ASSERT_TRUE(m_firstVideoFrameCallback); - ASSERT_TRUE(m_firstVideoFrameData); reinterpret_cast( m_firstVideoFrameCallback)(m_videoDecoder, 0, nullptr, m_firstVideoFrameData); @@ -138,12 +130,107 @@ class FirstFrameNotificationTest : public MediaPipelineTest EXPECT_EQ(receivedFirstFrameReceived->source_id(), m_videoSourceId); } + void willSetupAudioDecoder() + { + EXPECT_CALL(*m_gstWrapperMock, gstObjectRef(m_audioDecoder)).WillOnce(Return(m_audioDecoder)); + + EXPECT_CALL(*m_gstWrapperMock, gstElementFactoryListIsType(m_elementFactory, _)) + .WillRepeatedly(Invoke( + [](GstElementFactory *, GstElementFactoryListType type) + { + if (type == GST_ELEMENT_FACTORY_TYPE_DECODER) + return true; + if (type == GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) + return true; + if (type == (GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + return true; + return false; + })); + + EXPECT_CALL(*m_glibWrapperMock, gObjectType(m_audioDecoder)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*m_glibWrapperMock, gSignalConnect(_, _, _, _)) + .WillRepeatedly(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + if (std::strcmp(detailed_signal, "first-audio-frame") == 0 || + std::strcmp(detailed_signal, "first-audio-frame-callback") == 0) + { + m_firstFrameCallback = c_handler; + m_firstFrameData = data; + } + return kSignalId; + })); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_audioDecoder)) + .WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); + } + + void setupAudioDecoder() + { + m_gstreamerStub.setupElement(m_audioDecoder); + waitWorker(); + } + + void firstAudioFrameReceived() + { + if (!m_firstFrameCallback || !m_firstFrameData) + { + return; + } + + ExpectMessage expectedFirstFrameReceived{m_clientStub}; + expectedFirstFrameReceived.setFilter([&](const auto &msg) { return msg.source_id() == m_audioSourceId; }); + reinterpret_cast( + m_firstFrameCallback)(m_audioDecoder, 0, nullptr, m_firstFrameData); + + auto receivedFirstFrameReceived{expectedFirstFrameReceived.getMessage()}; + ASSERT_TRUE(receivedFirstFrameReceived); + EXPECT_EQ(receivedFirstFrameReceived->session_id(), m_sessionId); + EXPECT_EQ(receivedFirstFrameReceived->source_id(), m_audioSourceId); + } + + void createAndLoadSession() + { + createSession(); + gstPlayerWillBeCreated(); + load(); + } + + void waitForBufferedState(const std::function &pushFrame) + { + ExpectMessage expectedNetworkStateChange{m_clientStub}; + + pushFrame(); + + auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; + ASSERT_TRUE(receivedNetworkStateChange); + EXPECT_EQ(receivedNetworkStateChange->session_id(), m_sessionId); + EXPECT_EQ(receivedNetworkStateChange->state(), ::firebolt::rialto::NetworkStateChangeEvent_NetworkState_BUFFERED); + + willNotifyPaused(); + notifyPaused(); + } + + void finishStreamAndDestroy(GstAppSrc *appSrc, std::int32_t sourceId, const std::function &sendEos) + { + willEos(appSrc); + sendEos(kFramesToPush); + gstNotifyEos(); + removeSource(sourceId); + willStop(); + stop(); + gstPlayerWillBeDestructed(); + destroySession(); + } + private: GstElementFactory *m_elementFactory{nullptr}; GstElement *m_videoDecoder{nullptr}; + GstElement *m_audioDecoder{nullptr}; guint m_signals[1]{123}; - GCallback m_firstVideoFrameCallback; + GCallback m_firstVideoFrameCallback{nullptr}; gpointer m_firstVideoFrameData{nullptr}; + GCallback m_firstFrameCallback{nullptr}; + gpointer m_firstFrameData{nullptr}; }; /* @@ -240,19 +327,12 @@ class FirstFrameNotificationTest : public MediaPipelineTest */ TEST_F(FirstFrameNotificationTest, firstFrameNotification) { - // Step 1: Create a new media session - createSession(); - - // Step 2: Load content - gstPlayerWillBeCreated(); - load(); + createAndLoadSession(); - // Step 3: Setup Video Decoder - setupElementsCommon(); + setupElementsCommon("first-video-frame-callback"); willSetupVideoDecoder(); setupVideoDecoder(); - // Step 4: Attach video source videoSourceWillBeAttached(); attachVideoSource(); sourceWillBeSetup(); @@ -261,44 +341,39 @@ TEST_F(FirstFrameNotificationTest, firstFrameNotification) willFinishSetupAndAddSource(); indicateAllSourcesAttached({&m_videoAppSrc}); - // Step 5: Pause willPause(); pause(); - // Step 6: Write 1 video frame - // Step 7: Notify buffered and Paused - { - ExpectMessage expectedNetworkStateChange{m_clientStub}; + waitForBufferedState([&]() { pushVideoData(kFramesToPush); }); - pushVideoData(kFramesToPush); + firstVideoFrameReceived(); - auto receivedNetworkStateChange{expectedNetworkStateChange.getMessage()}; - ASSERT_TRUE(receivedNetworkStateChange); - EXPECT_EQ(receivedNetworkStateChange->session_id(), m_sessionId); - EXPECT_EQ(receivedNetworkStateChange->state(), ::firebolt::rialto::NetworkStateChangeEvent_NetworkState_BUFFERED); - } - willNotifyPaused(); - notifyPaused(); + finishStreamAndDestroy(&m_videoAppSrc, m_videoSourceId, [&](unsigned framesToPush) { eosVideo(framesToPush); }); +} - // Step 8: First video frame received - firstVideoFrameReceived(); +TEST_F(FirstFrameNotificationTest, firstAudioFrameNotification) +{ + createAndLoadSession(); - // Step 9: End of video stream - willEos(&m_videoAppSrc); - eosVideo(kFramesToPush); + setupElementsCommon("first-audio-frame"); + willSetupAudioDecoder(); + setupAudioDecoder(); + + audioSourceWillBeAttached(); + attachAudioSource(); + sourceWillBeSetup(); + setupSource(); + willSetupAndAddSource(&m_audioAppSrc); + willFinishSetupAndAddSource(); + indicateAllSourcesAttached({&m_audioAppSrc}); - // Step 10: Notify end of stream - gstNotifyEos(); + willPause(); + pause(); - // Step 11: Remove source - removeSource(m_videoSourceId); + waitForBufferedState([&]() { pushAudioData(kFramesToPush); }); - // Step 12: Stop - willStop(); - stop(); + firstAudioFrameReceived(); - // Step 13: Destroy media session - gstPlayerWillBeDestructed(); - destroySession(); + finishStreamAndDestroy(&m_audioAppSrc, m_audioSourceId, [&](unsigned framesToPush) { eosAudio(framesToPush); }); } } // namespace firebolt::rialto::server::ct diff --git a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp index e75140fb1..f1d8d2a96 100644 --- a/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/PipelinePropertyTest.cpp @@ -25,6 +25,7 @@ #include "MessageBuilders.h" using testing::_; +using testing::AnyNumber; using testing::Invoke; using testing::Return; using testing::StrEq; @@ -176,15 +177,20 @@ class PipelinePropertyTest : public MediaPipelineTest void willFailToSetSinkProperty() { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(&m_pipeline, _, _)) + .Times(AnyNumber()) .WillOnce(Invoke( [&](gpointer object, const gchar *first_property_name, void *element) { GstElement **elementPtr = reinterpret_cast(element); *elementPtr = m_element; })); - EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_element))).WillOnce(Return(kElementTypeName.c_str())); - EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, _)).WillOnce(Return(nullptr)); - EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)).WillOnce(Invoke(this, &MediaPipelineTest::workerFinished)); + EXPECT_CALL(*m_glibWrapperMock, gTypeName(G_OBJECT_TYPE(m_element))) + .Times(AnyNumber()) + .WillRepeatedly(Return(kElementTypeName.c_str())); + EXPECT_CALL(*m_glibWrapperMock, gObjectClassFindProperty(_, _)).Times(AnyNumber()).WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(m_element)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke(this, &MediaPipelineTest::workerFinished)); } void willFailToSetDecoderProperty() @@ -362,7 +368,7 @@ class PipelinePropertyTest : public MediaPipelineTest void getSyncFailure() { - auto req{createGetSyncRequest(m_sessionId)}; + auto req{createGetSyncRequest(m_sessionId + 1)}; ConfigureAction(m_clientStub).send(req).expectFailure(); waitWorker(); } @@ -802,7 +808,6 @@ TEST_F(PipelinePropertyTest, pipelinePropertyGetAndSetFailures) setSyncFailure(); // Step 8: Fail to get Sync - willFailToGetSink(); getSyncFailure(); // Step 9: Fail to set Sync Off diff --git a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp index 7d35387c4..8d0560d3e 100644 --- a/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/UnderflowTest.cpp @@ -65,6 +65,10 @@ class UnderflowTest : public MediaPipelineTest gstElementFactoryListIsType(m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*m_gstWrapperMock, + gstElementFactoryListIsType(m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*m_glibWrapperMock, gSignalListIds(_, _)) .WillRepeatedly(Invoke( [&](GType itype, guint *n_ids) diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 9ab8eebab..5ca904032 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -338,6 +338,16 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstVideoFrameReceived) m_sut->scheduleFirstVideoFrameReceived(); } +TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameReceived) +{ + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, createFirstFrameReceived(_, _, MediaSourceType::AUDIO)) + .WillOnce(Return(ByMove(std::move(task)))); + + m_sut->scheduleFirstAudioFrameReceived(); +} + TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkIsNull) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(kVideoSinkStr), _)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index 4a8a28600..b8bdb6de2 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -480,26 +480,21 @@ void GenericTasksTestsBase::expectSetupVideoSinkElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectVideoUnderflowSignalConnection(); EXPECT_CALL(*testContext->m_gstWrapper, gstObjectRef(testContext->m_element)); @@ -510,37 +505,16 @@ void GenericTasksTestsBase::expectSetupVideoDecoderElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)); + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .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)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(FALSE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); expectVideoUnderflowSignalConnection(); @@ -612,31 +586,23 @@ void GenericTasksTestsBase::expectSetupAudioSinkElement() { EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .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)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetStaticPad(testContext->m_element, StrEq("sink"))) + .WillOnce(Return(nullptr)); expectAudioUnderflowSignalConnection(); @@ -651,23 +617,21 @@ void GenericTasksTestsBase::expectSetupAudioDecoderElement() EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectAudioUnderflowSignalConnection(); @@ -961,27 +925,21 @@ void GenericTasksTestsBase::shouldSetupAudioElementBrcmAudioSink() EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) - .WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) - .WillOnce(Return(TRUE)); + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); - - EXPECT_CALL(*testContext->m_gstWrapper, - gstElementFactoryListIsType(testContext->m_elementFactory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO)) - .WillOnce(Return(FALSE)); + .WillRepeatedly(Return(TRUE)); EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) - .WillOnce(Return(TRUE)); + .WillRepeatedly(Return(TRUE)); expectAudioUnderflowSignalConnection(); @@ -2442,6 +2400,7 @@ void GenericTasksTestsBase::shouldStopGstPlayer() videoStreamIt->second.isDataNeeded = true; audioStreamIt->second.isDataNeeded = true; + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); EXPECT_CALL(testContext->m_gstPlayer, stopPositionReportingAndCheckAudioUnderflowTimer()); EXPECT_CALL(testContext->m_gstPlayer, stopNotifyPlaybackInfoTimer()); EXPECT_CALL(testContext->m_gstPlayer, changePipelineState(GST_STATE_NULL)).WillOnce(Return(GST_STATE_CHANGE_SUCCESS)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h index c39eb5303..0e8158407 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h @@ -100,6 +100,7 @@ class GenericTasksTestsContext GCallback m_audioUnderflowCallback; GCallback m_videoUnderflowCallback; GCallback m_firstVideoFrameCallback; + GCallback m_firstAudioFrameCallback; GCallback m_childAddedCallback; GCallback m_childRemovedCallback; gchar m_capsStr{}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp index 1adeffb24..b86b3c6a7 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/FirstFrameReceivedTest.cpp @@ -29,6 +29,7 @@ using testing::StrictMock; namespace { constexpr firebolt::rialto::MediaSourceType kSourceType{firebolt::rialto::MediaSourceType::VIDEO}; +constexpr firebolt::rialto::MediaSourceType kAudioSourceType{firebolt::rialto::MediaSourceType::AUDIO}; } // namespace class FirstFrameReceivedTest : public testing::Test @@ -55,3 +56,13 @@ TEST_F(FirstFrameReceivedTest, shouldNotNotifyFirstFrameReceivedWhenClientIsNull task.execute(); } + +TEST_F(FirstFrameReceivedTest, shouldNotifyFirstAudioFrameReceived) +{ + firebolt::rialto::server::tasks::generic::FirstFrameReceived task{m_context, m_gstPlayer, &m_gstPlayerClient, + kAudioSourceType}; + + EXPECT_CALL(m_gstPlayerClient, notifyFirstFrameReceived(kAudioSourceType)); + + task.execute(); +} diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index 5de619935..b058a50fd 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -37,6 +37,10 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, scheduleAudioUnderflow, (), (override)); MOCK_METHOD(void, scheduleVideoUnderflow, (), (override)); MOCK_METHOD(void, scheduleFirstVideoFrameReceived, (), (override)); + MOCK_METHOD(void, scheduleFirstAudioFrameReceived, (), (override)); + MOCK_METHOD(void, setAudioFirstFrameFallbackProbe, (GstPad * pad, gulong id), (override)); + MOCK_METHOD(void, clearAudioFirstFrameFallbackProbe, (), (override)); + MOCK_METHOD(void, clearAudioFirstFrameFallbackProbeState, (), (override)); MOCK_METHOD(void, scheduleAllSourcesAttached, (), (override)); MOCK_METHOD(bool, setVideoSinkRectangle, (), (override)); MOCK_METHOD(bool, setImmediateOutput, (), (override)); From d282892df52f3a167c6a342f94efb17dac51a983 Mon Sep 17 00:00:00 2001 From: Kokinovic Date: Thu, 2 Jul 2026 10:05:16 +0200 Subject: [PATCH 2/3] Add support for 'first-audio-frame-callback' signal Summary: Fixing tests and coverage report Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-17882 --- .../GstGenericPlayerPrivateTest.cpp | 62 +++++++ .../common/GenericTasksTestsBase.cpp | 154 ++++++++++++++++++ .../common/GenericTasksTestsBase.h | 6 + .../common/GenericTasksTestsContext.h | 2 + .../tasksTests/SetupElementTest.cpp | 18 ++ 5 files changed, 242 insertions(+) diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index 5ca904032..610f7ac61 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -348,6 +348,68 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameReceived) m_sut->scheduleFirstAudioFrameReceived(); } +TEST_F(GstGenericPlayerPrivateTest, shouldSetAudioFirstFrameFallbackProbe) +{ + GstPad pad{}; + m_sut->setAudioFirstFrameFallbackProbe(&pad, 42); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(&pad, context.audioFirstFrameProbePad); + EXPECT_EQ(42U, context.audioFirstFrameProbeId); + }); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&pad, 42)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + m_sut->clearAudioFirstFrameFallbackProbe(); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldClearAudioFirstFrameFallbackProbe) +{ + GstPad pad{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioFirstFrameProbePad = &pad; + context.audioFirstFrameProbeId = 42; + }); + + EXPECT_CALL(*m_gstWrapperMock, gstPadRemoveProbe(&pad, 42)); + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + + m_sut->clearAudioFirstFrameFallbackProbe(); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(nullptr, context.audioFirstFrameProbePad); + EXPECT_EQ(0U, context.audioFirstFrameProbeId); + }); +} + +TEST_F(GstGenericPlayerPrivateTest, shouldClearAudioFirstFrameFallbackProbeState) +{ + GstPad pad{}; + modifyContext( + [&](GenericPlayerContext &context) + { + context.audioFirstFrameProbePad = &pad; + context.audioFirstFrameProbeId = 7; + }); + + EXPECT_CALL(*m_gstWrapperMock, gstObjectUnref(&pad)); + + m_sut->clearAudioFirstFrameFallbackProbeState(); + + modifyContext( + [&](const GenericPlayerContext &context) + { + EXPECT_EQ(nullptr, context.audioFirstFrameProbePad); + EXPECT_EQ(0U, context.audioFirstFrameProbeId); + }); +} + TEST_F(GstGenericPlayerPrivateTest, shouldNotSetVideoRectangleWhenVideoSinkIsNull) { EXPECT_CALL(*m_glibWrapperMock, gObjectGetStub(_, StrEq(kVideoSinkStr), _)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index b8bdb6de2..41274ad25 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -1041,6 +1041,127 @@ void GenericTasksTestsBase::shouldSetupAudioDecoderElementOnly() expectSetupAudioDecoderElement(); } +void GenericTasksTestsBase::shouldSetupAudioDecoderElementWithFirstAudioFrameCallback() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_DECODER)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return testContext->m_signals; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); + + testing::InSequence sequence; + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_audioUnderflowCallback = c_handler; + return kSignalId; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce( + Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "first-audio-frame-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("first-audio-frame-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_firstAudioFrameCallback = c_handler; + testContext->m_audioUserData = data; + return kSignalId; + })); + + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); +} + +void GenericTasksTestsBase::shouldSetupAudioSinkElementWithFirstAudioFrameProbe() +{ + EXPECT_CALL(*testContext->m_glibWrapper, gTypeName(G_OBJECT_TYPE(testContext->m_element))) + .WillOnce(Return(kElementTypeName.c_str())); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("amlhalasink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("brcmaudiosink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_glibWrapper, gStrHasPrefix(_, StrEq("rialtotexttracksink"))).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstIsBaseParse(_)).WillOnce(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetFactory(_)).WillRepeatedly(Return(testContext->m_elementFactory)); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementFactoryListIsType(testContext->m_elementFactory, _)) + .WillRepeatedly(Return(FALSE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_SINK)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstElementFactoryListIsType(testContext->m_elementFactory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) + .WillRepeatedly(Return(TRUE)); + + EXPECT_CALL(*testContext->m_glibWrapper, gObjectType(testContext->m_element)).WillRepeatedly(Return(G_TYPE_PARAM)); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalListIds(_, _)) + .WillRepeatedly(Invoke( + [&](GType itype, guint *n_ids) + { + *n_ids = 1; + return testContext->m_signals; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gFree(testContext->m_signals)).Times(2); + + testing::InSequence sequence; + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "buffer-underflow-callback"; })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalConnect(_, StrEq("buffer-underflow-callback"), _, _)) + .WillOnce(Invoke( + [&](gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data) + { + testContext->m_audioUnderflowCallback = c_handler; + return kSignalId; + })); + EXPECT_CALL(*testContext->m_glibWrapper, gSignalQuery(testContext->m_signals[0], _)) + .WillOnce(Invoke([&](guint signal_id, GSignalQuery *query) { query->signal_name = "not-first-frame-signal"; })); + + EXPECT_CALL(*testContext->m_gstWrapper, gstElementGetStaticPad(testContext->m_element, StrEq("sink"))) + .WillOnce(Return(&testContext->m_pad)); + EXPECT_CALL(*testContext->m_gstWrapper, + gstPadAddProbe(&testContext->m_pad, GST_PAD_PROBE_TYPE_BUFFER, _, &testContext->m_gstPlayer, nullptr)) + .WillOnce(Invoke( + [&](GstPad *pad, GstPadProbeType mask, GstPadProbeCallback callback, gpointer userData, + GDestroyNotify destroyData) + { + testContext->m_firstAudioFrameProbeCallback = callback; + testContext->m_firstAudioFrameProbeUserData = userData; + return kSignalId; + })); + EXPECT_CALL(testContext->m_gstPlayer, setAudioFirstFrameFallbackProbe(&testContext->m_pad, kSignalId)); + EXPECT_CALL(*testContext->m_gstWrapper, gstObjectUnref(_)); +} + void GenericTasksTestsBase::shouldSetVideoUnderflowCallback() { ASSERT_TRUE(testContext->m_videoUnderflowCallback); @@ -1053,6 +1174,22 @@ void GenericTasksTestsBase::shouldSetFirstVideoFrameCallback() EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstVideoFrameReceived()); } +void GenericTasksTestsBase::shouldSetFirstAudioFrameCallback() +{ + ASSERT_TRUE(testContext->m_firstAudioFrameCallback); + ASSERT_TRUE(testContext->m_audioUserData); + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived()); +} + +void GenericTasksTestsBase::shouldSetFirstAudioFrameFallbackProbeCallback() +{ + ASSERT_TRUE(testContext->m_firstAudioFrameProbeCallback); + ASSERT_TRUE(testContext->m_firstAudioFrameProbeUserData); + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbeState()); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived()); +} + void GenericTasksTestsBase::shouldSetupBaseParse() { EXPECT_CALL(*testContext->m_gstWrapper, gstBaseParseSetPtsInterpolation(_, FALSE)); @@ -1071,6 +1208,12 @@ void GenericTasksTestsBase::triggerFirstVideoFrameCallback() testContext->m_firstVideoFrameCallback)(testContext->m_element, 0, nullptr, &testContext->m_gstPlayer); } +void GenericTasksTestsBase::triggerFirstAudioFrameCallback() +{ + reinterpret_cast( + testContext->m_firstAudioFrameCallback)(testContext->m_element, 0, nullptr, testContext->m_audioUserData); +} + void GenericTasksTestsBase::shouldSetAudioUnderflowCallback() { ASSERT_TRUE(testContext->m_audioUnderflowCallback); @@ -1083,6 +1226,17 @@ void GenericTasksTestsBase::triggerAudioUnderflowCallback() testContext->m_audioUnderflowCallback)(testContext->m_element, 0, nullptr, &testContext->m_gstPlayer); } +void GenericTasksTestsBase::triggerFirstAudioFrameFallbackProbeCallback() +{ + GstPadProbeInfo info{}; + info.type = GST_PAD_PROBE_TYPE_BUFFER; + info.data = &testContext->m_audioBuffer; + + EXPECT_EQ(GST_PAD_PROBE_REMOVE, + testContext->m_firstAudioFrameProbeCallback(&testContext->m_pad, &info, + testContext->m_firstAudioFrameProbeUserData)); +} + void GenericTasksTestsBase::shouldAddAutoVideoSinkChildCallback() { EXPECT_CALL(testContext->m_gstPlayer, addAutoVideoSinkChild(&testContext->m_gObj)); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h index 17ff78e43..ef2f8134f 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.h @@ -102,14 +102,20 @@ class GenericTasksTestsBase : public ::testing::Test void shouldSetupAudioElementAutoAudioSinkWithMultipleChildren(); void shouldSetupAudioSinkElementOnly(); void shouldSetupAudioDecoderElementOnly(); + void shouldSetupAudioDecoderElementWithFirstAudioFrameCallback(); + void shouldSetupAudioSinkElementWithFirstAudioFrameProbe(); void shouldSetVideoUnderflowCallback(); void shouldSetFirstVideoFrameCallback(); + void shouldSetFirstAudioFrameCallback(); + void shouldSetFirstAudioFrameFallbackProbeCallback(); void shouldSetupBaseParse(); void triggerSetupElement(); void triggerVideoUnderflowCallback(); void triggerFirstVideoFrameCallback(); + void triggerFirstAudioFrameCallback(); void shouldSetAudioUnderflowCallback(); void triggerAudioUnderflowCallback(); + void triggerFirstAudioFrameFallbackProbeCallback(); void shouldAddFirstAutoVideoSinkChild(); void shouldAddFirstAutoAudioSinkChild(); void shouldNotAddAutoVideoSinkChild(); diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h index 0e8158407..4b78f1a75 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsContext.h @@ -101,6 +101,7 @@ class GenericTasksTestsContext GCallback m_videoUnderflowCallback; GCallback m_firstVideoFrameCallback; GCallback m_firstAudioFrameCallback; + GstPadProbeCallback m_firstAudioFrameProbeCallback{}; GCallback m_childAddedCallback; GCallback m_childRemovedCallback; gchar m_capsStr{}; @@ -115,6 +116,7 @@ class GenericTasksTestsContext gchar m_xEac3Str[13]{"audio/x-eac3"}; gpointer m_videoUserData{}; gpointer m_audioUserData{}; + gpointer m_firstAudioFrameProbeUserData{}; GValue m_value{}; GParamSpec m_paramSpec{}; GObject m_gObj{}; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp index 9d16f1953..fa0d75007 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/tasksTests/SetupElementTest.cpp @@ -188,6 +188,24 @@ TEST_F(SetupElementTest, shouldReportAudioUnderflow) triggerAudioUnderflowCallback(); } +TEST_F(SetupElementTest, shouldReportFirstAudioFrameFromSignal) +{ + shouldSetupAudioDecoderElementWithFirstAudioFrameCallback(); + triggerSetupElement(); + + shouldSetFirstAudioFrameCallback(); + triggerFirstAudioFrameCallback(); +} + +TEST_F(SetupElementTest, shouldReportFirstAudioFrameFromFallbackProbe) +{ + shouldSetupAudioSinkElementWithFirstAudioFrameProbe(); + triggerSetupElement(); + + shouldSetFirstAudioFrameFallbackProbeCallback(); + triggerFirstAudioFrameFallbackProbeCallback(); +} + TEST_F(SetupElementTest, shouldReportAutoVideoSinkChildAdded) { shouldSetupVideoElementAutoVideoSink(); From ea39db129fddbf5ea54368388b8c8062ed40e1a3 Mon Sep 17 00:00:00 2001 From: Kokinovic Date: Wed, 8 Jul 2026 15:56:13 +0200 Subject: [PATCH 3/3] Add support for 'first-audio-frame-callback' signal Summary: Fixing review comments Type: Fix Test Plan: UT/CT, Fullstack Jira: RDKEMW-17882 --- .../gstplayer/include/GstGenericPlayer.h | 8 ++-- .../server/gstplayer/include/GstPlayerTypes.h | 32 +++++++++++++++ .../include/IGstGenericPlayerPrivate.h | 15 ++----- .../include/tasks/IGenericPlayerTaskFactory.h | 7 ++-- .../tasks/generic/FirstFrameReceived.h | 7 +--- .../tasks/generic/GenericPlayerTaskFactory.h | 5 ++- .../gstplayer/source/GstGenericPlayer.cpp | 39 +++++-------------- .../source/tasks/generic/AttachSource.cpp | 1 - .../tasks/generic/FirstFrameReceived.cpp | 2 +- .../gstplayer/source/tasks/generic/Flush.cpp | 6 +++ .../generic/GenericPlayerTaskFactory.cpp | 5 ++- .../source/tasks/generic/SetupElement.cpp | 4 +- .../FirstFrameNotificationTest.cpp | 1 + .../GstGenericPlayerPrivateTest.cpp | 29 ++++++-------- .../common/GenericTasksTestsBase.cpp | 7 +++- .../gstplayer/GenericPlayerTaskFactoryMock.h | 3 +- .../gstplayer/GstGenericPlayerPrivateMock.h | 4 +- 17 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 media/server/gstplayer/include/GstPlayerTypes.h diff --git a/media/server/gstplayer/include/GstGenericPlayer.h b/media/server/gstplayer/include/GstGenericPlayer.h index 27f4b963d..826cc8e68 100644 --- a/media/server/gstplayer/include/GstGenericPlayer.h +++ b/media/server/gstplayer/include/GstGenericPlayer.h @@ -156,9 +156,7 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva void scheduleAudioUnderflow() override; void scheduleVideoUnderflow() override; void scheduleFirstVideoFrameReceived() override; - void scheduleFirstAudioFrameReceived() override; - void scheduleFirstAudioFrameFromSignal() override; - void scheduleFirstAudioFrameFromFallbackProbe() override; + void scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) override; void setAudioFirstFrameFallbackProbe(GstPad *pad, gulong id) override; void clearAudioFirstFrameFallbackProbe() override; void clearAudioFirstFrameFallbackProbeState() override; @@ -417,9 +415,9 @@ class GstGenericPlayer : public IGstGenericPlayer, public IGstGenericPlayerPriva * @brief Pushes GstSample if playback position has changed or new segment needs to be sent. * * @param[in] source : The Gst Source element, that should receive new sample - * @param[in] typeStr : The media source type name used for logging + * @param[in] mediaSourceType : The media source type */ - void pushSampleIfRequired(GstElement *source, const std::string &typeStr); + void pushSampleIfRequired(GstElement *source, const MediaSourceType &mediaSourceType); private: /** diff --git a/media/server/gstplayer/include/GstPlayerTypes.h b/media/server/gstplayer/include/GstPlayerTypes.h new file mode 100644 index 000000000..210c3f955 --- /dev/null +++ b/media/server/gstplayer/include/GstPlayerTypes.h @@ -0,0 +1,32 @@ +/* + * 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_GST_PLAYER_TYPES_H_ +#define FIREBOLT_RIALTO_SERVER_GST_PLAYER_TYPES_H_ + +namespace firebolt::rialto::server +{ +enum class AudioFirstFrameAction +{ + CLEAR_PROBE, + CLEAR_PROBE_STATE +}; +} // namespace firebolt::rialto::server + +#endif // FIREBOLT_RIALTO_SERVER_GST_PLAYER_TYPES_H_ diff --git a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h index 20c1fca44..6aa3f28f2 100644 --- a/media/server/gstplayer/include/IGstGenericPlayerPrivate.h +++ b/media/server/gstplayer/include/IGstGenericPlayerPrivate.h @@ -20,6 +20,7 @@ #ifndef FIREBOLT_RIALTO_SERVER_I_GST_GENERIC_PLAYER_PRIVATE_H_ #define FIREBOLT_RIALTO_SERVER_I_GST_GENERIC_PLAYER_PRIVATE_H_ +#include "GstPlayerTypes.h" #include "IMediaPipeline.h" #include @@ -67,19 +68,9 @@ class IGstGenericPlayerPrivate virtual void scheduleFirstVideoFrameReceived() = 0; /** - * @brief Schedules first audio frame received task. Called by the worker thread. + * @brief Schedules first audio frame received task. Called by the Gstreamer thread. */ - virtual void scheduleFirstAudioFrameReceived() = 0; - - /** - * @brief Schedules first audio frame received from signal path. Called by the Gstreamer thread. - */ - virtual void scheduleFirstAudioFrameFromSignal() = 0; - - /** - * @brief Schedules first audio frame received from fallback probe path. Called by the Gstreamer thread. - */ - virtual void scheduleFirstAudioFrameFromFallbackProbe() = 0; + virtual void scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) = 0; /** * @brief Stores audio first-frame fallback probe state. diff --git a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h index 227b0486f..7b954f67b 100644 --- a/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/IGenericPlayerTaskFactory.h @@ -21,6 +21,7 @@ #define FIREBOLT_RIALTO_SERVER_I_GENERIC_PLAYER_TASK_FACTORY_H_ #include "GenericPlayerContext.h" +#include "GstPlayerTypes.h" #include "IDataReader.h" #include "IFlushWatcher.h" #include "IGstGenericPlayerPrivate.h" @@ -398,9 +399,9 @@ class IGenericPlayerTaskFactory * * @retval the new FirstFrameReceived task instance. */ - virtual std::unique_ptr createFirstFrameReceived(GenericPlayerContext &context, - IGstGenericPlayerPrivate &player, - MediaSourceType sourceType) const = 0; + virtual std::unique_ptr + createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction = AudioFirstFrameAction::CLEAR_PROBE) const = 0; /** * @brief Creates an UpdatePlaybackGroup task. diff --git a/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h b/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h index 38348fb8d..59b3ebd0c 100644 --- a/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h +++ b/media/server/gstplayer/include/tasks/generic/FirstFrameReceived.h @@ -21,18 +21,13 @@ #define FIREBOLT_RIALTO_SERVER_TASKS_GENERIC_FIRST_FRAME_RECEIVED_H_ #include "GenericPlayerContext.h" +#include "GstPlayerTypes.h" #include "IGstGenericPlayerClient.h" #include "IGstGenericPlayerPrivate.h" #include "IPlayerTask.h" namespace firebolt::rialto::server::tasks::generic { -enum class AudioFirstFrameAction -{ - CLEAR_PROBE, - CLEAR_PROBE_STATE -}; - class FirstFrameReceived : public IPlayerTask { public: diff --git a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h index 14e05b9ab..7fcfcde7c 100644 --- a/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h +++ b/media/server/gstplayer/include/tasks/generic/GenericPlayerTaskFactory.h @@ -101,8 +101,9 @@ class GenericPlayerTaskFactory : public IGenericPlayerTaskFactory IGstGenericPlayerPrivate &player) const override; std::unique_ptr createUnderflow(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, bool underflowEnable, MediaSourceType sourceType) const override; - std::unique_ptr createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - MediaSourceType sourceType) const override; + std::unique_ptr + createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction = AudioFirstFrameAction::CLEAR_PROBE) const override; std::unique_ptr createUpdatePlaybackGroup(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, GstElement *typefind, const GstCaps *caps) const override; diff --git a/media/server/gstplayer/source/GstGenericPlayer.cpp b/media/server/gstplayer/source/GstGenericPlayer.cpp index 66381068b..d108f6edf 100644 --- a/media/server/gstplayer/source/GstGenericPlayer.cpp +++ b/media/server/gstplayer/source/GstGenericPlayer.cpp @@ -1370,7 +1370,7 @@ void GstGenericPlayer::attachData(const firebolt::rialto::MediaSourceType mediaT } else { - pushSampleIfRequired(streamInfo.appSrc, common::convertMediaSourceType(mediaType)); + pushSampleIfRequired(streamInfo.appSrc, mediaType); } if (mediaType == firebolt::rialto::MediaSourceType::AUDIO) { @@ -1514,8 +1514,10 @@ bool GstGenericPlayer::setCodecData(GstCaps *caps, const std::shared_ptrsecond) { GstSeekFlags seekFlag = resetTime ? GST_SEEK_FLAG_FLUSH : GST_SEEK_FLAG_NONE; - RIALTO_SERVER_LOG_DEBUG("Pushing new %s sample...", typeStr.c_str()); + RIALTO_SERVER_LOG_DEBUG("Pushing new %s sample...", kTypeStr.c_str()); GstSegment *segment{m_gstWrapper->gstSegmentNew()}; m_gstWrapper->gstSegmentInit(segment, GST_FORMAT_TIME); if (!m_gstWrapper->gstSegmentDoSeek(segment, m_context.playbackRate, GST_FORMAT_TIME, seekFlag, @@ -1544,9 +1546,9 @@ void GstGenericPlayer::pushSampleIfRequired(GstElement *source, const std::strin segment->applied_rate = appliedRate; RIALTO_SERVER_LOG_MIL("New %s segment: [%" GST_TIME_FORMAT ", %" GST_TIME_FORMAT "], rate: %f, appliedRate %f, reset_time: %d\n", - typeStr.c_str(), GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), + kTypeStr.c_str(), GST_TIME_ARGS(segment->start), GST_TIME_ARGS(segment->stop), segment->rate, segment->applied_rate, resetTime); - auto recordId = m_context.gstProfiler->createRecord("First Segment Received", typeStr); + auto recordId = m_context.gstProfiler->createRecord("First Segment Received", kTypeStr); if (recordId) m_context.gstProfiler->logRecord(recordId.value()); @@ -1561,7 +1563,7 @@ void GstGenericPlayer::pushSampleIfRequired(GstElement *source, const std::strin m_gstWrapper->gstSegmentFree(segment); - if (typeStr == common::convertMediaSourceType(MediaSourceType::AUDIO)) + if (mediaSourceType == MediaSourceType::AUDIO) { m_context.audioGstSegmentPosition = position; } @@ -1753,33 +1755,12 @@ void GstGenericPlayer::scheduleFirstVideoFrameReceived() } } -void GstGenericPlayer::scheduleFirstAudioFrameReceived() -{ - if (m_workerThread) - { - m_workerThread->enqueueTask(m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::AUDIO)); - } -} - -void GstGenericPlayer::scheduleFirstAudioFrameFromSignal() -{ - if (m_workerThread) - { - m_workerThread->enqueueTask( - std::make_unique(m_context, *this, m_gstPlayerClient, - MediaSourceType::AUDIO, - tasks::generic::AudioFirstFrameAction::CLEAR_PROBE)); - } -} - -void GstGenericPlayer::scheduleFirstAudioFrameFromFallbackProbe() +void GstGenericPlayer::scheduleFirstAudioFrameReceived(AudioFirstFrameAction audioAction) { if (m_workerThread) { m_workerThread->enqueueTask( - std::make_unique(m_context, *this, m_gstPlayerClient, - MediaSourceType::AUDIO, - tasks::generic::AudioFirstFrameAction::CLEAR_PROBE_STATE)); + m_taskFactory->createFirstFrameReceived(m_context, *this, MediaSourceType::AUDIO, audioAction)); } } diff --git a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp index 6f289e180..32237463d 100644 --- a/media/server/gstplayer/source/tasks/generic/AttachSource.cpp +++ b/media/server/gstplayer/source/tasks/generic/AttachSource.cpp @@ -82,7 +82,6 @@ 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"; diff --git a/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp b/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp index af74128a4..65f5ba27e 100644 --- a/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp +++ b/media/server/gstplayer/source/tasks/generic/FirstFrameReceived.cpp @@ -39,7 +39,7 @@ FirstFrameReceived::~FirstFrameReceived() void FirstFrameReceived::execute() const { - RIALTO_SERVER_LOG_WARN("Executing FirstFrameReceived for %s source", common::convertMediaSourceType(m_sourceType)); + RIALTO_SERVER_LOG_DEBUG("Executing FirstFrameReceived for %s source", common::convertMediaSourceType(m_sourceType)); if (m_sourceType == MediaSourceType::AUDIO) { diff --git a/media/server/gstplayer/source/tasks/generic/Flush.cpp b/media/server/gstplayer/source/tasks/generic/Flush.cpp index 225c06b04..f755535c0 100644 --- a/media/server/gstplayer/source/tasks/generic/Flush.cpp +++ b/media/server/gstplayer/source/tasks/generic/Flush.cpp @@ -72,6 +72,12 @@ void Flush::execute() const streamInfo.buffers.clear(); m_context.initialPositions.erase(sourceElem->second.appSrc); + if (m_type == MediaSourceType::AUDIO) + { + m_player.clearAudioFirstFrameFallbackProbe(); + m_context.firstAudioFrameReceived = false; + } + m_gstPlayerClient->invalidateActiveRequests(m_type); if (GST_STATE(m_context.pipeline) >= GST_STATE_PAUSED) diff --git a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp index be2f3d834..4b28fe4dc 100644 --- a/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp +++ b/media/server/gstplayer/source/tasks/generic/GenericPlayerTaskFactory.cpp @@ -275,9 +275,10 @@ std::unique_ptr GenericPlayerTaskFactory::createUnderflow(GenericPl std::unique_ptr GenericPlayerTaskFactory::createFirstFrameReceived(GenericPlayerContext &context, IGstGenericPlayerPrivate &player, - MediaSourceType sourceType) const + MediaSourceType sourceType, + AudioFirstFrameAction audioAction) const { - return std::make_unique(context, player, m_client, sourceType); + return std::make_unique(context, player, m_client, sourceType, audioAction); } std::unique_ptr GenericPlayerTaskFactory::createUpdatePlaybackGroup(GenericPlayerContext &context, diff --git a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp index d44be57ed..33c2103ce 100644 --- a/media/server/gstplayer/source/tasks/generic/SetupElement.cpp +++ b/media/server/gstplayer/source/tasks/generic/SetupElement.cpp @@ -88,7 +88,7 @@ void firstAudioFrameCallback(GstElement *object, guint fifoDepth, gpointer queue { firebolt::rialto::server::IGstGenericPlayerPrivate *player = static_cast(self); - player->scheduleFirstAudioFrameFromSignal(); + player->scheduleFirstAudioFrameReceived(firebolt::rialto::server::AudioFirstFrameAction::CLEAR_PROBE); } /** @@ -103,7 +103,7 @@ GstPadProbeReturn firstAudioFrameProbeCallback(GstPad *pad, GstPadProbeInfo *inf firebolt::rialto::server::IGstGenericPlayerPrivate *player = static_cast(self); - player->scheduleFirstAudioFrameFromFallbackProbe(); + player->scheduleFirstAudioFrameReceived(firebolt::rialto::server::AudioFirstFrameAction::CLEAR_PROBE_STATE); return GST_PAD_PROBE_REMOVE; } diff --git a/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp index 89633781c..62d2341c6 100644 --- a/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp +++ b/tests/componenttests/server/tests/mediaPipeline/FirstFrameNotificationTest.cpp @@ -22,6 +22,7 @@ #include "MediaPipelineTest.h" #include +#include #include using testing::_; diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp index b42f6dc31..262aebd2e 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/GstGenericPlayerPrivateTest.cpp @@ -332,38 +332,33 @@ TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstVideoFrameReceived) { std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFirstFrameReceived(_, _, MediaSourceType::VIDEO)) + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::VIDEO, AudioFirstFrameAction::CLEAR_PROBE)) .WillOnce(Return(ByMove(std::move(task)))); m_sut->scheduleFirstVideoFrameReceived(); } -TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameReceived) +TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameFromSignal) { std::unique_ptr task{std::make_unique>()}; EXPECT_CALL(dynamic_cast &>(*task), execute()); - EXPECT_CALL(m_taskFactoryMock, createFirstFrameReceived(_, _, MediaSourceType::AUDIO)) + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::AUDIO, AudioFirstFrameAction::CLEAR_PROBE)) .WillOnce(Return(ByMove(std::move(task)))); - m_sut->scheduleFirstAudioFrameReceived(); -} - -TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameFromSignal) -{ - EXPECT_CALL(m_gstPlayerClient, notifyFirstFrameReceived(MediaSourceType::AUDIO)); - - m_sut->scheduleFirstAudioFrameFromSignal(); - - modifyContext([&](const GenericPlayerContext &context) { EXPECT_TRUE(context.firstAudioFrameReceived); }); + m_sut->scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE); } TEST_F(GstGenericPlayerPrivateTest, shouldScheduleFirstAudioFrameFromFallbackProbe) { - EXPECT_CALL(m_gstPlayerClient, notifyFirstFrameReceived(MediaSourceType::AUDIO)); - - m_sut->scheduleFirstAudioFrameFromFallbackProbe(); + std::unique_ptr task{std::make_unique>()}; + EXPECT_CALL(dynamic_cast &>(*task), execute()); + EXPECT_CALL(m_taskFactoryMock, + createFirstFrameReceived(_, _, MediaSourceType::AUDIO, AudioFirstFrameAction::CLEAR_PROBE_STATE)) + .WillOnce(Return(ByMove(std::move(task)))); - modifyContext([&](const GenericPlayerContext &context) { EXPECT_TRUE(context.firstAudioFrameReceived); }); + m_sut->scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE_STATE); } TEST_F(GstGenericPlayerPrivateTest, shouldSetAudioFirstFrameFallbackProbe) diff --git a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp index ad7180d35..f62f591c0 100644 --- a/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp +++ b/tests/unittests/media/server/gstplayer/genericPlayer/common/GenericTasksTestsBase.cpp @@ -1178,14 +1178,14 @@ void GenericTasksTestsBase::shouldSetFirstAudioFrameCallback() { ASSERT_TRUE(testContext->m_firstAudioFrameCallback); ASSERT_TRUE(testContext->m_audioUserData); - EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameFromSignal()); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE)); } void GenericTasksTestsBase::shouldSetFirstAudioFrameFallbackProbeCallback() { ASSERT_TRUE(testContext->m_firstAudioFrameProbeCallback); ASSERT_TRUE(testContext->m_firstAudioFrameProbeUserData); - EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameFromFallbackProbe()); + EXPECT_CALL(testContext->m_gstPlayer, scheduleFirstAudioFrameReceived(AudioFirstFrameAction::CLEAR_PROBE_STATE)); } void GenericTasksTestsBase::shouldSetupBaseParse() @@ -3426,7 +3426,9 @@ void GenericTasksTestsBase::triggerReadShmDataAndAttachSamples() void GenericTasksTestsBase::shouldFlushAudio() { + testContext->m_context.firstAudioFrameReceived = true; EXPECT_CALL(*testContext->m_gstWrapper, gstBufferUnref(&testContext->m_audioBuffer)); + EXPECT_CALL(testContext->m_gstPlayer, clearAudioFirstFrameFallbackProbe()); EXPECT_CALL(testContext->m_gstPlayerClient, invalidateActiveRequests(firebolt::rialto::MediaSourceType::AUDIO)); EXPECT_CALL(testContext->m_gstPlayerClient, notifySourceFlushed(firebolt::rialto::MediaSourceType::AUDIO)); EXPECT_CALL(testContext->m_gstPlayer, setSourceFlushed(firebolt::rialto::MediaSourceType::AUDIO)); @@ -3465,6 +3467,7 @@ void GenericTasksTestsBase::checkAudioFlushed() testContext->m_context.endOfStreamInfo.end()); EXPECT_NE(testContext->m_context.endOfStreamInfo.find(firebolt::rialto::MediaSourceType::VIDEO), testContext->m_context.endOfStreamInfo.end()); + EXPECT_FALSE(testContext->m_context.firstAudioFrameReceived); } void GenericTasksTestsBase::checkVideoFlushed() diff --git a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h index 77fac21c3..8b9fc2a8d 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GenericPlayerTaskFactoryMock.h @@ -48,7 +48,8 @@ class GenericPlayerTaskFactoryMock : public IGenericPlayerTaskFactory const firebolt::rialto::MediaSourceType &type), (const, override)); MOCK_METHOD(std::unique_ptr, createFirstFrameReceived, - (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType), + (GenericPlayerContext & context, IGstGenericPlayerPrivate &player, MediaSourceType sourceType, + AudioFirstFrameAction audioAction), (const, override)); MOCK_METHOD(std::unique_ptr, createFinishSetupSource, (GenericPlayerContext & context, IGstGenericPlayerPrivate &player), (const, override)); diff --git a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h index e00d0f7f9..b5d134fff 100644 --- a/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h +++ b/tests/unittests/media/server/mocks/gstplayer/GstGenericPlayerPrivateMock.h @@ -37,9 +37,7 @@ class GstGenericPlayerPrivateMock : public IGstGenericPlayerPrivate MOCK_METHOD(void, scheduleAudioUnderflow, (), (override)); MOCK_METHOD(void, scheduleVideoUnderflow, (), (override)); MOCK_METHOD(void, scheduleFirstVideoFrameReceived, (), (override)); - MOCK_METHOD(void, scheduleFirstAudioFrameReceived, (), (override)); - MOCK_METHOD(void, scheduleFirstAudioFrameFromSignal, (), (override)); - MOCK_METHOD(void, scheduleFirstAudioFrameFromFallbackProbe, (), (override)); + MOCK_METHOD(void, scheduleFirstAudioFrameReceived, (AudioFirstFrameAction audioAction), (override)); MOCK_METHOD(void, setAudioFirstFrameFallbackProbe, (GstPad * pad, gulong id), (override)); MOCK_METHOD(void, clearAudioFirstFrameFallbackProbe, (), (override)); MOCK_METHOD(void, clearAudioFirstFrameFallbackProbeState, (), (override));