gstwatchdog.c 12.3 KB
Newer Older
David Schleef's avatar
David Schleef committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* GStreamer
 * Copyright (C) 2013 Rdio <ingestions@rdio.com>
 * Copyright (C) 2013 David Schleef <ds@schleef.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
 * Boston, MA 02110-1335, USA.
 */
/**
 * SECTION:element-gstwatchdog
22
 * @title: watchdog
David Schleef's avatar
David Schleef committed
23 24 25 26 27 28 29 30 31 32 33
 *
 * The watchdog element watches buffers and events flowing through
 * a pipeline.  If no buffers are seen for a configurable amount of
 * time, a error message is sent to the bus.
 *
 * To use this element, insert it into a pipeline as you would an
 * identity element.  Once activated, any pause in the flow of
 * buffers through the element will cause an element error.  The
 * maximum allowed pause is determined by the timeout property.
 *
 * This element is currently intended for transcoding pipelines,
34
 * although may be useful in other contexts.
David Schleef's avatar
David Schleef committed
35
 *
36
 * ## Example launch line
David Schleef's avatar
David Schleef committed
37
 * |[
38
 * gst-launch-1.0 -v fakesrc ! watchdog ! fakesink
David Schleef's avatar
David Schleef committed
39
 * ]|
40
 *
David Schleef's avatar
David Schleef committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include "gstwatchdog.h"

GST_DEBUG_CATEGORY_STATIC (gst_watchdog_debug_category);
#define GST_CAT_DEFAULT gst_watchdog_debug_category

/* prototypes */

static void gst_watchdog_set_property (GObject * object,
    guint property_id, const GValue * value, GParamSpec * pspec);
static void gst_watchdog_get_property (GObject * object,
    guint property_id, GValue * value, GParamSpec * pspec);

static gboolean gst_watchdog_start (GstBaseTransform * trans);
static gboolean gst_watchdog_stop (GstBaseTransform * trans);
static gboolean gst_watchdog_sink_event (GstBaseTransform * trans,
    GstEvent * event);
static gboolean gst_watchdog_src_event (GstBaseTransform * trans,
    GstEvent * event);
static GstFlowReturn gst_watchdog_transform_ip (GstBaseTransform * trans,
    GstBuffer * buf);
69 70
static void gst_watchdog_feed (GstWatchdog * watchdog, gpointer mini_object,
    gboolean force);
71 72 73

static GstStateChangeReturn
gst_watchdog_change_state (GstElement * element, GstStateChange transition);
David Schleef's avatar
David Schleef committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

enum
{
  PROP_0,
  PROP_TIMEOUT
};

/* class initialization */

G_DEFINE_TYPE_WITH_CODE (GstWatchdog, gst_watchdog, GST_TYPE_BASE_TRANSFORM,
    GST_DEBUG_CATEGORY_INIT (gst_watchdog_debug_category, "watchdog", 0,
        "debug category for watchdog element"));

static void
gst_watchdog_class_init (GstWatchdogClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstBaseTransformClass *base_transform_class =
      GST_BASE_TRANSFORM_CLASS (klass);

94 95
  GstElementClass *gstelement_klass = (GstElementClass *) klass;

David Schleef's avatar
David Schleef committed
96 97 98 99 100 101 102 103 104 105 106
  gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass),
      gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
          gst_caps_new_any ()));
  gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass),
      gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
          gst_caps_new_any ()));

  gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
      "Watchdog", "Generic", "Watches for pauses in stream buffers",
      "David Schleef <ds@schleef.org>");

107 108
  gstelement_klass->change_state =
      GST_DEBUG_FUNCPTR (gst_watchdog_change_state);
David Schleef's avatar
David Schleef committed
109 110 111 112 113 114 115 116 117 118 119 120 121
  gobject_class->set_property = gst_watchdog_set_property;
  gobject_class->get_property = gst_watchdog_get_property;
  base_transform_class->start = GST_DEBUG_FUNCPTR (gst_watchdog_start);
  base_transform_class->stop = GST_DEBUG_FUNCPTR (gst_watchdog_stop);
  base_transform_class->sink_event =
      GST_DEBUG_FUNCPTR (gst_watchdog_sink_event);
  base_transform_class->src_event = GST_DEBUG_FUNCPTR (gst_watchdog_src_event);
  base_transform_class->transform_ip =
      GST_DEBUG_FUNCPTR (gst_watchdog_transform_ip);

  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
      g_param_spec_int ("timeout", "Timeout", "Timeout (in ms) after "
          "which an element error is sent to the bus if no buffers are "
122
          "received. 0 means disabled.", 0, G_MAXINT, 1000,
David Schleef's avatar
David Schleef committed
123 124 125 126 127 128 129 130 131
          G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

}

static void
gst_watchdog_init (GstWatchdog * watchdog)
{
}

132
static void
David Schleef's avatar
David Schleef committed
133 134 135 136 137 138 139 140 141
gst_watchdog_set_property (GObject * object, guint property_id,
    const GValue * value, GParamSpec * pspec)
{
  GstWatchdog *watchdog = GST_WATCHDOG (object);

  GST_DEBUG_OBJECT (watchdog, "set_property");

  switch (property_id) {
    case PROP_TIMEOUT:
142
      GST_OBJECT_LOCK (watchdog);
David Schleef's avatar
David Schleef committed
143
      watchdog->timeout = g_value_get_int (value);
144
      gst_watchdog_feed (watchdog, NULL, FALSE);
145
      GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
146 147 148 149 150 151 152
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

153
static void
David Schleef's avatar
David Schleef committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
gst_watchdog_get_property (GObject * object, guint property_id,
    GValue * value, GParamSpec * pspec)
{
  GstWatchdog *watchdog = GST_WATCHDOG (object);

  GST_DEBUG_OBJECT (watchdog, "get_property");

  switch (property_id) {
    case PROP_TIMEOUT:
      g_value_set_int (value, watchdog->timeout);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}

static gpointer
gst_watchdog_thread (gpointer user_data)
{
  GstWatchdog *watchdog = GST_WATCHDOG (user_data);

  GST_DEBUG_OBJECT (watchdog, "thread starting");

  g_main_loop_run (watchdog->main_loop);

  GST_DEBUG_OBJECT (watchdog, "thread exiting");

  return NULL;
}

static gboolean
gst_watchdog_trigger (gpointer ptr)
{
  GstWatchdog *watchdog = GST_WATCHDOG (ptr);

  GST_DEBUG_OBJECT (watchdog, "watchdog triggered");

  GST_ELEMENT_ERROR (watchdog, STREAM, FAILED, ("Watchdog triggered"),
      ("Watchdog triggered"));

  return FALSE;
}

198 199 200 201 202 203 204 205 206 207 208 209
static gboolean
gst_watchdog_quit_mainloop (gpointer ptr)
{
  GstWatchdog *watchdog = GST_WATCHDOG (ptr);

  GST_DEBUG_OBJECT (watchdog, "watchdog quit");

  g_main_loop_quit (watchdog->main_loop);

  return FALSE;
}

210
/*  Call with OBJECT_LOCK taken */
David Schleef's avatar
David Schleef committed
211
static void
212
gst_watchdog_feed (GstWatchdog * watchdog, gpointer mini_object, gboolean force)
David Schleef's avatar
David Schleef committed
213 214
{
  if (watchdog->source) {
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
    if (watchdog->waiting_for_flush_start) {
      if (mini_object && GST_IS_EVENT (mini_object) &&
          GST_EVENT_TYPE (mini_object) == GST_EVENT_FLUSH_START) {
        watchdog->waiting_for_flush_start = FALSE;
        watchdog->waiting_for_flush_stop = TRUE;
      }

      force = TRUE;
    } else if (watchdog->waiting_for_flush_stop) {
      if (mini_object && GST_IS_EVENT (mini_object) &&
          GST_EVENT_TYPE (mini_object) == GST_EVENT_FLUSH_STOP) {
        watchdog->waiting_for_flush_stop = FALSE;
        watchdog->waiting_for_a_buffer = TRUE;
      }

      force = TRUE;
    } else if (watchdog->waiting_for_a_buffer) {
232 233 234 235 236 237 238 239 240
      if (mini_object && GST_IS_BUFFER (mini_object)) {
        watchdog->waiting_for_a_buffer = FALSE;
        GST_DEBUG_OBJECT (watchdog, "Got a buffer \\o/");
      } else {
        GST_DEBUG_OBJECT (watchdog, "Waiting for a buffer and did not get it,"
            " keep trying even in PAUSED state");
        force = TRUE;
      }
    }
David Schleef's avatar
David Schleef committed
241 242 243
    g_source_destroy (watchdog->source);
    g_source_unref (watchdog->source);
    watchdog->source = NULL;
244

David Schleef's avatar
David Schleef committed
245
  }
246

247 248 249 250 251 252 253 254
  if (watchdog->timeout == 0) {
    GST_LOG_OBJECT (watchdog, "Timeout is 0 => nothing to do");
  } else if (watchdog->main_context == NULL) {
    GST_LOG_OBJECT (watchdog, "No maincontext => nothing to do");
  } else if ((GST_STATE (watchdog) != GST_STATE_PLAYING) && force == FALSE) {
    GST_LOG_OBJECT (watchdog,
        "Not in playing and force is FALSE => Nothing to do");
  } else {
255
    watchdog->source = g_timeout_source_new (watchdog->timeout);
256 257
    g_source_set_callback (watchdog->source, gst_watchdog_trigger,
        gst_object_ref (watchdog), gst_object_unref);
258 259
    g_source_attach (watchdog->source, watchdog->main_context);
  }
David Schleef's avatar
David Schleef committed
260 261 262 263 264 265 266 267
}

static gboolean
gst_watchdog_start (GstBaseTransform * trans)
{
  GstWatchdog *watchdog = GST_WATCHDOG (trans);

  GST_DEBUG_OBJECT (watchdog, "start");
268
  GST_OBJECT_LOCK (watchdog);
David Schleef's avatar
David Schleef committed
269 270 271 272 273

  watchdog->main_context = g_main_context_new ();
  watchdog->main_loop = g_main_loop_new (watchdog->main_context, TRUE);
  watchdog->thread = g_thread_new ("watchdog", gst_watchdog_thread, watchdog);

274
  GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
275 276 277 278 279 280 281
  return TRUE;
}

static gboolean
gst_watchdog_stop (GstBaseTransform * trans)
{
  GstWatchdog *watchdog = GST_WATCHDOG (trans);
282
  GSource *quit_source;
David Schleef's avatar
David Schleef committed
283 284

  GST_DEBUG_OBJECT (watchdog, "stop");
285
  GST_OBJECT_LOCK (watchdog);
David Schleef's avatar
David Schleef committed
286 287 288 289 290 291

  if (watchdog->source) {
    g_source_destroy (watchdog->source);
    g_source_unref (watchdog->source);
    watchdog->source = NULL;
  }
292 293 294 295 296 297 298 299 300

  /* dispatch an idle event that trigger g_main_loop_quit to avoid race
   * between g_main_loop_run and g_main_loop_quit */
  quit_source = g_idle_source_new ();
  g_source_set_callback (quit_source, gst_watchdog_quit_mainloop, watchdog,
      NULL);
  g_source_attach (quit_source, watchdog->main_context);
  g_source_unref (quit_source);

David Schleef's avatar
David Schleef committed
301
  g_thread_join (watchdog->thread);
302 303
  watchdog->thread = NULL;

David Schleef's avatar
David Schleef committed
304
  g_main_loop_unref (watchdog->main_loop);
305 306
  watchdog->main_loop = NULL;

David Schleef's avatar
David Schleef committed
307
  g_main_context_unref (watchdog->main_context);
308
  watchdog->main_context = NULL;
David Schleef's avatar
David Schleef committed
309

310
  GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
311 312 313 314 315 316 317 318 319 320
  return TRUE;
}

static gboolean
gst_watchdog_sink_event (GstBaseTransform * trans, GstEvent * event)
{
  GstWatchdog *watchdog = GST_WATCHDOG (trans);

  GST_DEBUG_OBJECT (watchdog, "sink_event");

321
  GST_OBJECT_LOCK (watchdog);
322
  gst_watchdog_feed (watchdog, event, FALSE);
323
  GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
324

David Schleef's avatar
David Schleef committed
325 326 327
  return
      GST_BASE_TRANSFORM_CLASS (gst_watchdog_parent_class)->sink_event (trans,
      event);
David Schleef's avatar
David Schleef committed
328 329 330 331 332
}

static gboolean
gst_watchdog_src_event (GstBaseTransform * trans, GstEvent * event)
{
333
  gboolean force = FALSE;
David Schleef's avatar
David Schleef committed
334 335 336 337
  GstWatchdog *watchdog = GST_WATCHDOG (trans);

  GST_DEBUG_OBJECT (watchdog, "src_event");

338
  GST_OBJECT_LOCK (watchdog);
339 340 341 342 343 344 345 346
  if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
    GstSeekFlags flags;

    gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);

    if (flags & GST_SEEK_FLAG_FLUSH) {
      force = TRUE;
      GST_DEBUG_OBJECT (watchdog, "Got a FLUSHING seek, we need a buffer now!");
347
      watchdog->waiting_for_flush_start = TRUE;
348 349 350 351
    }
  }

  gst_watchdog_feed (watchdog, event, force);
352
  GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
353

David Schleef's avatar
David Schleef committed
354 355
  return GST_BASE_TRANSFORM_CLASS (gst_watchdog_parent_class)->src_event (trans,
      event);
David Schleef's avatar
David Schleef committed
356 357 358 359 360 361 362 363 364
}

static GstFlowReturn
gst_watchdog_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
{
  GstWatchdog *watchdog = GST_WATCHDOG (trans);

  GST_DEBUG_OBJECT (watchdog, "transform_ip");

365
  GST_OBJECT_LOCK (watchdog);
366
  gst_watchdog_feed (watchdog, buf, FALSE);
367
  GST_OBJECT_UNLOCK (watchdog);
David Schleef's avatar
David Schleef committed
368 369 370

  return GST_FLOW_OK;
}
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386

/*
 * Change state handler for the element.
 */
static GstStateChangeReturn
gst_watchdog_change_state (GstElement * element, GstStateChange transition)
{
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
  GstWatchdog *watchdog = GST_WATCHDOG (element);

  GST_DEBUG_OBJECT (watchdog, "gst_watchdog_change_state");

  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
      /* Activate timer */
      GST_OBJECT_LOCK (watchdog);
387
      gst_watchdog_feed (watchdog, NULL, FALSE);
388 389 390 391 392 393 394 395 396 397 398
      GST_OBJECT_UNLOCK (watchdog);
      break;
    default:
      break;
  }

  ret =
      GST_ELEMENT_CLASS (gst_watchdog_parent_class)->change_state (element,
      transition);

  switch (transition) {
399 400 401 402 403 404
    case GST_STATE_CHANGE_READY_TO_PAUSED:
      GST_OBJECT_LOCK (watchdog);
      watchdog->waiting_for_a_buffer = TRUE;
      gst_watchdog_feed (watchdog, NULL, TRUE);
      GST_OBJECT_UNLOCK (watchdog);
      break;
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
      /* Disable the timer */
      GST_OBJECT_LOCK (watchdog);
      if (watchdog->source) {
        g_source_destroy (watchdog->source);
        g_source_unref (watchdog->source);
        watchdog->source = NULL;
      }
      GST_OBJECT_UNLOCK (watchdog);
      break;
    default:
      break;
  }

  return ret;
}