[MOVED FROM BAD 018/134] vp8: Improve error handling and debug output
authorSebastian Dröge <sebastian.droege@collabora.co.uk>
Wed, 19 May 2010 15:13:17 +0000 (17:13 +0200)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Sun, 16 Sep 2012 13:27:13 +0000 (15:27 +0200)
ext/vp8/Makefile.am
ext/vp8/gstvp8dec.c
ext/vp8/gstvp8enc.c
ext/vp8/gstvp8utils.c [new file with mode: 0644]
ext/vp8/gstvp8utils.h [new file with mode: 0644]

index 583f263..5487b35 100644 (file)
@@ -4,6 +4,7 @@ plugin_LTLIBRARIES = \
 libgstvp8_la_SOURCES = \
        gstvp8dec.c \
        gstvp8enc.c \
+       gstvp8utils.c \
        plugin.c \
        gst/video/gstbasevideocodec.c \
        gst/video/gstbasevideodecoder.c \
@@ -33,6 +34,7 @@ libgstvp8_la_LIBADD += $(GST_PLUGINS_BASE_LIBS) -lgsttag-@GST_MAJORMINOR@ -lgstv
 libgstvp8_la_LIBADD += $(VPX_LIBS)
 
 noinst_HEADERS = \
+       gstvp8utils.h \
        gst/video/gstbasevideocodec.h \
        gst/video/gstbasevideodecoder.h \
        gst/video/gstbasevideoencoder.h \
index 00eb7e2..f6d08f4 100644 (file)
@@ -36,6 +36,7 @@
 #include <vpx/vpx_decoder.h>
 #include <vpx/vp8dx.h>
 
+#include "gstvp8utils.h"
 
 GST_DEBUG_CATEGORY (gst_vp8dec_debug);
 #define GST_CAT_DEFAULT gst_vp8dec_debug
@@ -156,7 +157,7 @@ gst_vp8_dec_class_init (GstVP8DecClass * klass)
 static void
 gst_vp8_dec_init (GstVP8Dec * gst_vp8_dec, GstVP8DecClass * klass)
 {
-  GST_DEBUG ("gst_vp8_dec_init");
+  GST_DEBUG_OBJECT (gst_vp8_dec, "gst_vp8_dec_init");
 }
 
 static void
@@ -164,7 +165,7 @@ gst_vp8_dec_finalize (GObject * object)
 {
   GstVP8Dec *gst_vp8_dec;
 
-  GST_DEBUG ("finalize");
+  GST_DEBUG_OBJECT (object, "finalize");
 
   g_return_if_fail (GST_IS_GST_VP8_DEC (object));
   gst_vp8_dec = GST_VP8_DEC (object);
@@ -181,7 +182,7 @@ gst_vp8_dec_set_property (GObject * object, guint prop_id,
   g_return_if_fail (GST_IS_GST_VP8_DEC (object));
   src = GST_VP8_DEC (object);
 
-  GST_DEBUG ("gst_vp8_dec_set_property");
+  GST_DEBUG_OBJECT (object, "gst_vp8_dec_set_property");
   switch (prop_id) {
     default:
       break;
@@ -210,6 +211,7 @@ gst_vp8_dec_start (GstBaseVideoDecoder * decoder)
 {
   GstVP8Dec *gst_vp8_dec = GST_VP8_DEC (decoder);
 
+  GST_DEBUG_OBJECT (gst_vp8_dec, "start");
   decoder->packetized = TRUE;
   gst_vp8_dec->decoder_inited = FALSE;
 
@@ -221,6 +223,7 @@ gst_vp8_dec_stop (GstBaseVideoDecoder * base_video_decoder)
 {
   GstVP8Dec *gst_vp8_dec = GST_VP8_DEC (base_video_decoder);
 
+  GST_DEBUG_OBJECT (gst_vp8_dec, "stop");
   if (gst_vp8_dec->decoder_inited)
     vpx_codec_destroy (&gst_vp8_dec->decoder);
   gst_vp8_dec->decoder_inited = FALSE;
@@ -232,7 +235,7 @@ gst_vp8_dec_reset (GstBaseVideoDecoder * base_video_decoder)
 {
   GstVP8Dec *decoder;
 
-  GST_DEBUG ("reset");
+  GST_DEBUG_OBJECT (base_video_decoder, "reset");
 
   decoder = GST_VP8_DEC (base_video_decoder);
 
@@ -246,7 +249,6 @@ gst_vp8_dec_reset (GstBaseVideoDecoder * base_video_decoder)
 static GstFlowReturn
 gst_vp8_dec_parse_data (GstBaseVideoDecoder * decoder, gboolean at_eos)
 {
-
   return GST_FLOW_OK;
 }
 
@@ -315,12 +317,11 @@ gst_vp8_dec_handle_frame (GstBaseVideoDecoder * decoder, GstVideoFrame * frame)
 {
   GstVP8Dec *dec;
   GstFlowReturn ret;
-  long status;
+  vpx_codec_err_t status;
   vpx_codec_iter_t iter = NULL;
   vpx_image_t *img;
-  vpx_codec_err_t res;
 
-  GST_DEBUG ("handle_frame");
+  GST_DEBUG_OBJECT (decoder, "handle_frame");
 
   dec = GST_VP8_DEC (decoder);
 
@@ -331,11 +332,12 @@ gst_vp8_dec_handle_frame (GstBaseVideoDecoder * decoder, GstVideoFrame * frame)
     memset (&stream_info, 0, sizeof (stream_info));
     stream_info.sz = sizeof (stream_info);
 
-    res = vpx_codec_peek_stream_info (&vpx_codec_vp8_dx_algo,
+    status = vpx_codec_peek_stream_info (&vpx_codec_vp8_dx_algo,
         GST_BUFFER_DATA (frame->sink_buffer),
         GST_BUFFER_SIZE (frame->sink_buffer), &stream_info);
 
-    if (res != VPX_CODEC_OK || !stream_info.is_kf) {
+    if (status != VPX_CODEC_OK || !stream_info.is_kf) {
+      GST_WARNING_OBJECT (decoder, "No keyframe, skipping");
       gst_base_video_decoder_skip_frame (decoder, frame);
       return GST_FLOW_OK;
     }
@@ -346,11 +348,12 @@ gst_vp8_dec_handle_frame (GstBaseVideoDecoder * decoder, GstVideoFrame * frame)
     decoder->state.format = GST_VIDEO_FORMAT_I420;
     gst_vp8_dec_send_tags (dec);
 
-    res =
+    status =
         vpx_codec_dec_init (&dec->decoder, &vpx_codec_vp8_dx_algo, NULL, flags);
-    if (res != VPX_CODEC_OK) {
+    if (status != VPX_CODEC_OK) {
       GST_ELEMENT_ERROR (dec, LIBRARY, INIT,
-          ("Failed to initialize VP8 decoder"), (NULL));
+          ("Failed to initialize VP8 decoder"), ("%s",
+              gst_vpx_error_name (status)));
       return GST_FLOW_ERROR;
     }
     dec->decoder_inited = TRUE;
@@ -376,7 +379,8 @@ gst_vp8_dec_handle_frame (GstBaseVideoDecoder * decoder, GstVideoFrame * frame)
 
   ret = gst_base_video_decoder_alloc_src_frame (decoder, frame);
   if (ret != GST_FLOW_OK) {
-    GST_WARNING ("failed to get buffer");
+    GST_WARNING_OBJECT (decoder, "failed to get buffer: %s",
+        gst_flow_get_name (ret));
     goto out;
   }
 
@@ -384,6 +388,8 @@ gst_vp8_dec_handle_frame (GstBaseVideoDecoder * decoder, GstVideoFrame * frame)
       GST_BUFFER_DATA (frame->sink_buffer),
       GST_BUFFER_SIZE (frame->sink_buffer), NULL, 0);
   if (status) {
+    GST_ELEMENT_ERROR (decoder, LIBRARY, ENCODE,
+        ("Failed to decode frame"), ("%s", gst_vpx_error_name (status)));
     return GST_FLOW_ERROR;
   }
 
index bb22dfe..f4d9e27 100644 (file)
@@ -38,6 +38,7 @@
 #include <vpx/vpx_encoder.h>
 #include <vpx/vp8cx.h>
 
+#include "gstvp8utils.h"
 
 GST_DEBUG_CATEGORY (gst_vp8enc_debug);
 #define GST_CAT_DEFAULT gst_vp8enc_debug
@@ -138,9 +139,6 @@ static gboolean gst_vp8_enc_sink_event (GstPad * pad, GstEvent * event);
 
 GType gst_vp8_enc_get_type (void);
 
-static const char *vpx_error_name (vpx_codec_err_t status);
-
-
 static GstStaticPadTemplate gst_vp8_enc_sink_template =
 GST_STATIC_PAD_TEMPLATE ("sink",
     GST_PAD_SINK,
@@ -250,14 +248,13 @@ gst_vp8_enc_class_init (GstVP8EncClass * klass)
           "Speed",
           0, 2, DEFAULT_SPEED,
           (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
-
 }
 
 static void
 gst_vp8_enc_init (GstVP8Enc * gst_vp8_enc, GstVP8EncClass * klass)
 {
 
-  GST_DEBUG ("init");
+  GST_DEBUG_OBJECT (gst_vp8_enc, "init");
 
   gst_vp8_enc->bitrate = DEFAULT_BITRATE;
   gst_vp8_enc->quality = DEFAULT_QUALITY;
@@ -277,7 +274,7 @@ gst_vp8_enc_finalize (GObject * object)
 {
   GstVP8Enc *gst_vp8_enc;
 
-  GST_DEBUG ("finalize");
+  GST_DEBUG_OBJECT (object, "finalize");
 
   g_return_if_fail (GST_IS_GST_VP8_ENC (object));
   gst_vp8_enc = GST_VP8_ENC (object);
@@ -295,7 +292,7 @@ gst_vp8_enc_set_property (GObject * object, guint prop_id,
   g_return_if_fail (GST_IS_GST_VP8_ENC (object));
   gst_vp8_enc = GST_VP8_ENC (object);
 
-  GST_DEBUG ("gst_vp8_enc_set_property");
+  GST_DEBUG_OBJECT (object, "gst_vp8_enc_set_property");
   switch (prop_id) {
     case PROP_BITRATE:
       gst_vp8_enc->bitrate = g_value_get_int (value);
@@ -359,7 +356,7 @@ gst_vp8_enc_start (GstBaseVideoEncoder * base_video_encoder)
 {
   GstVP8Enc *encoder;
 
-  GST_DEBUG ("start");
+  GST_DEBUG_OBJECT (base_video_encoder, "start");
 
   encoder = GST_VP8_ENC (base_video_encoder);
 
@@ -371,6 +368,8 @@ gst_vp8_enc_stop (GstBaseVideoEncoder * base_video_encoder)
 {
   GstVP8Enc *encoder;
 
+  GST_DEBUG_OBJECT (base_video_encoder, "stop");
+
   encoder = GST_VP8_ENC (base_video_encoder);
 
   if (encoder->inited) {
@@ -389,11 +388,10 @@ gst_vp8_enc_set_format (GstBaseVideoEncoder * base_video_encoder,
 {
   GstVP8Enc *encoder;
 
-  GST_DEBUG ("set_format");
+  GST_DEBUG_OBJECT (base_video_encoder, "set_format");
 
   encoder = GST_VP8_ENC (base_video_encoder);
 
-
   return TRUE;
 }
 
@@ -423,7 +421,6 @@ gst_vp8_enc_get_caps (GstBaseVideoEncoder * base_video_encoder)
       "pixel-aspect-ratio", GST_TYPE_FRACTION, state->par_n,
       state->par_d, NULL);
 
-
   /* Create Ogg stream-info */
   stream_hdr = gst_buffer_new_and_alloc (24);
   data = GST_BUFFER_DATA (stream_hdr);
@@ -479,11 +476,11 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
   GstVP8Enc *encoder;
   GstVideoFrame *frame;
   int flags = 0;
-  int status;
+  vpx_codec_err_t status;
   vpx_codec_iter_t iter = NULL;
   const vpx_codec_cx_pkt_t *pkt;
 
-  GST_DEBUG ("finish");
+  GST_DEBUG_OBJECT (base_video_encoder, "finish");
 
   encoder = GST_VP8_ENC (base_video_encoder);
 
@@ -491,7 +488,9 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
       vpx_codec_encode (&encoder->encoder, NULL, encoder->n_frames, 1, flags,
       0);
   if (status != 0) {
-    GST_ERROR ("encode returned %d %s", status, vpx_error_name (status));
+    GST_ERROR_OBJECT (encoder, "encode returned %d %s", status,
+        gst_vpx_error_name (status));
+    return FALSE;
   }
 
   pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
@@ -500,18 +499,19 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
     GstVP8EncCoderHook *hook;
     gboolean invisible, keyframe;
 
-    GST_DEBUG ("packet %d type %d", pkt->data.frame.sz, pkt->kind);
+    GST_DEBUG_OBJECT (encoder, "packet %d type %d", pkt->data.frame.sz,
+        pkt->kind);
 
     if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
-      GST_ERROR ("non frame pkt");
+      GST_ERROR_OBJECT (encoder, "non frame pkt");
       continue;
     }
 
     invisible = (pkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) != 0;
     keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
     frame = gst_base_video_encoder_get_oldest_frame (base_video_encoder);
+    g_assert (frame != NULL);
     hook = frame->coder_hook;
-    /* FIXME: If frame is NULL something went really wrong! */
 
     buffer = gst_buffer_new_and_alloc (pkt->data.frame.sz);
 
@@ -536,33 +536,6 @@ gst_vp8_enc_finish (GstBaseVideoEncoder * base_video_encoder)
   return TRUE;
 }
 
-static const char *
-vpx_error_name (vpx_codec_err_t status)
-{
-  switch (status) {
-    case VPX_CODEC_OK:
-      return "OK";
-    case VPX_CODEC_ERROR:
-      return "error";
-    case VPX_CODEC_MEM_ERROR:
-      return "mem error";
-    case VPX_CODEC_ABI_MISMATCH:
-      return "abi mismatch";
-    case VPX_CODEC_INCAPABLE:
-      return "incapable";
-    case VPX_CODEC_UNSUP_BITSTREAM:
-      return "unsupported bitstream";
-    case VPX_CODEC_UNSUP_FEATURE:
-      return "unsupported feature";
-    case VPX_CODEC_CORRUPT_FRAME:
-      return "corrupt frame";
-    case VPX_CODEC_INVALID_PARAM:
-      return "invalid parameter";
-    default:
-      return "unknown";
-  }
-}
-
 static vpx_image_t *
 gst_vp8_enc_buffer_to_image (GstVP8Enc * enc, GstBuffer * buffer)
 {
@@ -612,14 +585,14 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
   GstVP8Enc *encoder;
   const GstVideoState *state;
   guint8 *src;
-  long status;
+  vpx_codec_err_t status;
   int flags = 0;
   vpx_codec_iter_t iter = NULL;
   const vpx_codec_cx_pkt_t *pkt;
   vpx_image_t *image;
   GstVP8EncCoderHook *hook;
 
-  GST_DEBUG ("handle_frame");
+  GST_DEBUG_OBJECT (base_video_encoder, "handle_frame");
 
   encoder = GST_VP8_ENC (base_video_encoder);
   src = GST_BUFFER_DATA (frame->sink_buffer);
@@ -627,13 +600,19 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
   state = gst_base_video_encoder_get_state (base_video_encoder);
   encoder->n_frames++;
 
-  GST_DEBUG ("res id %d size %d %d", encoder->resolution_id,
-      state->width, state->height);
+  GST_DEBUG_OBJECT (base_video_encoder, "res id %d size %d %d",
+      encoder->resolution_id, state->width, state->height);
 
   if (!encoder->inited) {
     vpx_codec_enc_cfg_t cfg;
 
-    vpx_codec_enc_config_default (&vpx_codec_vp8_cx_algo, &cfg, 0);
+    status = vpx_codec_enc_config_default (&vpx_codec_vp8_cx_algo, &cfg, 0);
+    if (status != VPX_CODEC_OK) {
+      GST_ELEMENT_ERROR (encoder, LIBRARY, INIT,
+          ("Failed to get default encoder configuration"), ("%s",
+              gst_vpx_error_name (status)));
+      return FALSE;
+    }
 
     cfg.g_w = base_video_encoder->state.width;
     cfg.g_h = base_video_encoder->state.height;
@@ -664,7 +643,8 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
         &cfg, 0);
     if (status) {
       GST_ELEMENT_ERROR (encoder, LIBRARY, INIT,
-          ("Failed to initialize VP8 encoder"), (NULL));
+          ("Failed to initialize encoder"), ("%s",
+              gst_vpx_error_name (status)));
       return GST_FLOW_ERROR;
     }
 
@@ -688,7 +668,12 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
   status = vpx_codec_encode (&encoder->encoder, image,
       encoder->n_frames, 1, flags, speed_table[encoder->speed]);
   if (status != 0) {
-    GST_ERROR ("encode returned %d %s", status, vpx_error_name (status));
+    GST_ELEMENT_ERROR (encoder, LIBRARY, ENCODE,
+        ("Failed to encode frame"), ("%s", gst_vpx_error_name (status)));
+    g_slice_free (GstVP8EncCoderHook, hook);
+    frame->coder_hook = NULL;
+    g_slice_free (vpx_image_t, image);
+    return FALSE;
   }
 
   pkt = vpx_codec_get_cx_data (&encoder->encoder, &iter);
@@ -696,18 +681,19 @@ gst_vp8_enc_handle_frame (GstBaseVideoEncoder * base_video_encoder,
     GstBuffer *buffer;
     gboolean invisible;
 
-    GST_DEBUG ("packet %d type %d", pkt->data.frame.sz, pkt->kind);
+    GST_DEBUG_OBJECT (encoder, "packet %d type %d", pkt->data.frame.sz,
+        pkt->kind);
 
     if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) {
-      GST_ERROR ("non frame pkt");
+      GST_ERROR_OBJECT (encoder, "non frame pkt");
       continue;
     }
 
     invisible = (pkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) != 0;
     frame = gst_base_video_encoder_get_oldest_frame (base_video_encoder);
+    g_assert (frame != NULL);
     frame->is_sync_point = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0;
     hook = frame->coder_hook;
-    /* FIXME: If frame is NULL something went really wrong! */
 
     buffer = gst_buffer_new_and_alloc (pkt->data.frame.sz);
 
@@ -754,7 +740,7 @@ gst_vp8_enc_shape_output (GstBaseVideoEncoder * base_video_encoder,
   GList *l;
   gint inv_count;
 
-  GST_DEBUG ("shape_output");
+  GST_DEBUG_OBJECT (base_video_encoder, "shape_output");
 
   encoder = GST_VP8_ENC (base_video_encoder);
 
@@ -784,8 +770,10 @@ gst_vp8_enc_shape_output (GstBaseVideoEncoder * base_video_encoder,
     gst_buffer_set_caps (buf, base_video_encoder->caps);
     ret = gst_pad_push (GST_BASE_VIDEO_CODEC_SRC_PAD (base_video_encoder), buf);
 
-    if (ret != GST_FLOW_OK)
+    if (ret != GST_FLOW_OK) {
+      GST_WARNING_OBJECT (encoder, "flow error %d", ret);
       goto done;
+    }
   }
 
   buf = frame->src_buffer;
@@ -814,8 +802,9 @@ gst_vp8_enc_shape_output (GstBaseVideoEncoder * base_video_encoder,
   gst_buffer_set_caps (buf, base_video_encoder->caps);
 
   ret = gst_pad_push (GST_BASE_VIDEO_CODEC_SRC_PAD (base_video_encoder), buf);
-  if (ret != GST_FLOW_OK)
-    GST_ERROR ("flow error %d", ret);
+  if (ret != GST_FLOW_OK) {
+    GST_WARNING_OBJECT (encoder, "flow error %d", ret);
+  }
 
 done:
   g_list_foreach (hook->invisible, (GFunc) gst_mini_object_unref, NULL);
diff --git a/ext/vp8/gstvp8utils.c b/ext/vp8/gstvp8utils.c
new file mode 100644 (file)
index 0000000..e206780
--- /dev/null
@@ -0,0 +1,57 @@
+/* VP8
+ * Copyright (C) 2006 David Schleef <ds@schleef.org>
+ * Copyright (C) 2010 Entropy Wave Inc
+ * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/gst.h>
+#include <vpx/vpx_codec.h>
+
+#include "gstvp8utils.h"
+
+const char *
+gst_vpx_error_name (vpx_codec_err_t status)
+{
+  switch (status) {
+    case VPX_CODEC_OK:
+      return "OK";
+    case VPX_CODEC_ERROR:
+      return "error";
+    case VPX_CODEC_MEM_ERROR:
+      return "mem error";
+    case VPX_CODEC_ABI_MISMATCH:
+      return "abi mismatch";
+    case VPX_CODEC_INCAPABLE:
+      return "incapable";
+    case VPX_CODEC_UNSUP_BITSTREAM:
+      return "unsupported bitstream";
+    case VPX_CODEC_UNSUP_FEATURE:
+      return "unsupported feature";
+    case VPX_CODEC_CORRUPT_FRAME:
+      return "corrupt frame";
+    case VPX_CODEC_INVALID_PARAM:
+      return "invalid parameter";
+    default:
+      return "unknown";
+  }
+}
diff --git a/ext/vp8/gstvp8utils.h b/ext/vp8/gstvp8utils.h
new file mode 100644 (file)
index 0000000..4c07127
--- /dev/null
@@ -0,0 +1,30 @@
+/* VP8
+ * Copyright (C) 2006 David Schleef <ds@schleef.org>
+ * Copyright (C) 2010 Entropy Wave Inc
+ * Copyright (C) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <gst/gst.h>
+#include <vpx/vpx_codec.h>
+
+G_BEGIN_DECLS
+
+const char * gst_vpx_error_name (vpx_codec_err_t status);
+
+G_END_DECLS