LCOV - code coverage report
Current view: top level - source - RialtoGStreamerMSEAudioSink.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 95.4 % 459 438
Test Date: 2025-06-24 14:11:58 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2022 Sky UK
       3              :  *
       4              :  * This library is free software; you can redistribute it and/or
       5              :  * modify it under the terms of the GNU Lesser General Public
       6              :  * License as published by the Free Software Foundation;
       7              :  * version 2.1 of the License.
       8              :  *
       9              :  * This library is distributed in the hope that it will be useful,
      10              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12              :  * Lesser General Public License for more details.
      13              :  *
      14              :  * You should have received a copy of the GNU Lesser General Public
      15              :  * License along with this library; if not, write to the Free Software
      16              :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      17              :  */
      18              : #include <mutex>
      19              : 
      20              : #include <gst/audio/audio.h>
      21              : #include <gst/gst.h>
      22              : #include <gst/pbutils/pbutils.h>
      23              : #include <inttypes.h>
      24              : #include <stdint.h>
      25              : 
      26              : #include "Constants.h"
      27              : #include "GStreamerEMEUtils.h"
      28              : #include "GStreamerMSEUtils.h"
      29              : #include "IMediaPipelineCapabilities.h"
      30              : #include "RialtoGStreamerMSEAudioSink.h"
      31              : #include "RialtoGStreamerMSEAudioSinkPrivate.h"
      32              : #include "RialtoGStreamerMSEBaseSinkPrivate.h"
      33              : 
      34              : using namespace firebolt::rialto::client;
      35              : 
      36              : GST_DEBUG_CATEGORY_STATIC(RialtoMSEAudioSinkDebug);
      37              : #define GST_CAT_DEFAULT RialtoMSEAudioSinkDebug
      38              : 
      39              : #define rialto_mse_audio_sink_parent_class parent_class
      40          773 : G_DEFINE_TYPE_WITH_CODE(RialtoMSEAudioSink, rialto_mse_audio_sink, RIALTO_TYPE_MSE_BASE_SINK,
      41              :                         G_ADD_PRIVATE(RialtoMSEAudioSink) G_IMPLEMENT_INTERFACE(GST_TYPE_STREAM_VOLUME, NULL)
      42              :                             GST_DEBUG_CATEGORY_INIT(RialtoMSEAudioSinkDebug, "rialtomseaudiosink", 0,
      43              :                                                     "rialto mse audio sink"));
      44              : 
      45              : enum
      46              : {
      47              :     PROP_0,
      48              :     PROP_VOLUME,
      49              :     PROP_MUTE,
      50              :     PROP_GAP,
      51              :     PROP_LOW_LATENCY,
      52              :     PROP_SYNC,
      53              :     PROP_SYNC_OFF,
      54              :     PROP_STREAM_SYNC_MODE,
      55              :     PROP_AUDIO_FADE,
      56              :     PROP_FADE_VOLUME,
      57              :     PROP_LIMIT_BUFFERING_MS,
      58              :     PROP_USE_BUFFERING,
      59              :     PROP_ASYNC,
      60              :     PROP_LAST
      61              : };
      62              : 
      63          415 : static GstStateChangeReturn rialto_mse_audio_sink_change_state(GstElement *element, GstStateChange transition)
      64              : {
      65          415 :     RialtoMSEAudioSink *sink = RIALTO_MSE_AUDIO_SINK(element);
      66          415 :     RialtoMSEBaseSinkPrivate *basePriv = sink->parent.priv;
      67          415 :     RialtoMSEAudioSinkPrivate *priv = sink->priv;
      68              : 
      69          415 :     switch (transition)
      70              :     {
      71          100 :     case GST_STATE_CHANGE_READY_TO_PAUSED:
      72              :     {
      73          100 :         if (!rialto_mse_base_sink_attach_to_media_client_and_set_streams_number(element))
      74              :         {
      75            2 :             return GST_STATE_CHANGE_FAILURE;
      76              :         }
      77              : 
      78           98 :         std::shared_ptr<GStreamerMSEMediaPlayerClient> client = basePriv->m_mediaPlayerManager.getMediaPlayerClient();
      79           98 :         if (!client)
      80              :         {
      81            0 :             GST_ERROR_OBJECT(sink, "MediaPlayerClient is nullptr");
      82            0 :             return GST_STATE_CHANGE_FAILURE;
      83              :         }
      84           98 :         if (priv->isVolumeQueued)
      85              :         {
      86            1 :             client->setVolume(priv->targetVolume, kDefaultVolumeDuration, kDefaultEaseType);
      87            1 :             priv->isVolumeQueued = false;
      88              :         }
      89           98 :         if (priv->isAudioFadeQueued)
      90              :         {
      91              :             AudioFadeConfig audioFadeConfig;
      92              :             {
      93            1 :                 std::lock_guard<std::mutex> lock(priv->audioFadeConfigMutex);
      94            1 :                 audioFadeConfig = priv->audioFadeConfig;
      95              :             }
      96            1 :             client->setVolume(audioFadeConfig.volume, audioFadeConfig.duration, audioFadeConfig.easeType);
      97            1 :             priv->isAudioFadeQueued = false;
      98              :         }
      99           98 :         break;
     100              :     }
     101          315 :     default:
     102          315 :         break;
     103              :     }
     104              : 
     105          413 :     GstStateChangeReturn result = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition);
     106          413 :     if (G_UNLIKELY(result == GST_STATE_CHANGE_FAILURE))
     107              :     {
     108            0 :         GST_WARNING_OBJECT(sink, "State change failed");
     109            0 :         return result;
     110              :     }
     111              : 
     112          413 :     return result;
     113              : }
     114              : 
     115              : static std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource>
     116           96 : rialto_mse_audio_sink_create_media_source(RialtoMSEBaseSink *sink, GstCaps *caps)
     117              : {
     118           96 :     GstStructure *structure = gst_caps_get_structure(caps, 0);
     119           96 :     const gchar *strct_name = gst_structure_get_name(structure);
     120              : 
     121           96 :     firebolt::rialto::AudioConfig audioConfig;
     122           96 :     firebolt::rialto::SegmentAlignment alignment = rialto_mse_base_sink_get_segment_alignment(sink, structure);
     123           96 :     std::shared_ptr<firebolt::rialto::CodecData> codecData = rialto_mse_base_sink_get_codec_data(sink, structure);
     124           96 :     firebolt::rialto::StreamFormat format = rialto_mse_base_sink_get_stream_format(sink, structure);
     125           96 :     std::string mimeType;
     126              : 
     127           96 :     if (strct_name)
     128              :     {
     129          101 :         if (g_str_has_prefix(strct_name, "audio/mpeg") || g_str_has_prefix(strct_name, "audio/x-eac3") ||
     130            6 :             g_str_has_prefix(strct_name, "audio/x-ac3"))
     131              :         {
     132           90 :             gint sample_rate = 0;
     133           90 :             gint number_of_channels = 0;
     134           90 :             gst_structure_get_int(structure, "rate", &sample_rate);
     135           90 :             gst_structure_get_int(structure, "channels", &number_of_channels);
     136              : 
     137          180 :             audioConfig = firebolt::rialto::AudioConfig{static_cast<uint32_t>(number_of_channels),
     138           90 :                                                         static_cast<uint32_t>(sample_rate),
     139           90 :                                                         {}};
     140              : 
     141           90 :             if (g_str_has_prefix(strct_name, "audio/mpeg"))
     142              :             {
     143           88 :                 mimeType = "audio/mp4";
     144              :             }
     145              :             else
     146              :             {
     147            2 :                 mimeType = "audio/x-eac3";
     148              :             }
     149              :         }
     150            5 :         else if (g_str_has_prefix(strct_name, "audio/x-opus"))
     151              :         {
     152            2 :             mimeType = "audio/x-opus";
     153            2 :             guint32 sample_rate = 48000;
     154              :             guint8 number_of_channels, streams, stereo_streams, channel_mapping_family;
     155              :             guint8 channel_mapping[256];
     156            2 :             guint16 pre_skip = 0;
     157            2 :             gint16 gain = 0;
     158            2 :             if (gst_codec_utils_opus_parse_caps(caps, &sample_rate, &number_of_channels, &channel_mapping_family,
     159              :                                                 &streams, &stereo_streams, channel_mapping))
     160              :             {
     161              :                 GstBuffer *id_header;
     162            1 :                 id_header = gst_codec_utils_opus_create_header(sample_rate, number_of_channels, channel_mapping_family,
     163              :                                                                streams, stereo_streams, channel_mapping, pre_skip, gain);
     164            1 :                 std::vector<uint8_t> codec_specific_config;
     165              :                 GstMapInfo lsMap;
     166            1 :                 if (gst_buffer_map(id_header, &lsMap, GST_MAP_READ))
     167              :                 {
     168            1 :                     codec_specific_config.assign(lsMap.data, lsMap.data + lsMap.size);
     169            1 :                     gst_buffer_unmap(id_header, &lsMap);
     170              :                 }
     171              :                 else
     172              :                 {
     173            0 :                     GST_ERROR_OBJECT(sink, "Failed to read opus header details from a GstBuffer!");
     174              :                 }
     175            1 :                 gst_buffer_unref(id_header);
     176              : 
     177            1 :                 audioConfig = firebolt::rialto::AudioConfig{number_of_channels, sample_rate, codec_specific_config};
     178              :             }
     179              :             else
     180              :             {
     181            1 :                 GST_ERROR("Failed to parse opus caps!");
     182            1 :                 return nullptr;
     183              :             }
     184              :         }
     185            3 :         else if (g_str_has_prefix(strct_name, "audio/b-wav") || g_str_has_prefix(strct_name, "audio/x-raw"))
     186              :         {
     187            2 :             gint sample_rate = 0;
     188            2 :             gint number_of_channels = 0;
     189            2 :             std::optional<uint64_t> channelMask;
     190            2 :             gst_structure_get_int(structure, "rate", &sample_rate);
     191            2 :             gst_structure_get_int(structure, "channels", &number_of_channels);
     192              :             std::optional<firebolt::rialto::Layout> layout =
     193            2 :                 rialto_mse_sink_convert_layout(gst_structure_get_string(structure, "layout"));
     194              :             std::optional<firebolt::rialto::Format> format =
     195            2 :                 rialto_mse_sink_convert_format(gst_structure_get_string(structure, "format"));
     196            2 :             const GValue *channelMaskValue = gst_structure_get_value(structure, "channel-mask");
     197            2 :             if (channelMaskValue)
     198              :             {
     199            2 :                 channelMask = gst_value_get_bitmask(channelMaskValue);
     200              :             }
     201              : 
     202            2 :             if (g_str_has_prefix(strct_name, "audio/b-wav"))
     203              :             {
     204            1 :                 mimeType = "audio/b-wav";
     205              :             }
     206              :             else
     207              :             {
     208            1 :                 mimeType = "audio/x-raw";
     209              :             }
     210              : 
     211            4 :             audioConfig = firebolt::rialto::AudioConfig{static_cast<uint32_t>(number_of_channels),
     212            2 :                                                         static_cast<uint32_t>(sample_rate),
     213              :                                                         {},
     214              :                                                         format,
     215              :                                                         layout,
     216            2 :                                                         channelMask};
     217              :         }
     218            1 :         else if (g_str_has_prefix(strct_name, "audio/x-flac"))
     219              :         {
     220            1 :             mimeType = "audio/x-flac";
     221            1 :             gint sample_rate = 0;
     222            1 :             gint number_of_channels = 0;
     223            1 :             gst_structure_get_int(structure, "rate", &sample_rate);
     224            1 :             gst_structure_get_int(structure, "channels", &number_of_channels);
     225            1 :             std::vector<std::vector<uint8_t>> streamHeaderVec;
     226            1 :             const GValue *streamheader = gst_structure_get_value(structure, "streamheader");
     227            1 :             if (streamheader)
     228              :             {
     229            2 :                 for (guint i = 0; i < gst_value_array_get_size(streamheader); ++i)
     230              :                 {
     231            1 :                     const GValue *headerValue = gst_value_array_get_value(streamheader, i);
     232            1 :                     GstBuffer *headerBuffer = gst_value_get_buffer(headerValue);
     233            1 :                     if (headerBuffer)
     234              :                     {
     235            1 :                         GstMappedBuffer mappedBuf(headerBuffer, GST_MAP_READ);
     236            1 :                         if (mappedBuf)
     237              :                         {
     238            1 :                             streamHeaderVec.push_back(
     239            3 :                                 std::vector<std::uint8_t>(mappedBuf.data(), mappedBuf.data() + mappedBuf.size()));
     240              :                         }
     241            1 :                     }
     242              :                 }
     243              :             }
     244            1 :             std::optional<bool> framed;
     245            1 :             gboolean framedValue{FALSE};
     246            1 :             if (gst_structure_get_boolean(structure, "framed", &framedValue))
     247              :             {
     248            1 :                 framed = framedValue;
     249              :             }
     250              : 
     251            2 :             audioConfig = firebolt::rialto::AudioConfig{static_cast<uint32_t>(number_of_channels),
     252            1 :                                                         static_cast<uint32_t>(sample_rate),
     253              :                                                         {},
     254              :                                                         std::nullopt,
     255              :                                                         std::nullopt,
     256              :                                                         std::nullopt,
     257              :                                                         streamHeaderVec,
     258            1 :                                                         framed};
     259              :         }
     260              :         else
     261              :         {
     262            0 :             GST_INFO_OBJECT(sink, "%s audio media source created", strct_name);
     263            0 :             mimeType = strct_name;
     264              :         }
     265              : 
     266          188 :         return std::make_unique<firebolt::rialto::IMediaPipeline::MediaSourceAudio>(mimeType, sink->priv->m_hasDrm,
     267              :                                                                                     audioConfig, alignment, format,
     268           94 :                                                                                     codecData);
     269              :     }
     270              : 
     271            1 :     GST_ERROR_OBJECT(sink, "Empty caps' structure name! Failed to set mime type for audio media source.");
     272            1 :     return nullptr;
     273           96 : }
     274              : 
     275          100 : static gboolean rialto_mse_audio_sink_event(GstPad *pad, GstObject *parent, GstEvent *event)
     276              : {
     277          100 :     RialtoMSEBaseSink *sink = RIALTO_MSE_BASE_SINK(parent);
     278          100 :     RialtoMSEAudioSink *audioSink = RIALTO_MSE_AUDIO_SINK(parent);
     279          100 :     RialtoMSEBaseSinkPrivate *basePriv = sink->priv;
     280          100 :     switch (GST_EVENT_TYPE(event))
     281              :     {
     282           94 :     case GST_EVENT_CAPS:
     283              :     {
     284              :         GstCaps *caps;
     285           94 :         gst_event_parse_caps(event, &caps);
     286           94 :         if (basePriv->m_sourceAttached)
     287              :         {
     288            1 :             GST_INFO_OBJECT(sink, "Source already attached. Skip calling attachSource");
     289            1 :             break;
     290              :         }
     291              : 
     292           93 :         GST_INFO_OBJECT(sink, "Attaching AUDIO source with caps %" GST_PTR_FORMAT, caps);
     293              : 
     294              :         std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource> asource =
     295           93 :             rialto_mse_audio_sink_create_media_source(sink, caps);
     296           93 :         if (asource)
     297              :         {
     298              :             std::shared_ptr<GStreamerMSEMediaPlayerClient> client =
     299           92 :                 sink->priv->m_mediaPlayerManager.getMediaPlayerClient();
     300           92 :             if ((!client) || (!client->attachSource(asource, sink)))
     301              :             {
     302            1 :                 GST_ERROR_OBJECT(sink, "Failed to attach AUDIO source");
     303              :             }
     304              :             else
     305              :             {
     306           91 :                 basePriv->m_sourceAttached = true;
     307           91 :                 RialtoMSEAudioSinkPrivate *priv = audioSink->priv;
     308              : 
     309           91 :                 if (priv->isMuteQueued)
     310              :                 {
     311            1 :                     client->setMute(priv->mute, basePriv->m_sourceId);
     312            1 :                     priv->isMuteQueued = false;
     313              :                 }
     314           91 :                 if (priv->isLowLatencyQueued)
     315              :                 {
     316            2 :                     if (!client->setLowLatency(priv->lowLatency))
     317              :                     {
     318            1 :                         GST_ERROR_OBJECT(audioSink, "Could not set queued low-latency");
     319              :                     }
     320            2 :                     priv->isLowLatencyQueued = false;
     321              :                 }
     322           91 :                 if (priv->isSyncQueued)
     323              :                 {
     324            2 :                     if (!client->setSync(priv->sync))
     325              :                     {
     326            1 :                         GST_ERROR_OBJECT(audioSink, "Could not set queued sync");
     327              :                     }
     328            2 :                     priv->isSyncQueued = false;
     329              :                 }
     330           91 :                 if (priv->isSyncOffQueued)
     331              :                 {
     332            2 :                     if (!client->setSyncOff(priv->syncOff))
     333              :                     {
     334            1 :                         GST_ERROR_OBJECT(audioSink, "Could not set queued sync-off");
     335              :                     }
     336            2 :                     priv->isSyncOffQueued = false;
     337              :                 }
     338           91 :                 if (priv->isStreamSyncModeQueued)
     339              :                 {
     340            2 :                     if (!client->setStreamSyncMode(basePriv->m_sourceId, audioSink->priv->streamSyncMode))
     341              :                     {
     342            1 :                         GST_ERROR_OBJECT(audioSink, "Could not set queued stream-sync-mode");
     343              :                     }
     344            2 :                     priv->isStreamSyncModeQueued = false;
     345              :                 }
     346           91 :                 if (priv->isBufferingLimitQueued)
     347              :                 {
     348            1 :                     client->setBufferingLimit(audioSink->priv->bufferingLimit);
     349            1 :                     priv->isBufferingLimitQueued = false;
     350              :                 }
     351           91 :                 if (priv->isUseBufferingQueued)
     352              :                 {
     353            1 :                     client->setUseBuffering(audioSink->priv->useBuffering);
     354            1 :                     priv->isUseBufferingQueued = false;
     355              :                 }
     356              : 
     357              :                 // check if READY -> PAUSED was requested before source was attached
     358           91 :                 if (GST_STATE_NEXT(sink) == GST_STATE_PAUSED)
     359              :                 {
     360           91 :                     client->pause(sink->priv->m_sourceId);
     361              :                 }
     362              :             }
     363           92 :         }
     364              :         else
     365              :         {
     366            1 :             GST_ERROR_OBJECT(sink, "Failed to create AUDIO source");
     367              :         }
     368           93 :         break;
     369              :     }
     370            4 :     case GST_EVENT_CUSTOM_DOWNSTREAM:
     371              :     case GST_EVENT_CUSTOM_DOWNSTREAM_OOB:
     372              :     {
     373            4 :         if (gst_event_has_name(event, "switch-source"))
     374              :         {
     375            4 :             GST_DEBUG_OBJECT(sink, "Switch source event received");
     376            4 :             const GstStructure *structure{gst_event_get_structure(event)};
     377            4 :             const GValue *value = gst_structure_get_value(structure, "caps");
     378            4 :             if (!value)
     379              :             {
     380            1 :                 GST_ERROR_OBJECT(sink, "Caps not available in switch-source event");
     381            2 :                 break;
     382              :             }
     383            3 :             const GstCaps *caps = gst_value_get_caps(value);
     384            3 :             GstCaps *mutableCaps = gst_caps_copy(caps);
     385              :             std::unique_ptr<firebolt::rialto::IMediaPipeline::MediaSource> asource =
     386            3 :                 rialto_mse_audio_sink_create_media_source(sink, mutableCaps);
     387            3 :             gst_caps_unref(mutableCaps);
     388            3 :             if (!asource)
     389              :             {
     390            1 :                 GST_ERROR_OBJECT(sink, "Not able to parse caps");
     391            1 :                 break;
     392              :             }
     393              :             std::shared_ptr<GStreamerMSEMediaPlayerClient> client =
     394            2 :                 sink->priv->m_mediaPlayerManager.getMediaPlayerClient();
     395            2 :             if ((!client) || (!client->switchSource(asource)))
     396              :             {
     397            1 :                 GST_ERROR_OBJECT(sink, "Failed to switch AUDIO source");
     398              :             }
     399            3 :         }
     400            2 :         break;
     401              :     }
     402            2 :     default:
     403            2 :         break;
     404              :     }
     405              : 
     406          100 :     return rialto_mse_base_sink_event(pad, parent, event);
     407              : }
     408              : 
     409           26 : static void rialto_mse_audio_sink_get_property(GObject *object, guint propId, GValue *value, GParamSpec *pspec)
     410              : {
     411           26 :     RialtoMSEAudioSink *sink = RIALTO_MSE_AUDIO_SINK(object);
     412           26 :     if (!sink)
     413              :     {
     414            0 :         GST_ERROR_OBJECT(object, "Sink not initalised");
     415           11 :         return;
     416              :     }
     417           26 :     RialtoMSEBaseSinkPrivate *basePriv = sink->parent.priv;
     418           26 :     RialtoMSEAudioSinkPrivate *priv = sink->priv;
     419           26 :     if (!basePriv || !priv)
     420              :     {
     421            0 :         GST_ERROR_OBJECT(object, "Private Sink not initalised");
     422            0 :         return;
     423              :     }
     424              : 
     425           26 :     std::shared_ptr<GStreamerMSEMediaPlayerClient> client = basePriv->m_mediaPlayerManager.getMediaPlayerClient();
     426              : 
     427           26 :     switch (propId)
     428              :     {
     429            5 :     case PROP_VOLUME:
     430              :     {
     431              :         double volume;
     432            5 :         if (client)
     433              :         {
     434            3 :             if (client->getVolume(volume))
     435            2 :                 priv->targetVolume = volume;
     436              :             else
     437            1 :                 volume = priv->targetVolume; // Use last known volume
     438              :         }
     439              :         else
     440              :         {
     441            2 :             volume = priv->targetVolume;
     442              :         }
     443            5 :         g_value_set_double(value, volume);
     444            5 :         break;
     445              :     }
     446            3 :     case PROP_MUTE:
     447              :     {
     448            3 :         if (!client)
     449              :         {
     450            2 :             g_value_set_boolean(value, priv->mute);
     451            2 :             return;
     452              :         }
     453            1 :         g_value_set_boolean(value, client->getMute(basePriv->m_sourceId));
     454            1 :         break;
     455              :     }
     456            4 :     case PROP_SYNC:
     457              :     {
     458            4 :         if (!client)
     459              :         {
     460            2 :             g_value_set_boolean(value, priv->sync);
     461            2 :             return;
     462              :         }
     463              : 
     464            2 :         bool sync{kDefaultSync};
     465            2 :         if (!client->getSync(sync))
     466              :         {
     467            1 :             GST_ERROR_OBJECT(sink, "Could not get sync");
     468              :         }
     469            2 :         g_value_set_boolean(value, sync);
     470            2 :         break;
     471              :     }
     472            4 :     case PROP_STREAM_SYNC_MODE:
     473              :     {
     474            4 :         if (!client)
     475              :         {
     476            2 :             g_value_set_int(value, priv->streamSyncMode);
     477            2 :             return;
     478              :         }
     479              : 
     480            2 :         int32_t streamSyncMode{kDefaultStreamSyncMode};
     481            2 :         if (!client->getStreamSyncMode(streamSyncMode))
     482              :         {
     483            1 :             GST_ERROR_OBJECT(sink, "Could not get stream-sync-mode");
     484              :         }
     485            2 :         g_value_set_int(value, streamSyncMode);
     486            2 :         break;
     487              :     }
     488            2 :     case PROP_FADE_VOLUME:
     489              :     {
     490              :         double volume;
     491            2 :         if (!client || !client->getVolume(volume))
     492              :         {
     493            1 :             g_value_set_uint(value, kDefaultFadeVolume);
     494            1 :             return;
     495              :         }
     496            1 :         g_value_set_uint(value, static_cast<uint32_t>(volume * 100.0));
     497            1 :         break;
     498              :     }
     499            3 :     case PROP_LIMIT_BUFFERING_MS:
     500              :     {
     501            3 :         if (!client)
     502              :         {
     503            2 :             g_value_set_uint(value, priv->bufferingLimit);
     504            2 :             return;
     505              :         }
     506            1 :         g_value_set_uint(value, client->getBufferingLimit());
     507            1 :         break;
     508              :     }
     509            3 :     case PROP_USE_BUFFERING:
     510              :     {
     511            3 :         if (!client)
     512              :         {
     513            2 :             g_value_set_boolean(value, priv->useBuffering);
     514            2 :             return;
     515              :         }
     516            1 :         g_value_set_boolean(value, client->getUseBuffering());
     517            1 :         break;
     518              :     }
     519            1 :     case PROP_ASYNC:
     520              :     {
     521              :         // Rialto MSE Audio sink is always async
     522            1 :         g_value_set_boolean(value, TRUE);
     523            1 :         break;
     524              :     }
     525            1 :     default:
     526              :     {
     527            1 :         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
     528            1 :         break;
     529              :     }
     530              :     }
     531           26 : }
     532              : 
     533            3 : firebolt::rialto::EaseType convertCharToEaseType(char easeTypeChar)
     534              : {
     535            3 :     switch (easeTypeChar)
     536              :     {
     537            1 :     case 'L':
     538            1 :         return firebolt::rialto::EaseType::EASE_LINEAR;
     539            1 :     case 'I':
     540            1 :         return firebolt::rialto::EaseType::EASE_IN_CUBIC;
     541            1 :     case 'O':
     542            1 :         return firebolt::rialto::EaseType::EASE_OUT_CUBIC;
     543            0 :     default:
     544            0 :         return firebolt::rialto::EaseType::EASE_LINEAR;
     545              :     }
     546              : }
     547              : 
     548           40 : static void rialto_mse_audio_sink_set_property(GObject *object, guint propId, const GValue *value, GParamSpec *pspec)
     549              : {
     550           40 :     RialtoMSEAudioSink *sink = RIALTO_MSE_AUDIO_SINK(object);
     551           40 :     if (!sink)
     552              :     {
     553            0 :         GST_ERROR_OBJECT(object, "Sink not initalised");
     554           22 :         return;
     555              :     }
     556           40 :     RialtoMSEBaseSinkPrivate *basePriv = sink->parent.priv;
     557           40 :     RialtoMSEAudioSinkPrivate *priv = sink->priv;
     558           40 :     if (!basePriv || !priv)
     559              :     {
     560            0 :         GST_ERROR_OBJECT(object, "Private Sink not initalised");
     561            0 :         return;
     562              :     }
     563              : 
     564           40 :     std::shared_ptr<GStreamerMSEMediaPlayerClient> client = basePriv->m_mediaPlayerManager.getMediaPlayerClient();
     565              : 
     566           40 :     switch (propId)
     567              :     {
     568            3 :     case PROP_VOLUME:
     569              :     {
     570            3 :         priv->targetVolume = g_value_get_double(value);
     571            3 :         if (!client || !basePriv->m_sourceAttached)
     572              :         {
     573            2 :             GST_DEBUG_OBJECT(object, "Enqueue volume setting");
     574            2 :             priv->isVolumeQueued = true;
     575            2 :             return;
     576              :         }
     577            1 :         client->setVolume(priv->targetVolume, kDefaultVolumeDuration, kDefaultEaseType);
     578            1 :         break;
     579              :     }
     580            3 :     case PROP_MUTE:
     581              :     {
     582            3 :         priv->mute = g_value_get_boolean(value);
     583            3 :         if (!client || !basePriv->m_sourceAttached)
     584              :         {
     585            2 :             GST_DEBUG_OBJECT(object, "Enqueue mute setting");
     586            2 :             priv->isMuteQueued = true;
     587            2 :             return;
     588              :         }
     589            1 :         client->setMute(priv->mute, basePriv->m_sourceId);
     590            1 :         break;
     591              :     }
     592            2 :     case PROP_GAP:
     593              :     {
     594            2 :         gint64 position{0}, discontinuityGap{0};
     595            2 :         guint duration{0};
     596            2 :         gboolean audioAac{FALSE};
     597              : 
     598            2 :         GstStructure *gapData = GST_STRUCTURE_CAST(g_value_get_boxed(value));
     599            2 :         if (!gst_structure_get_int64(gapData, "position", &position))
     600              :         {
     601            1 :             GST_WARNING_OBJECT(object, "Set gap: position is missing!");
     602              :         }
     603            2 :         if (!gst_structure_get_uint(gapData, "duration", &duration))
     604              :         {
     605            1 :             GST_WARNING_OBJECT(object, "Set gap: duration is missing!");
     606              :         }
     607            2 :         if (!gst_structure_get_int64(gapData, "discontinuity-gap", &discontinuityGap))
     608              :         {
     609            1 :             GST_WARNING_OBJECT(object, "Set gap: discontinuity gap is missing!");
     610              :         }
     611            2 :         if (!gst_structure_get_boolean(gapData, "audio-aac", &audioAac))
     612              :         {
     613            1 :             GST_WARNING_OBJECT(object, "Set gap: audio aac is missing!");
     614              :         }
     615              : 
     616            2 :         GST_DEBUG_OBJECT(object, "Processing audio gap.");
     617            2 :         client->processAudioGap(position, duration, discontinuityGap, audioAac);
     618            2 :         break;
     619              :     }
     620            5 :     case PROP_LOW_LATENCY:
     621              :     {
     622            5 :         priv->lowLatency = g_value_get_boolean(value);
     623            5 :         if (!client)
     624              :         {
     625            3 :             GST_DEBUG_OBJECT(object, "Enqueue low latency setting");
     626            3 :             priv->isLowLatencyQueued = true;
     627            3 :             return;
     628              :         }
     629              : 
     630            2 :         if (!client->setLowLatency(priv->lowLatency))
     631              :         {
     632            1 :             GST_ERROR_OBJECT(sink, "Could not set low-latency");
     633              :         }
     634            2 :         break;
     635              :     }
     636            5 :     case PROP_SYNC:
     637              :     {
     638            5 :         priv->sync = g_value_get_boolean(value);
     639            5 :         if (!client)
     640              :         {
     641            3 :             GST_DEBUG_OBJECT(object, "Enqueue sync setting");
     642            3 :             priv->isSyncQueued = true;
     643            3 :             return;
     644              :         }
     645              : 
     646            2 :         if (!client->setSync(priv->sync))
     647              :         {
     648            1 :             GST_ERROR_OBJECT(sink, "Could not set sync");
     649              :         }
     650            2 :         break;
     651              :     }
     652            5 :     case PROP_SYNC_OFF:
     653              :     {
     654            5 :         priv->syncOff = g_value_get_boolean(value);
     655            5 :         if (!client)
     656              :         {
     657            3 :             GST_DEBUG_OBJECT(object, "Enqueue sync off setting");
     658            3 :             priv->isSyncOffQueued = true;
     659            3 :             return;
     660              :         }
     661              : 
     662            2 :         if (!client->setSyncOff(priv->syncOff))
     663              :         {
     664            1 :             GST_ERROR_OBJECT(sink, "Could not set sync-off");
     665              :         }
     666            2 :         break;
     667              :     }
     668            5 :     case PROP_STREAM_SYNC_MODE:
     669              :     {
     670            5 :         priv->streamSyncMode = g_value_get_int(value);
     671            5 :         if (!client || !basePriv->m_sourceAttached)
     672              :         {
     673            3 :             GST_DEBUG_OBJECT(object, "Enqueue stream sync mode setting");
     674            3 :             priv->isStreamSyncModeQueued = true;
     675            3 :             return;
     676              :         }
     677              : 
     678            2 :         if (!client->setStreamSyncMode(basePriv->m_sourceId, priv->streamSyncMode))
     679              :         {
     680            1 :             GST_ERROR_OBJECT(sink, "Could not set stream-sync-mode");
     681              :         }
     682            2 :         break;
     683              :     }
     684            4 :     case PROP_AUDIO_FADE:
     685              :     {
     686            4 :         const gchar *audioFadeStr = g_value_get_string(value);
     687              : 
     688            4 :         uint32_t fadeVolume = static_cast<uint32_t>(kDefaultVolume * 100);
     689            4 :         uint32_t duration = kDefaultVolumeDuration;
     690            4 :         char easeTypeChar = 'L';
     691              : 
     692            4 :         int parsedItems = sscanf(audioFadeStr, "%u,%u,%c", &fadeVolume, &duration, &easeTypeChar);
     693              : 
     694            4 :         if (parsedItems == 0)
     695              :         {
     696            1 :             GST_ERROR_OBJECT(object, "Failed to parse any values from audio fade string: %s.", audioFadeStr);
     697            2 :             return;
     698              :         }
     699            3 :         else if (parsedItems == 1 || parsedItems == 2)
     700              :         {
     701            1 :             GST_WARNING_OBJECT(object, "Partially parsed audio fade string: %s. Continuing with values: fadeVolume=%u, duration=%u, easeTypeChar=%c",
     702              :                                audioFadeStr, fadeVolume, duration, easeTypeChar);
     703              :         }
     704              : 
     705            3 :         if (fadeVolume > 100)
     706              :         {
     707            0 :             GST_WARNING_OBJECT(object, "Fade volume is greater than 100. Setting it to 100.");
     708            0 :             fadeVolume = 100;
     709              :         }
     710            3 :         double volume = fadeVolume / 100.0;
     711              : 
     712            3 :         firebolt::rialto::EaseType easeType = convertCharToEaseType(easeTypeChar);
     713              : 
     714              :         {
     715            3 :             std::lock_guard<std::mutex> lock(priv->audioFadeConfigMutex);
     716            3 :             priv->audioFadeConfig.volume = volume;
     717            3 :             priv->audioFadeConfig.duration = duration;
     718            3 :             priv->audioFadeConfig.easeType = easeType;
     719              :         }
     720              : 
     721            3 :         if (!client)
     722              :         {
     723            1 :             GST_DEBUG_OBJECT(object, "Enqueue audio fade setting");
     724            1 :             priv->isAudioFadeQueued = true;
     725            1 :             return;
     726              :         }
     727              : 
     728            2 :         client->setVolume(volume, duration, easeType);
     729            2 :         break;
     730              :     }
     731            3 :     case PROP_LIMIT_BUFFERING_MS:
     732              :     {
     733            3 :         priv->bufferingLimit = g_value_get_uint(value);
     734            3 :         if (!client)
     735              :         {
     736            2 :             GST_DEBUG_OBJECT(object, "Enqueue buffering limit setting");
     737            2 :             priv->isBufferingLimitQueued = true;
     738            2 :             return;
     739              :         }
     740              : 
     741            1 :         client->setBufferingLimit(priv->bufferingLimit);
     742            1 :         break;
     743              :     }
     744            3 :     case PROP_USE_BUFFERING:
     745              :     {
     746            3 :         priv->useBuffering = g_value_get_boolean(value);
     747            3 :         if (!client)
     748              :         {
     749            2 :             GST_DEBUG_OBJECT(object, "Enqueue use buffering setting");
     750            2 :             priv->isUseBufferingQueued = true;
     751            2 :             return;
     752              :         }
     753              : 
     754            1 :         client->setUseBuffering(priv->useBuffering);
     755            1 :         break;
     756              :     }
     757            1 :     case PROP_ASYNC:
     758              :     {
     759            1 :         if (FALSE == g_value_get_boolean(value))
     760              :         {
     761            1 :             GST_WARNING_OBJECT(object, "Cannot set ASYNC to false - not supported");
     762              :         }
     763            1 :         break;
     764              :     }
     765            1 :     default:
     766              :     {
     767            1 :         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec);
     768            1 :         break;
     769              :     }
     770              :     }
     771           40 : }
     772              : 
     773            1 : static void rialto_mse_audio_sink_qos_handle(GstElement *element, uint64_t processed, uint64_t dropped)
     774              : {
     775            1 :     GstBus *bus = gst_element_get_bus(element);
     776              :     /* Hardcode isLive to FALSE and set invalid timestamps */
     777            1 :     GstMessage *message = gst_message_new_qos(GST_OBJECT(element), FALSE, GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE,
     778              :                                               GST_CLOCK_TIME_NONE, GST_CLOCK_TIME_NONE);
     779            1 :     gst_message_set_qos_stats(message, GST_FORMAT_DEFAULT, processed, dropped);
     780            1 :     gst_bus_post(bus, message);
     781            1 :     gst_object_unref(bus);
     782              : }
     783              : 
     784          189 : static void rialto_mse_audio_sink_init(RialtoMSEAudioSink *sink)
     785              : {
     786          189 :     RialtoMSEBaseSinkPrivate *priv = sink->parent.priv;
     787              : 
     788          189 :     sink->priv = static_cast<RialtoMSEAudioSinkPrivate *>(rialto_mse_audio_sink_get_instance_private(sink));
     789          189 :     new (sink->priv) RialtoMSEAudioSinkPrivate();
     790              : 
     791          189 :     if (!rialto_mse_base_sink_initialise_sinkpad(RIALTO_MSE_BASE_SINK(sink)))
     792              :     {
     793            0 :         GST_ERROR_OBJECT(sink, "Failed to initialise AUDIO sink. Sink pad initialisation failed.");
     794            0 :         return;
     795              :     }
     796              : 
     797          189 :     priv->m_mediaSourceType = firebolt::rialto::MediaSourceType::AUDIO;
     798          189 :     gst_pad_set_chain_function(priv->m_sinkPad, rialto_mse_base_sink_chain);
     799          189 :     gst_pad_set_event_function(priv->m_sinkPad, rialto_mse_audio_sink_event);
     800              : 
     801          378 :     priv->m_callbacks.qosCallback = std::bind(rialto_mse_audio_sink_qos_handle, GST_ELEMENT_CAST(sink),
     802          189 :                                               std::placeholders::_1, std::placeholders::_2);
     803              : }
     804              : 
     805            1 : static void rialto_mse_audio_sink_class_init(RialtoMSEAudioSinkClass *klass)
     806              : {
     807            1 :     GObjectClass *gobjectClass = G_OBJECT_CLASS(klass);
     808            1 :     GstElementClass *elementClass = GST_ELEMENT_CLASS(klass);
     809            1 :     gobjectClass->get_property = rialto_mse_audio_sink_get_property;
     810            1 :     gobjectClass->set_property = rialto_mse_audio_sink_set_property;
     811            1 :     elementClass->change_state = rialto_mse_audio_sink_change_state;
     812              : 
     813            1 :     g_object_class_install_property(gobjectClass, PROP_VOLUME,
     814              :                                     g_param_spec_double("volume", "Volume", "Volume of this stream", 0, 1.0,
     815              :                                                         kDefaultVolume,
     816              :                                                         GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
     817              : 
     818            1 :     g_object_class_install_property(gobjectClass, PROP_MUTE,
     819              :                                     g_param_spec_boolean("mute", "Mute", "Mute status of this stream", kDefaultMute,
     820              :                                                          GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
     821              : 
     822            1 :     g_object_class_install_property(gobjectClass, PROP_GAP,
     823              :                                     g_param_spec_boxed("gap", "Gap", "Audio Gap", GST_TYPE_STRUCTURE,
     824              :                                                        (GParamFlags)(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)));
     825              : 
     826            1 :     g_object_class_install_property(gobjectClass, PROP_USE_BUFFERING,
     827              :                                     g_param_spec_boolean("use-buffering",
     828              :                                                          "Use buffering", "Emit GST_MESSAGE_BUFFERING based on low-/high-percent thresholds",
     829              :                                                          kDefaultUseBuffering, G_PARAM_READWRITE));
     830            1 :     g_object_class_install_property(gobjectClass, PROP_ASYNC,
     831              :                                     g_param_spec_boolean("async", "Async", "Asynchronous mode", FALSE, G_PARAM_READWRITE));
     832              : 
     833              :     std::unique_ptr<firebolt::rialto::IMediaPipelineCapabilities> mediaPlayerCapabilities =
     834            1 :         firebolt::rialto::IMediaPipelineCapabilitiesFactory::createFactory()->createMediaPipelineCapabilities();
     835            1 :     if (mediaPlayerCapabilities)
     836              :     {
     837              :         std::vector<std::string> supportedMimeTypes =
     838            1 :             mediaPlayerCapabilities->getSupportedMimeTypes(firebolt::rialto::MediaSourceType::AUDIO);
     839              : 
     840            1 :         rialto_mse_sink_setup_supported_caps(elementClass, supportedMimeTypes);
     841              : 
     842            2 :         const std::string kLowLatencyPropertyName{"low-latency"};
     843            2 :         const std::string kSyncPropertyName{"sync"};
     844            2 :         const std::string kSyncOffPropertyName{"sync-off"};
     845            2 :         const std::string kStreamSyncModePropertyName{"stream-sync-mode"};
     846            2 :         const std::string kAudioFadePropertyName{"audio-fade"};
     847            2 :         const std::string kFadeVolumePropertyName{"fade-volume"};
     848            1 :         const std::string kBufferingLimitPropertyName{"limit-buffering-ms"};
     849              :         const std::vector<std::string> kPropertyNamesToSearch{kLowLatencyPropertyName,     kSyncPropertyName,
     850              :                                                               kSyncOffPropertyName,        kStreamSyncModePropertyName,
     851              :                                                               kBufferingLimitPropertyName, kAudioFadePropertyName,
     852            9 :                                                               kFadeVolumePropertyName};
     853              :         std::vector<std::string> supportedProperties{
     854            1 :             mediaPlayerCapabilities->getSupportedProperties(firebolt::rialto::MediaSourceType::AUDIO,
     855            1 :                                                             kPropertyNamesToSearch)};
     856              : 
     857            8 :         for (auto it = supportedProperties.begin(); it != supportedProperties.end(); ++it)
     858              :         {
     859            7 :             if (kLowLatencyPropertyName == *it)
     860              :             {
     861            1 :                 g_object_class_install_property(gobjectClass, PROP_LOW_LATENCY,
     862              :                                                 g_param_spec_boolean(kLowLatencyPropertyName.c_str(),
     863              :                                                                      "low latency", "Turn on low latency mode, for use with gaming (no audio decoding, no a/v sync)",
     864              :                                                                      kDefaultLowLatency, GParamFlags(G_PARAM_WRITABLE)));
     865              :             }
     866            6 :             else if (kSyncPropertyName == *it)
     867              :             {
     868            1 :                 g_object_class_install_property(gobjectClass, PROP_SYNC,
     869              :                                                 g_param_spec_boolean(kSyncPropertyName.c_str(), "sync", "Clock sync",
     870              :                                                                      kDefaultSync, GParamFlags(G_PARAM_READWRITE)));
     871              :             }
     872            5 :             else if (kSyncOffPropertyName == *it)
     873              :             {
     874            1 :                 g_object_class_install_property(gobjectClass, PROP_SYNC_OFF,
     875              :                                                 g_param_spec_boolean(kSyncOffPropertyName.c_str(),
     876              :                                                                      "sync off", "Turn on free running audio. Must be set before pipeline is PLAYING state.",
     877              :                                                                      kDefaultSyncOff, GParamFlags(G_PARAM_WRITABLE)));
     878              :             }
     879            4 :             else if (kStreamSyncModePropertyName == *it)
     880              :             {
     881            1 :                 g_object_class_install_property(gobjectClass, PROP_STREAM_SYNC_MODE,
     882              :                                                 g_param_spec_int(kStreamSyncModePropertyName.c_str(),
     883              :                                                                  "stream sync mode", "1 - Frame to decode frame will immediately proceed next frame sync, 0 - Frame decoded with no frame sync",
     884              :                                                                  0, G_MAXINT, kDefaultStreamSyncMode,
     885              :                                                                  GParamFlags(G_PARAM_READWRITE)));
     886              :             }
     887            3 :             else if (kAudioFadePropertyName == *it)
     888              :             {
     889            1 :                 g_object_class_install_property(gobjectClass, PROP_AUDIO_FADE,
     890              :                                                 g_param_spec_string(kAudioFadePropertyName.c_str(),
     891              :                                                                     "audio fade", "Start audio fade (vol[0-100],duration ms,easetype[(L)inear,Cubic(I)n,Cubic(O)ut])",
     892              :                                                                     kDefaultAudioFade, GParamFlags(G_PARAM_WRITABLE)));
     893              :             }
     894            2 :             else if (kFadeVolumePropertyName == *it)
     895              :             {
     896            1 :                 g_object_class_install_property(gobjectClass, PROP_FADE_VOLUME,
     897              :                                                 g_param_spec_uint(kFadeVolumePropertyName.c_str(), "fade volume",
     898              :                                                                   "Get current fade volume", 0, 100, kDefaultFadeVolume,
     899              :                                                                   G_PARAM_READABLE));
     900              :             }
     901            1 :             else if (kBufferingLimitPropertyName == *it)
     902              :             {
     903            1 :                 constexpr uint32_t kMaxValue{20000};
     904            1 :                 g_object_class_install_property(gobjectClass, PROP_LIMIT_BUFFERING_MS,
     905              :                                                 g_param_spec_uint("limit-buffering-ms",
     906              :                                                                   "limit buffering ms", "Set millisecond threshold used if limit_buffering is set. Changing this value does not enable/disable limit_buffering",
     907              :                                                                   0, kMaxValue, kDefaultBufferingLimit,
     908              :                                                                   G_PARAM_READWRITE));
     909              :             }
     910              :             else
     911              :             {
     912            0 :                 GST_ERROR("Unexpected property %s returned from rialto", it->c_str());
     913              :             }
     914              :         }
     915            1 :     }
     916              :     else
     917              :     {
     918            0 :         GST_ERROR("Failed to get supported mime types for AUDIO");
     919              :     }
     920              : 
     921            1 :     gst_element_class_set_details_simple(elementClass, "Rialto Audio Sink", "Decoder/Audio/Sink/Audio",
     922              :                                          "Communicates with Rialto Server", "Sky");
     923            2 : }
        

Generated by: LCOV version 2.0-1