Coverage Report

Created: 2025-12-28 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gstreamer/subprojects/gst-plugins-base/gst-libs/gst/video/gstvideosink.c
Line
Count
Source
1
/*  GStreamer video sink base class
2
 *  Copyright (C) <2003> Julien Moutte <julien@moutte.net>
3
 *  Copyright (C) <2009> Tim-Philipp Müller <tim centricular net>
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Library General Public
7
 * License as published by the Free Software Foundation; either
8
 * version 2 of the License, or (at your option) any later version.
9
 *
10
 * This library is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Library General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Library General Public
16
 * License along with this library; if not, write to the
17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18
 * Boston, MA 02110-1301, USA.
19
 */
20
21
/**
22
 * SECTION:gstvideosink
23
 * @title: GstVideoSink
24
 * @short_description: Base class for video sinks
25
 *
26
 * Provides useful functions and a base class for video sinks.
27
 *
28
 * GstVideoSink will configure the default base sink to drop frames that
29
 * arrive later than 20ms as this is considered the default threshold for
30
 * observing out-of-sync frames.
31
 *
32
 */
33
34
#ifdef HAVE_CONFIG_H
35
#include "config.h"
36
#endif
37
38
#include "gstvideosink.h"
39
40
enum
41
{
42
  PROP_SHOW_PREROLL_FRAME = 1
43
};
44
45
0
#define DEFAULT_SHOW_PREROLL_FRAME TRUE
46
47
struct _GstVideoSinkPrivate
48
{
49
  GstVideoInfo info;
50
  gboolean show_preroll_frame;  /* ATOMIC */
51
};
52
53
0
G_DEFINE_TYPE_WITH_PRIVATE (GstVideoSink, gst_video_sink, GST_TYPE_BASE_SINK);
54
0
55
0
#ifndef GST_DISABLE_GST_DEBUG
56
0
#define GST_CAT_DEFAULT gst_video_sink_ensure_debug_category()
57
0
58
0
static GstDebugCategory *
59
0
gst_video_sink_ensure_debug_category (void)
60
0
{
61
0
  static gsize cat_gonce = 0;
62
63
0
  if (g_once_init_enter (&cat_gonce)) {
64
0
    GstDebugCategory *cat = NULL;
65
66
0
    GST_DEBUG_CATEGORY_INIT (cat, "videosink", 0, "GstVideoSink");
67
68
0
    g_once_init_leave (&cat_gonce, (gsize) cat);
69
0
  }
70
71
0
  return (GstDebugCategory *) cat_gonce;
72
0
}
73
#endif /* GST_DISABLE_GST_DEBUG */
74
75
static GstBaseSinkClass *parent_class = NULL;
76
77
static void gst_video_sink_set_property (GObject * object, guint prop_id,
78
    const GValue * value, GParamSpec * pspec);
79
static void gst_video_sink_get_property (GObject * object, guint prop_id,
80
    GValue * value, GParamSpec * pspec);
81
82
static GstStateChangeReturn gst_video_sink_change_state (GstElement * element,
83
    GstStateChange transition);
84
static GstFlowReturn gst_video_sink_show_preroll_frame (GstBaseSink * bsink,
85
    GstBuffer * buf);
86
static GstFlowReturn gst_video_sink_show_frame (GstBaseSink * bsink,
87
    GstBuffer * buf);
88
static gboolean gst_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
89
static void gst_video_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
90
    GstClockTime * start, GstClockTime * end);
91
92
/**
93
 * gst_video_sink_center_rect:
94
 * @src: the #GstVideoRectangle describing the source area
95
 * @dst: the #GstVideoRectangle describing the destination area
96
 * @result: (out caller-allocates): a pointer to a #GstVideoRectangle which will receive the result area
97
 * @scaling: a #gboolean indicating if scaling should be applied or not
98
 *
99
 * Deprecated: 1.20: Use gst_video_center_rect() instead.
100
 */
101
void
102
gst_video_sink_center_rect (GstVideoRectangle src, GstVideoRectangle dst,
103
    GstVideoRectangle * result, gboolean scaling)
104
0
{
105
0
  gst_video_center_rect (&src, &dst, result, scaling);
106
0
}
107
108
/**
109
 * gst_video_center_rect:
110
 * @src: a pointer to #GstVideoRectangle describing the source area
111
 * @dst: a pointer to #GstVideoRectangle describing the destination area
112
 * @result: (out caller-allocates): a pointer to a #GstVideoRectangle which will receive the result area
113
 * @scaling: a #gboolean indicating if scaling should be applied or not
114
 *
115
 * Takes @src rectangle and position it at the center of @dst rectangle with or
116
 * without @scaling. It handles clipping if the @src rectangle is bigger than
117
 * the @dst one and @scaling is set to FALSE.
118
 *
119
 * Since: 1.20
120
 */
121
void
122
gst_video_center_rect (const GstVideoRectangle * src,
123
    const GstVideoRectangle * dst, GstVideoRectangle * result, gboolean scaling)
124
0
{
125
0
  g_return_if_fail (src != NULL);
126
0
  g_return_if_fail (dst != NULL);
127
0
  g_return_if_fail (result != NULL);
128
129
0
  if (!scaling) {
130
0
    result->w = MIN (src->w, dst->w);
131
0
    result->h = MIN (src->h, dst->h);
132
0
    result->x = dst->x + (dst->w - result->w) / 2;
133
0
    result->y = dst->y + (dst->h - result->h) / 2;
134
0
  } else {
135
0
    gdouble src_ratio, dst_ratio;
136
137
0
    g_return_if_fail (src->h != 0);
138
0
    g_return_if_fail (dst->h != 0);
139
140
0
    src_ratio = (gdouble) src->w / src->h;
141
0
    dst_ratio = (gdouble) dst->w / dst->h;
142
143
0
    if (src_ratio > dst_ratio) {
144
0
      result->w = dst->w;
145
0
      result->h = dst->w / src_ratio;
146
0
      result->x = dst->x;
147
0
      result->y = dst->y + (dst->h - result->h) / 2;
148
0
    } else if (src_ratio < dst_ratio) {
149
0
      result->w = dst->h * src_ratio;
150
0
      result->h = dst->h;
151
0
      result->x = dst->x + (dst->w - result->w) / 2;
152
0
      result->y = dst->y;
153
0
    } else {
154
0
      result->x = dst->x;
155
0
      result->y = dst->y;
156
0
      result->w = dst->w;
157
0
      result->h = dst->h;
158
0
    }
159
0
  }
160
161
0
  GST_DEBUG ("source is %dx%d dest is %dx%d, result is %dx%d with x,y %dx%d",
162
0
      src->w, src->h, dst->w, dst->h,
163
0
      result->w, result->h, result->x, result->y);
164
0
}
165
166
/* Initing stuff */
167
168
static void
169
gst_video_sink_init (GstVideoSink * videosink)
170
0
{
171
0
  videosink->width = 0;
172
0
  videosink->height = 0;
173
174
  /* 20ms is more than enough, 80-130ms is noticeable */
175
0
  gst_base_sink_set_processing_deadline (GST_BASE_SINK (videosink),
176
0
      15 * GST_MSECOND);
177
0
  gst_base_sink_set_max_lateness (GST_BASE_SINK (videosink), 5 * GST_MSECOND);
178
0
  gst_base_sink_set_qos_enabled (GST_BASE_SINK (videosink), TRUE);
179
180
0
  videosink->priv = gst_video_sink_get_instance_private (videosink);
181
0
}
182
183
static void
184
gst_video_sink_class_init (GstVideoSinkClass * klass)
185
0
{
186
0
  GstElementClass *element_class = (GstElementClass *) klass;
187
0
  GstBaseSinkClass *basesink_class = (GstBaseSinkClass *) klass;
188
0
  GObjectClass *gobject_class = (GObjectClass *) klass;
189
190
0
  parent_class = g_type_class_peek_parent (klass);
191
192
0
  gobject_class->set_property = gst_video_sink_set_property;
193
0
  gobject_class->get_property = gst_video_sink_get_property;
194
195
  /**
196
   * GstVideoSink:show-preroll-frame:
197
   *
198
   * Whether to show video frames during preroll. If set to %FALSE, video
199
   * frames will only be rendered in PLAYING state.
200
   */
201
0
  g_object_class_install_property (gobject_class, PROP_SHOW_PREROLL_FRAME,
202
0
      g_param_spec_boolean ("show-preroll-frame", "Show preroll frame",
203
0
          "Whether to render video frames during preroll",
204
0
          DEFAULT_SHOW_PREROLL_FRAME,
205
0
          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
206
207
0
  element_class->change_state = GST_DEBUG_FUNCPTR (gst_video_sink_change_state);
208
209
0
  basesink_class->render = GST_DEBUG_FUNCPTR (gst_video_sink_show_frame);
210
0
  basesink_class->preroll =
211
0
      GST_DEBUG_FUNCPTR (gst_video_sink_show_preroll_frame);
212
0
  basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_sink_set_caps);
213
0
  basesink_class->get_times = GST_DEBUG_FUNCPTR (gst_video_sink_get_times);
214
0
}
215
216
static GstStateChangeReturn
217
gst_video_sink_change_state (GstElement * element, GstStateChange transition)
218
0
{
219
0
  GstVideoSink *vsink;
220
221
0
  vsink = GST_VIDEO_SINK_CAST (element);
222
223
0
  switch (transition) {
224
0
    case GST_STATE_CHANGE_READY_TO_PAUSED:
225
0
      gst_video_info_init (&vsink->priv->info);
226
0
      break;
227
0
    default:
228
0
      break;
229
0
  }
230
231
0
  return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
232
0
}
233
234
static gboolean
235
gst_video_sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
236
0
{
237
0
  GstVideoSink *vsink;
238
0
  GstVideoSinkClass *klass;
239
0
  GstVideoInfo info;
240
241
0
  vsink = GST_VIDEO_SINK_CAST (bsink);
242
0
  klass = GST_VIDEO_SINK_GET_CLASS (vsink);
243
244
0
  if (!gst_video_info_from_caps (&info, caps)) {
245
0
    GST_ERROR_OBJECT (bsink, "Failed to parse caps %" GST_PTR_FORMAT, caps);
246
0
    return FALSE;
247
0
  }
248
249
0
  GST_DEBUG_OBJECT (bsink, "Setting caps %" GST_PTR_FORMAT, caps);
250
0
  vsink->priv->info = info;
251
252
0
  if (klass->set_info)
253
0
    return klass->set_info (vsink, caps, &vsink->priv->info);
254
255
0
  return TRUE;
256
0
}
257
258
static void
259
gst_video_sink_get_times (GstBaseSink * bsink, GstBuffer * buffer,
260
    GstClockTime * start, GstClockTime * end)
261
0
{
262
0
  GstVideoSink *vsink;
263
0
  GstClockTime timestamp;
264
265
0
  vsink = GST_VIDEO_SINK_CAST (bsink);
266
267
0
  timestamp = GST_BUFFER_DTS_OR_PTS (buffer);
268
0
  if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
269
0
    *start = timestamp;
270
0
    if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
271
0
      *end = timestamp + GST_BUFFER_DURATION (buffer);
272
0
    } else if (vsink->priv->info.fps_n > 0) {
273
0
      *end = timestamp +
274
0
          gst_util_uint64_scale_int (GST_SECOND, vsink->priv->info.fps_d,
275
0
          vsink->priv->info.fps_n);
276
0
    } else if (bsink->segment.rate < 0) {
277
      /* The end time will be used for clock waiting time position
278
       * in case of revese playback, and unknown end time will result in
279
       * never waiting for clock (as if sync=false).
280
       * Returning timestamp here would be the best effort we can do */
281
0
      *end = timestamp;
282
0
    }
283
0
  }
284
0
}
285
286
static GstFlowReturn
287
gst_video_sink_show_preroll_frame (GstBaseSink * bsink, GstBuffer * buf)
288
0
{
289
0
  GstVideoSinkClass *klass;
290
0
  GstVideoSink *vsink;
291
0
  gboolean do_show;
292
293
0
  vsink = GST_VIDEO_SINK_CAST (bsink);
294
0
  klass = GST_VIDEO_SINK_GET_CLASS (vsink);
295
296
0
  do_show = g_atomic_int_get (&vsink->priv->show_preroll_frame);
297
298
0
  if (G_UNLIKELY (!do_show)) {
299
0
    GST_DEBUG_OBJECT (bsink, "not rendering frame with ts=%" GST_TIME_FORMAT
300
0
        ", preroll rendering disabled",
301
0
        GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
302
0
  }
303
304
0
  if (klass->show_frame == NULL || !do_show) {
305
0
    if (parent_class->preroll != NULL)
306
0
      return parent_class->preroll (bsink, buf);
307
0
    else
308
0
      return GST_FLOW_OK;
309
0
  }
310
311
0
  GST_LOG_OBJECT (bsink, "rendering frame, ts=%" GST_TIME_FORMAT,
312
0
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
313
314
0
  return klass->show_frame (GST_VIDEO_SINK_CAST (bsink), buf);
315
0
}
316
317
static GstFlowReturn
318
gst_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
319
0
{
320
0
  GstVideoSinkClass *klass;
321
322
0
  klass = GST_VIDEO_SINK_GET_CLASS (bsink);
323
324
0
  if (klass->show_frame == NULL) {
325
0
    if (parent_class->render != NULL)
326
0
      return parent_class->render (bsink, buf);
327
0
    else
328
0
      return GST_FLOW_OK;
329
0
  }
330
331
0
  GST_LOG_OBJECT (bsink, "rendering frame, ts=%" GST_TIME_FORMAT,
332
0
      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)));
333
334
0
  return klass->show_frame (GST_VIDEO_SINK_CAST (bsink), buf);
335
0
}
336
337
static void
338
gst_video_sink_set_property (GObject * object, guint prop_id,
339
    const GValue * value, GParamSpec * pspec)
340
0
{
341
0
  GstVideoSink *vsink;
342
343
0
  vsink = GST_VIDEO_SINK (object);
344
345
0
  switch (prop_id) {
346
0
    case PROP_SHOW_PREROLL_FRAME:
347
0
      g_atomic_int_set (&vsink->priv->show_preroll_frame,
348
0
          g_value_get_boolean (value));
349
0
      break;
350
0
    default:
351
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
352
0
      break;
353
0
  }
354
0
}
355
356
static void
357
gst_video_sink_get_property (GObject * object, guint prop_id,
358
    GValue * value, GParamSpec * pspec)
359
0
{
360
0
  GstVideoSink *vsink;
361
362
0
  vsink = GST_VIDEO_SINK (object);
363
364
0
  switch (prop_id) {
365
0
    case PROP_SHOW_PREROLL_FRAME:
366
0
      g_value_set_boolean (value,
367
0
          g_atomic_int_get (&vsink->priv->show_preroll_frame));
368
0
      break;
369
0
    default:
370
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
371
0
      break;
372
0
  }
373
0
}