gst_plugins_base/gst-libs/gst/rtp/gstbasertpdepayload.c
changeset 16 8e837d1bf446
parent 0 0e761a78d257
child 30 7e817e7e631c
equal deleted inserted replaced
15:4b0c6ed43234 16:8e837d1bf446
    54   gdouble play_scale;
    54   gdouble play_scale;
    55 
    55 
    56   gboolean discont;
    56   gboolean discont;
    57   GstClockTime timestamp;
    57   GstClockTime timestamp;
    58   GstClockTime duration;
    58   GstClockTime duration;
       
    59 
       
    60   guint32 next_seqnum;
       
    61 
       
    62   gboolean negotiated;
    59 };
    63 };
    60 
    64 
    61 /* Filter signals and args */
    65 /* Filter signals and args */
    62 enum
    66 enum
    63 {
    67 {
    68 #define DEFAULT_QUEUE_DELAY	0
    72 #define DEFAULT_QUEUE_DELAY	0
    69 
    73 
    70 enum
    74 enum
    71 {
    75 {
    72   PROP_0,
    76   PROP_0,
    73   PROP_QUEUE_DELAY
    77   PROP_QUEUE_DELAY,
       
    78   PROP_LAST
    74 };
    79 };
    75 
    80 
    76 static void gst_base_rtp_depayload_finalize (GObject * object);
    81 static void gst_base_rtp_depayload_finalize (GObject * object);
    77 static void gst_base_rtp_depayload_set_property (GObject * object,
    82 static void gst_base_rtp_depayload_set_property (GObject * object,
    78     guint prop_id, const GValue * value, GParamSpec * pspec);
    83     guint prop_id, const GValue * value, GParamSpec * pspec);
    88 static GstStateChangeReturn gst_base_rtp_depayload_change_state (GstElement *
    93 static GstStateChangeReturn gst_base_rtp_depayload_change_state (GstElement *
    89     element, GstStateChange transition);
    94     element, GstStateChange transition);
    90 
    95 
    91 static void gst_base_rtp_depayload_set_gst_timestamp
    96 static void gst_base_rtp_depayload_set_gst_timestamp
    92     (GstBaseRTPDepayload * filter, guint32 rtptime, GstBuffer * buf);
    97     (GstBaseRTPDepayload * filter, guint32 rtptime, GstBuffer * buf);
       
    98 static gboolean gst_base_rtp_depayload_packet_lost (GstBaseRTPDepayload *
       
    99     filter, GstEvent * event);
    93 
   100 
    94 GST_BOILERPLATE (GstBaseRTPDepayload, gst_base_rtp_depayload, GstElement,
   101 GST_BOILERPLATE (GstBaseRTPDepayload, gst_base_rtp_depayload, GstElement,
    95     GST_TYPE_ELEMENT);
   102     GST_TYPE_ELEMENT);
    96 
   103 
    97 static void
   104 static void
   126    */
   133    */
   127 #ifndef GST_REMOVE_DEPRECATED
   134 #ifndef GST_REMOVE_DEPRECATED
   128   g_object_class_install_property (gobject_class, PROP_QUEUE_DELAY,
   135   g_object_class_install_property (gobject_class, PROP_QUEUE_DELAY,
   129       g_param_spec_uint ("queue-delay", "Queue Delay",
   136       g_param_spec_uint ("queue-delay", "Queue Delay",
   130           "Amount of ms to queue/buffer, deprecated", 0, G_MAXUINT,
   137           "Amount of ms to queue/buffer, deprecated", 0, G_MAXUINT,
   131           DEFAULT_QUEUE_DELAY, G_PARAM_READWRITE));
   138           DEFAULT_QUEUE_DELAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
   132 #endif
   139 #endif
   133 
   140 
   134   gstelement_class->change_state = gst_base_rtp_depayload_change_state;
   141   gstelement_class->change_state = gst_base_rtp_depayload_change_state;
   135 
   142 
   136   klass->set_gst_timestamp = gst_base_rtp_depayload_set_gst_timestamp;
   143   klass->set_gst_timestamp = gst_base_rtp_depayload_set_gst_timestamp;
       
   144   klass->packet_lost = gst_base_rtp_depayload_packet_lost;
   137 
   145 
   138   GST_DEBUG_CATEGORY_INIT (basertpdepayload_debug, "basertpdepayload", 0,
   146   GST_DEBUG_CATEGORY_INIT (basertpdepayload_debug, "basertpdepayload", 0,
   139       "Base class for RTP Depayloaders");
   147       "Base class for RTP Depayloaders");
   140 }
   148 }
   141 
   149 
   235   if (bclass->set_caps)
   243   if (bclass->set_caps)
   236     res = bclass->set_caps (filter, caps);
   244     res = bclass->set_caps (filter, caps);
   237   else
   245   else
   238     res = TRUE;
   246     res = TRUE;
   239 
   247 
       
   248   priv->negotiated = res;
       
   249 
   240   gst_object_unref (filter);
   250   gst_object_unref (filter);
   241 
   251 
   242   return res;
   252   return res;
   243 }
   253 }
   244 
   254 
   249   GstBaseRTPDepayloadPrivate *priv;
   259   GstBaseRTPDepayloadPrivate *priv;
   250   GstBaseRTPDepayloadClass *bclass;
   260   GstBaseRTPDepayloadClass *bclass;
   251   GstFlowReturn ret = GST_FLOW_OK;
   261   GstFlowReturn ret = GST_FLOW_OK;
   252   GstBuffer *out_buf;
   262   GstBuffer *out_buf;
   253   GstClockTime timestamp;
   263   GstClockTime timestamp;
       
   264   guint16 seqnum;
       
   265   guint32 rtptime;
       
   266   gboolean reset_seq, discont;
       
   267   gint gap;
   254 
   268 
   255   filter = GST_BASE_RTP_DEPAYLOAD (GST_OBJECT_PARENT (pad));
   269   filter = GST_BASE_RTP_DEPAYLOAD (GST_OBJECT_PARENT (pad));
   256 
       
   257   priv = filter->priv;
   270   priv = filter->priv;
       
   271 
       
   272   /* we must have a setcaps first */
       
   273   if (G_UNLIKELY (!priv->negotiated))
       
   274     goto not_negotiated;
       
   275 
       
   276   /* we must validate, it's possible that this element is plugged right after a
       
   277    * network receiver and we don't want to operate on invalid data */
       
   278   if (G_UNLIKELY (!gst_rtp_buffer_validate (in)))
       
   279     goto invalid_buffer;
       
   280 
   258   priv->discont = GST_BUFFER_IS_DISCONT (in);
   281   priv->discont = GST_BUFFER_IS_DISCONT (in);
   259 
   282 
       
   283   timestamp = GST_BUFFER_TIMESTAMP (in);
   260   /* convert to running_time and save the timestamp, this is the timestamp
   284   /* convert to running_time and save the timestamp, this is the timestamp
   261    * we put on outgoing buffers. */
   285    * we put on outgoing buffers. */
   262   timestamp = GST_BUFFER_TIMESTAMP (in);
       
   263   timestamp = gst_segment_to_running_time (&filter->segment, GST_FORMAT_TIME,
   286   timestamp = gst_segment_to_running_time (&filter->segment, GST_FORMAT_TIME,
   264       timestamp);
   287       timestamp);
   265   priv->timestamp = timestamp;
   288   priv->timestamp = timestamp;
   266   priv->duration = GST_BUFFER_DURATION (in);
   289   priv->duration = GST_BUFFER_DURATION (in);
   267 
   290 
       
   291   seqnum = gst_rtp_buffer_get_seq (in);
       
   292   rtptime = gst_rtp_buffer_get_timestamp (in);
       
   293   reset_seq = TRUE;
       
   294   discont = FALSE;
       
   295 
       
   296   GST_LOG_OBJECT (filter, "discont %d, seqnum %u, rtptime %u, timestamp %"
       
   297       GST_TIME_FORMAT, priv->discont, seqnum, rtptime,
       
   298       GST_TIME_ARGS (timestamp));
       
   299 
       
   300   /* Check seqnum. This is a very simple check that makes sure that the seqnums
       
   301    * are striclty increasing, dropping anything that is out of the ordinary. We
       
   302    * can only do this when the next_seqnum is known. */
       
   303   if (G_LIKELY (priv->next_seqnum != -1)) {
       
   304     gap = gst_rtp_buffer_compare_seqnum (seqnum, priv->next_seqnum);
       
   305 
       
   306     /* if we have no gap, all is fine */
       
   307     if (G_UNLIKELY (gap != 0)) {
       
   308       GST_LOG_OBJECT (filter, "got packet %u, expected %u, gap %d", seqnum,
       
   309           priv->next_seqnum, gap);
       
   310       if (gap < 0) {
       
   311         /* seqnum > next_seqnum, we are missing some packets, this is always a
       
   312          * DISCONT. */
       
   313         GST_LOG_OBJECT (filter, "%d missing packets", gap);
       
   314         discont = TRUE;
       
   315       } else {
       
   316         /* seqnum < next_seqnum, we have seen this packet before or the sender
       
   317          * could be restarted. If the packet is not too old, we throw it away as
       
   318          * a duplicate, otherwise we mark discont and continue. 100 misordered
       
   319          * packets is a good threshold. See also RFC 4737. */
       
   320         if (gap < 100)
       
   321           goto dropping;
       
   322 
       
   323         GST_LOG_OBJECT (filter,
       
   324             "%d > 100, packet too old, sender likely restarted", gap);
       
   325         discont = TRUE;
       
   326       }
       
   327     }
       
   328   }
       
   329   priv->next_seqnum = (seqnum + 1) & 0xffff;
       
   330 
       
   331   if (G_UNLIKELY (discont && !priv->discont)) {
       
   332     GST_LOG_OBJECT (filter, "mark DISCONT on input buffer");
       
   333     /* we detected a seqnum discont but the buffer was not flagged with a discont,
       
   334      * set the discont flag so that the subclass can throw away old data. */
       
   335     priv->discont = TRUE;
       
   336     GST_BUFFER_FLAG_SET (in, GST_BUFFER_FLAG_DISCONT);
       
   337   }
       
   338 
   268   bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter);
   339   bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter);
       
   340 
       
   341   if (G_UNLIKELY (bclass->process == NULL))
       
   342     goto no_process;
   269 
   343 
   270   /* let's send it out to processing */
   344   /* let's send it out to processing */
   271   out_buf = bclass->process (filter, in);
   345   out_buf = bclass->process (filter, in);
   272   if (out_buf) {
   346   if (out_buf) {
   273     guint32 rtptime;
       
   274 
       
   275     rtptime = gst_rtp_buffer_get_timestamp (in);
       
   276 
       
   277     /* we pass rtptime as backward compatibility, in reality, the incomming
   347     /* we pass rtptime as backward compatibility, in reality, the incomming
   278      * buffer timestamp is always applied to the outgoing packet. */
   348      * buffer timestamp is always applied to the outgoing packet. */
   279     ret = gst_base_rtp_depayload_push_ts (filter, rtptime, out_buf);
   349     ret = gst_base_rtp_depayload_push_ts (filter, rtptime, out_buf);
   280   }
   350   }
   281   gst_buffer_unref (in);
   351   gst_buffer_unref (in);
   282 
   352 
   283   return ret;
   353   return ret;
       
   354 
       
   355   /* ERRORS */
       
   356 not_negotiated:
       
   357   {
       
   358     /* this is not fatal but should be filtered earlier */
       
   359     GST_ELEMENT_ERROR (filter, CORE, NEGOTIATION, (NULL),
       
   360         ("Not RTP format was negotiated"));
       
   361     gst_buffer_unref (in);
       
   362     return GST_FLOW_NOT_NEGOTIATED;
       
   363   }
       
   364 invalid_buffer:
       
   365   {
       
   366     /* this is not fatal but should be filtered earlier */
       
   367     GST_ELEMENT_WARNING (filter, STREAM, DECODE, (NULL),
       
   368         ("Received invalid RTP payload, dropping"));
       
   369     gst_buffer_unref (in);
       
   370     return GST_FLOW_OK;
       
   371   }
       
   372 dropping:
       
   373   {
       
   374     GST_WARNING_OBJECT (filter, "%d <= 100, dropping old packet", gap);
       
   375     gst_buffer_unref (in);
       
   376     return GST_FLOW_OK;
       
   377   }
       
   378 no_process:
       
   379   {
       
   380     /* this is not fatal but should be filtered earlier */
       
   381     GST_ELEMENT_ERROR (filter, STREAM, NOT_IMPLEMENTED, (NULL),
       
   382         ("The subclass does not have a process method"));
       
   383     gst_buffer_unref (in);
       
   384     return GST_FLOW_ERROR;
       
   385   }
   284 }
   386 }
   285 
   387 
   286 static gboolean
   388 static gboolean
   287 gst_base_rtp_depayload_handle_sink_event (GstPad * pad, GstEvent * event)
   389 gst_base_rtp_depayload_handle_sink_event (GstPad * pad, GstEvent * event)
   288 {
   390 {
   295     case GST_EVENT_FLUSH_STOP:
   397     case GST_EVENT_FLUSH_STOP:
   296       res = gst_pad_push_event (filter->srcpad, event);
   398       res = gst_pad_push_event (filter->srcpad, event);
   297 
   399 
   298       gst_segment_init (&filter->segment, GST_FORMAT_UNDEFINED);
   400       gst_segment_init (&filter->segment, GST_FORMAT_UNDEFINED);
   299       filter->need_newsegment = TRUE;
   401       filter->need_newsegment = TRUE;
       
   402       filter->priv->next_seqnum = -1;
   300       break;
   403       break;
   301     case GST_EVENT_NEWSEGMENT:
   404     case GST_EVENT_NEWSEGMENT:
   302     {
   405     {
   303       gboolean update;
   406       gboolean update;
   304       gdouble rate;
   407       gdouble rate;
   314       /* don't pass the event downstream, we generate our own segment including
   417       /* don't pass the event downstream, we generate our own segment including
   315        * the NTP time and other things we receive in caps */
   418        * the NTP time and other things we receive in caps */
   316       gst_event_unref (event);
   419       gst_event_unref (event);
   317       break;
   420       break;
   318     }
   421     }
       
   422     case GST_EVENT_CUSTOM_DOWNSTREAM:
       
   423     {
       
   424       GstBaseRTPDepayloadClass *bclass;
       
   425 
       
   426       bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter);
       
   427 
       
   428       if (gst_event_has_name (event, "GstRTPPacketLost")) {
       
   429         /* we get this event from the jitterbuffer when it considers a packet as
       
   430          * being lost. We send it to our packet_lost vmethod. The default
       
   431          * implementation will make time progress by pushing out a NEWSEGMENT
       
   432          * update event. Subclasses can override and to one of the following:
       
   433          *  - Adjust timestamp/duration to something more accurate before
       
   434          *    calling the parent (default) packet_lost method.
       
   435          *  - do some more advanced error concealing on the already received
       
   436          *    (fragmented) packets.
       
   437          *  - ignore the packet lost.
       
   438          */
       
   439         if (bclass->packet_lost)
       
   440           res = bclass->packet_lost (filter, event);
       
   441       }
       
   442       gst_event_unref (event);
       
   443       break;
       
   444     }
   319     default:
   445     default:
   320       /* pass other events forward */
   446       /* pass other events forward */
   321       res = gst_pad_push_event (filter->srcpad, event);
   447       res = gst_pad_push_event (filter->srcpad, event);
   322       break;
   448       break;
   323   }
   449   }
   324   return res;
   450   return res;
       
   451 }
       
   452 
       
   453 static GstEvent *
       
   454 create_segment_event (GstBaseRTPDepayload * filter, gboolean update,
       
   455     GstClockTime position)
       
   456 {
       
   457   GstEvent *event;
       
   458   GstClockTime stop;
       
   459   GstBaseRTPDepayloadPrivate *priv;
       
   460 
       
   461   priv = filter->priv;
       
   462 
       
   463   if (priv->npt_stop != -1)
       
   464     stop = priv->npt_stop - priv->npt_start;
       
   465   else
       
   466     stop = -1;
       
   467 
       
   468   event = gst_event_new_new_segment_full (update, priv->play_speed,
       
   469       priv->play_scale, GST_FORMAT_TIME, position, stop,
       
   470       position + priv->npt_start);
       
   471 
       
   472   return event;
   325 }
   473 }
   326 
   474 
   327 static GstFlowReturn
   475 static GstFlowReturn
   328 gst_base_rtp_depayload_push_full (GstBaseRTPDepayload * filter,
   476 gst_base_rtp_depayload_push_full (GstBaseRTPDepayload * filter,
   329     gboolean do_ts, guint32 rtptime, GstBuffer * out_buf)
   477     gboolean do_ts, guint32 rtptime, GstBuffer * out_buf)
   335 
   483 
   336   priv = filter->priv;
   484   priv = filter->priv;
   337 
   485 
   338   /* set the caps if any */
   486   /* set the caps if any */
   339   srccaps = GST_PAD_CAPS (filter->srcpad);
   487   srccaps = GST_PAD_CAPS (filter->srcpad);
   340   if (srccaps)
   488   if (G_LIKELY (srccaps))
   341     gst_buffer_set_caps (out_buf, srccaps);
   489     gst_buffer_set_caps (out_buf, srccaps);
   342 
   490 
   343   bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter);
   491   bclass = GST_BASE_RTP_DEPAYLOAD_GET_CLASS (filter);
   344 
   492 
   345   /* set the timestamp if we must and can */
   493   /* set the timestamp if we must and can */
   346   if (bclass->set_gst_timestamp && do_ts)
   494   if (bclass->set_gst_timestamp && do_ts)
   347     bclass->set_gst_timestamp (filter, rtptime, out_buf);
   495     bclass->set_gst_timestamp (filter, rtptime, out_buf);
   348 
   496 
   349   if (priv->discont) {
   497   /* if this is the first buffer send a NEWSEGMENT */
       
   498   if (G_UNLIKELY (filter->need_newsegment)) {
       
   499     GstEvent *event;
       
   500 
       
   501     event = create_segment_event (filter, FALSE, 0);
       
   502 
       
   503     gst_pad_push_event (filter->srcpad, event);
       
   504 
       
   505     filter->need_newsegment = FALSE;
       
   506     GST_DEBUG_OBJECT (filter, "Pushed newsegment event on this first buffer");
       
   507   }
       
   508 
       
   509   if (G_UNLIKELY (priv->discont)) {
       
   510     GST_LOG_OBJECT (filter, "Marking DISCONT on output buffer");
   350     GST_BUFFER_FLAG_SET (out_buf, GST_BUFFER_FLAG_DISCONT);
   511     GST_BUFFER_FLAG_SET (out_buf, GST_BUFFER_FLAG_DISCONT);
   351     priv->discont = FALSE;
   512     priv->discont = FALSE;
   352   }
   513   }
   353 
   514 
   354   /* push it */
   515   /* push it */
   355   GST_LOG_OBJECT (filter, "Pushing buffer size %d, timestamp %" GST_TIME_FORMAT,
   516   GST_LOG_OBJECT (filter, "Pushing buffer size %d, timestamp %" GST_TIME_FORMAT,
   356       GST_BUFFER_SIZE (out_buf),
   517       GST_BUFFER_SIZE (out_buf),
   357       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)));
   518       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (out_buf)));
       
   519 
   358   ret = gst_pad_push (filter->srcpad, out_buf);
   520   ret = gst_pad_push (filter->srcpad, out_buf);
   359   GST_LOG_OBJECT (filter, "Pushed buffer: %s", gst_flow_get_name (ret));
       
   360 
   521 
   361   return ret;
   522   return ret;
   362 }
   523 }
   363 
   524 
   364 /**
   525 /**
   406 
   567 
   407 GstFlowReturn
   568 GstFlowReturn
   408 gst_base_rtp_depayload_push (GstBaseRTPDepayload * filter, GstBuffer * out_buf)
   569 gst_base_rtp_depayload_push (GstBaseRTPDepayload * filter, GstBuffer * out_buf)
   409 {
   570 {
   410   return gst_base_rtp_depayload_push_full (filter, FALSE, 0, out_buf);
   571   return gst_base_rtp_depayload_push_full (filter, FALSE, 0, out_buf);
       
   572 }
       
   573 
       
   574 /* convert the PacketLost event form a jitterbuffer to a segment update.
       
   575  * subclasses can override this.  */
       
   576 static gboolean
       
   577 gst_base_rtp_depayload_packet_lost (GstBaseRTPDepayload * filter,
       
   578     GstEvent * event)
       
   579 {
       
   580   GstBaseRTPDepayloadPrivate *priv;
       
   581   GstClockTime timestamp, duration, position;
       
   582   GstEvent *sevent;
       
   583   const GstStructure *s;
       
   584 
       
   585   priv = filter->priv;
       
   586 
       
   587   s = gst_event_get_structure (event);
       
   588 
       
   589   /* first start by parsing the timestamp and duration */
       
   590   timestamp = -1;
       
   591   duration = -1;
       
   592 
       
   593   gst_structure_get_clock_time (s, "timestamp", &timestamp);
       
   594   gst_structure_get_clock_time (s, "duration", &duration);
       
   595 
       
   596   position = timestamp;
       
   597   if (duration != -1)
       
   598     position += duration;
       
   599 
       
   600   /* update the current segment with the elapsed time */
       
   601   sevent = create_segment_event (filter, TRUE, position);
       
   602 
       
   603   return gst_pad_push_event (filter->srcpad, sevent);
   411 }
   604 }
   412 
   605 
   413 static void
   606 static void
   414 gst_base_rtp_depayload_set_gst_timestamp (GstBaseRTPDepayload * filter,
   607 gst_base_rtp_depayload_set_gst_timestamp (GstBaseRTPDepayload * filter,
   415     guint32 rtptime, GstBuffer * buf)
   608     guint32 rtptime, GstBuffer * buf)
   426    * not otherwise set. */
   619    * not otherwise set. */
   427   if (!GST_CLOCK_TIME_IS_VALID (timestamp))
   620   if (!GST_CLOCK_TIME_IS_VALID (timestamp))
   428     GST_BUFFER_TIMESTAMP (buf) = priv->timestamp;
   621     GST_BUFFER_TIMESTAMP (buf) = priv->timestamp;
   429   if (!GST_CLOCK_TIME_IS_VALID (duration))
   622   if (!GST_CLOCK_TIME_IS_VALID (duration))
   430     GST_BUFFER_DURATION (buf) = priv->duration;
   623     GST_BUFFER_DURATION (buf) = priv->duration;
   431 
       
   432   /* if this is the first buffer send a NEWSEGMENT */
       
   433   if (filter->need_newsegment) {
       
   434     GstEvent *event;
       
   435     GstClockTime stop, position;
       
   436 
       
   437     if (priv->npt_stop != -1)
       
   438       stop = priv->npt_stop - priv->npt_start;
       
   439     else
       
   440       stop = -1;
       
   441 
       
   442     position = priv->npt_start;
       
   443 
       
   444     event =
       
   445         gst_event_new_new_segment_full (FALSE, priv->play_speed,
       
   446         priv->play_scale, GST_FORMAT_TIME, 0, stop, position);
       
   447 
       
   448     gst_pad_push_event (filter->srcpad, event);
       
   449 
       
   450     filter->need_newsegment = FALSE;
       
   451     GST_DEBUG_OBJECT (filter, "Pushed newsegment event on this first buffer");
       
   452   }
       
   453 }
   624 }
   454 
   625 
   455 static GstStateChangeReturn
   626 static GstStateChangeReturn
   456 gst_base_rtp_depayload_change_state (GstElement * element,
   627 gst_base_rtp_depayload_change_state (GstElement * element,
   457     GstStateChange transition)
   628     GstStateChange transition)
   470       filter->need_newsegment = TRUE;
   641       filter->need_newsegment = TRUE;
   471       priv->npt_start = 0;
   642       priv->npt_start = 0;
   472       priv->npt_stop = -1;
   643       priv->npt_stop = -1;
   473       priv->play_speed = 1.0;
   644       priv->play_speed = 1.0;
   474       priv->play_scale = 1.0;
   645       priv->play_scale = 1.0;
       
   646       priv->next_seqnum = -1;
       
   647       priv->negotiated = FALSE;
   475       break;
   648       break;
   476     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
   649     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
   477       break;
   650       break;
   478     default:
   651     default:
   479       break;
   652       break;