rtpmanager: add new rtprtxsend / rtprtxreceive elements
[platform/upstream/gst-plugins-good.git] / gst / rtpmanager / gstrtprtxsend.c
1 /* RTP Retransmission sender element for GStreamer
2  *
3  * gstrtprtxsend.c:
4  *
5  * Copyright (C) 2013 Collabora Ltd.
6  *   @author Julien Isorce <julien.isorce@collabora.co.uk>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:element-rtprtxsend
26  *
27  * See #GstRtpRtxReceive for examples
28  * 
29  * The purpose of the sender RTX object is to keep a history of RTP packets up
30  * to a configurable limit (max-size-time or max-size-packets). It will listen
31  * for upstream custom retransmission events (GstRTPRetransmissionRequest) that
32  * comes from downstream (#GstRtpSession). When receiving a request it will
33  * look up the requested seqnum in its list of stored packets. If the packet
34  * is available, it will create a RTX packet according to RFC 4588 and send
35  * this as an auxiliary stream. RTX is SSRC-multiplexed
36  */
37
38 #ifdef HAVE_CONFIG_H
39 #include "config.h"
40 #endif
41
42 #include <gst/gst.h>
43 #include <gst/rtp/gstrtpbuffer.h>
44 #include <string.h>
45
46 #include "gstrtprtxsend.h"
47
48 GST_DEBUG_CATEGORY_STATIC (gst_rtp_rtx_send_debug);
49 #define GST_CAT_DEFAULT gst_rtp_rtx_send_debug
50
51 #define DEFAULT_RTX_PAYLOAD_TYPE 0
52 #define DEFAULT_MAX_SIZE_TIME    0
53 #define DEFAULT_MAX_SIZE_PACKETS 100
54
55 enum
56 {
57   PROP_0,
58   PROP_RTX_PAYLOAD_TYPE,
59   PROP_MAX_SIZE_TIME,
60   PROP_MAX_SIZE_PACKETS,
61   PROP_NUM_RTX_REQUESTS,
62   PROP_NUM_RTX_PACKETS,
63   PROP_LAST
64 };
65
66 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
67     GST_PAD_SRC,
68     GST_PAD_ALWAYS,
69     GST_STATIC_CAPS ("application/x-rtp")
70     );
71
72 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
73     GST_PAD_SINK,
74     GST_PAD_ALWAYS,
75     GST_STATIC_CAPS ("application/x-rtp")
76     );
77
78 static gboolean gst_rtp_rtx_send_src_event (GstPad * pad, GstObject * parent,
79     GstEvent * event);
80 static GstFlowReturn gst_rtp_rtx_send_chain (GstPad * pad, GstObject * parent,
81     GstBuffer * buffer);
82
83 static GstStateChangeReturn gst_rtp_rtx_send_change_state (GstElement *
84     element, GstStateChange transition);
85
86 static void gst_rtp_rtx_send_set_property (GObject * object, guint prop_id,
87     const GValue * value, GParamSpec * pspec);
88 static void gst_rtp_rtx_send_get_property (GObject * object, guint prop_id,
89     GValue * value, GParamSpec * pspec);
90 static void gst_rtp_rtx_send_finalize (GObject * object);
91
92 G_DEFINE_TYPE (GstRtpRtxSend, gst_rtp_rtx_send, GST_TYPE_ELEMENT);
93
94 static void
95 gst_rtp_rtx_send_class_init (GstRtpRtxSendClass * klass)
96 {
97   GObjectClass *gobject_class;
98   GstElementClass *gstelement_class;
99
100   gobject_class = (GObjectClass *) klass;
101   gstelement_class = (GstElementClass *) klass;
102
103   gobject_class->get_property = gst_rtp_rtx_send_get_property;
104   gobject_class->set_property = gst_rtp_rtx_send_set_property;
105   gobject_class->finalize = gst_rtp_rtx_send_finalize;
106
107   g_object_class_install_property (gobject_class, PROP_RTX_PAYLOAD_TYPE,
108       g_param_spec_uint ("rtx-payload-type", "RTX Payload Type",
109           "Payload type of the retransmission stream (fmtp in SDP)", 0,
110           G_MAXUINT, DEFAULT_RTX_PAYLOAD_TYPE,
111           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
112
113   g_object_class_install_property (gobject_class, PROP_MAX_SIZE_TIME,
114       g_param_spec_uint ("max-size-time", "Max Size Times",
115           "Amount of ms to queue (0 = unlimited)", 0, G_MAXUINT,
116           DEFAULT_MAX_SIZE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
117
118   g_object_class_install_property (gobject_class, PROP_MAX_SIZE_PACKETS,
119       g_param_spec_uint ("max-size-packets", "Max Size Packets",
120           "Amount of packets to queue (0 = unlimited)", 0, G_MAXUINT,
121           DEFAULT_MAX_SIZE_PACKETS,
122           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
123
124   g_object_class_install_property (gobject_class, PROP_NUM_RTX_REQUESTS,
125       g_param_spec_uint ("num-rtx-requests", "Num RTX Requests",
126           "Number of retransmission events received", 0, G_MAXUINT,
127           0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
128
129   g_object_class_install_property (gobject_class, PROP_NUM_RTX_PACKETS,
130       g_param_spec_uint ("num-rtx-packets", "Num RTX Packets",
131           " Number of retransmission packets sent", 0, G_MAXUINT,
132           0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
133
134   gst_element_class_add_pad_template (gstelement_class,
135       gst_static_pad_template_get (&src_factory));
136   gst_element_class_add_pad_template (gstelement_class,
137       gst_static_pad_template_get (&sink_factory));
138
139   gst_element_class_set_static_metadata (gstelement_class,
140       "RTP Retransmission Sender", "Codec",
141       "Retransmit RTP packets when needed, according to RFC4588",
142       "Julien Isorce <julien.isorce@collabora.co.uk>");
143
144   gstelement_class->change_state =
145       GST_DEBUG_FUNCPTR (gst_rtp_rtx_send_change_state);
146 }
147
148 static void
149 gst_rtp_rtx_send_reset (GstRtpRtxSend * rtx, gboolean full)
150 {
151   g_mutex_lock (&rtx->lock);
152   g_queue_foreach (rtx->queue, (GFunc) gst_buffer_unref, NULL);
153   g_queue_clear (rtx->queue);
154   g_list_foreach (rtx->pending, (GFunc) gst_buffer_unref, NULL);
155   g_list_free (rtx->pending);
156   rtx->pending = NULL;
157   rtx->master_ssrc = 0;
158   rtx->next_seqnum = g_random_int_range (0, G_MAXUINT16);
159   rtx->rtx_ssrc = g_random_int ();
160   rtx->num_rtx_requests = 0;
161   rtx->num_rtx_packets = 0;
162   g_mutex_unlock (&rtx->lock);
163 }
164
165 static void
166 gst_rtp_rtx_send_finalize (GObject * object)
167 {
168   GstRtpRtxSend *rtx = GST_RTP_RTX_SEND (object);
169
170   gst_rtp_rtx_send_reset (rtx, TRUE);
171   g_queue_free (rtx->queue);
172   g_mutex_clear (&rtx->lock);
173
174   G_OBJECT_CLASS (gst_rtp_rtx_send_parent_class)->finalize (object);
175 }
176
177 static void
178 gst_rtp_rtx_send_init (GstRtpRtxSend * rtx)
179 {
180   GstElementClass *klass = GST_ELEMENT_GET_CLASS (rtx);
181
182   rtx->srcpad =
183       gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
184           "src"), "src");
185   GST_PAD_SET_PROXY_CAPS (rtx->srcpad);
186   GST_PAD_SET_PROXY_ALLOCATION (rtx->srcpad);
187   gst_pad_set_event_function (rtx->srcpad,
188       GST_DEBUG_FUNCPTR (gst_rtp_rtx_send_src_event));
189   gst_element_add_pad (GST_ELEMENT (rtx), rtx->srcpad);
190
191   rtx->sinkpad =
192       gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
193           "sink"), "sink");
194   GST_PAD_SET_PROXY_CAPS (rtx->sinkpad);
195   GST_PAD_SET_PROXY_ALLOCATION (rtx->sinkpad);
196   gst_pad_set_chain_function (rtx->sinkpad,
197       GST_DEBUG_FUNCPTR (gst_rtp_rtx_send_chain));
198   gst_element_add_pad (GST_ELEMENT (rtx), rtx->sinkpad);
199
200   rtx->queue = g_queue_new ();
201   rtx->pending = NULL;
202   g_mutex_init (&rtx->lock);
203
204   rtx->next_seqnum = g_random_int_range (0, G_MAXUINT16);
205   rtx->rtx_ssrc = g_random_int ();
206
207   rtx->max_size_time = DEFAULT_MAX_SIZE_TIME;
208   rtx->max_size_packets = DEFAULT_MAX_SIZE_PACKETS;
209 }
210
211 static guint32
212 choose_ssrc (GstRtpRtxSend * rtx)
213 {
214   guint32 ssrc;
215
216   while (TRUE) {
217     ssrc = g_random_int ();
218
219     /* make sure to be different than master */
220     if (ssrc != rtx->master_ssrc)
221       break;
222   }
223   return ssrc;
224 }
225
226 typedef struct
227 {
228   GstRtpRtxSend *rtx;
229   guint seqnum;
230   gboolean found;
231 } RTXData;
232
233 /* traverse queue history and try to find the buffer that the
234  * requested seqnum */
235 static void
236 push_seqnum (GstBuffer * buffer, RTXData * data)
237 {
238   GstRtpRtxSend *rtx = data->rtx;
239   GstRTPBuffer rtpbuffer = GST_RTP_BUFFER_INIT;
240   guint16 seqnum;
241
242   if (data->found)
243     return;
244
245   if (!gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtpbuffer))
246     return;
247
248   seqnum = gst_rtp_buffer_get_seq (&rtpbuffer);
249   gst_rtp_buffer_unmap (&rtpbuffer);
250
251   /* data->seqnum comes from the request */
252   if (seqnum == data->seqnum) {
253     data->found = TRUE;
254     GST_DEBUG_OBJECT (rtx, "found %" G_GUINT16_FORMAT, seqnum);
255     rtx->pending = g_list_prepend (rtx->pending, gst_buffer_ref (buffer));
256   }
257 }
258
259 static gboolean
260 gst_rtp_rtx_send_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
261 {
262   GstRtpRtxSend *rtx = GST_RTP_RTX_SEND (parent);
263   gboolean res;
264
265   switch (GST_EVENT_TYPE (event)) {
266     case GST_EVENT_CUSTOM_UPSTREAM:
267     {
268       const GstStructure *s = gst_event_get_structure (event);
269
270       /* This event usually comes from the downstream gstrtpsession */
271       if (gst_structure_has_name (s, "GstRTPRetransmissionRequest")) {
272         guint32 seqnum = 0;
273         guint ssrc = 0;
274         RTXData data;
275
276         /* retrieve seqnum of the packet that need to be restransmisted */
277         if (!gst_structure_get_uint (s, "seqnum", &seqnum))
278           seqnum = -1;
279
280         /* retrieve ssrc of the packet that need to be restransmisted */
281         if (!gst_structure_get_uint (s, "ssrc", &ssrc))
282           ssrc = -1;
283
284         GST_DEBUG_OBJECT (rtx,
285             "request seqnum: %" G_GUINT16_FORMAT ", ssrc: %" G_GUINT32_FORMAT,
286             seqnum, ssrc);
287
288         g_mutex_lock (&rtx->lock);
289         /* check if request is for us */
290         if (rtx->master_ssrc == ssrc) {
291           ++rtx->num_rtx_requests;
292           data.rtx = rtx;
293           data.seqnum = seqnum;
294           data.found = FALSE;
295           /* TODO do a binary search because rtx->queue is sorted by seq num */
296           g_queue_foreach (rtx->queue, (GFunc) push_seqnum, &data);
297         }
298         g_mutex_unlock (&rtx->lock);
299
300         gst_event_unref (event);
301         res = TRUE;
302
303         /* This event usually comes from the downstream gstrtpsession */
304       } else if (gst_structure_has_name (s, "GstRTPCollision")) {
305         guint ssrc = 0;
306
307         if (!gst_structure_get_uint (s, "ssrc", &ssrc))
308           ssrc = -1;
309
310         GST_DEBUG_OBJECT (rtx, "collision ssrc: %" G_GUINT32_FORMAT, ssrc);
311
312         g_mutex_lock (&rtx->lock);
313
314         /* choose another ssrc for our retransmited stream */
315         if (ssrc == rtx->rtx_ssrc) {
316           rtx->rtx_ssrc = choose_ssrc (rtx);
317
318           /* clear buffers we already saved */
319           g_queue_foreach (rtx->queue, (GFunc) gst_buffer_unref, NULL);
320           g_queue_clear (rtx->queue);
321
322           /* clear buffers that are about to be retransmited */
323           g_list_foreach (rtx->pending, (GFunc) gst_buffer_unref, NULL);
324           g_list_free (rtx->pending);
325           rtx->pending = NULL;
326
327           g_mutex_unlock (&rtx->lock);
328
329           /* no need to forward to payloader because we make sure to have
330            * a different ssrc
331            */
332           gst_event_unref (event);
333           res = TRUE;
334         } else {
335           g_mutex_unlock (&rtx->lock);
336
337           /* forward event to payloader in case collided ssrc is
338            * master stream */
339           res = gst_pad_event_default (pad, parent, event);
340         }
341       } else {
342         res = gst_pad_event_default (pad, parent, event);
343       }
344       break;
345     }
346     default:
347       res = gst_pad_event_default (pad, parent, event);
348       break;
349   }
350   return res;
351 }
352
353 /* Copy fixed header and extension. Add OSN before to copy payload
354  * Copy memory to avoid to manually copy each rtp buffer field.
355  */
356 static GstBuffer *
357 _gst_rtp_rtx_buffer_new (GstBuffer * buffer, guint32 ssrc, guint16 seqnum,
358     guint8 fmtp)
359 {
360   GstMemory *mem = NULL;
361   GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
362   GstRTPBuffer new_rtp = GST_RTP_BUFFER_INIT;
363   GstBuffer *new_buffer = gst_buffer_new ();
364   GstMapInfo map;
365   guint payload_len = 0;
366
367   gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp);
368
369   /* gst_rtp_buffer_map does not map the payload so do it now */
370   gst_rtp_buffer_get_payload (&rtp);
371
372   /* If payload type is not set through SDP/property then
373    * just bump the value */
374   if (fmtp < 96)
375     fmtp = gst_rtp_buffer_get_payload_type (&rtp) + 1;
376
377   /* copy fixed header */
378   mem = gst_memory_copy (rtp.map[0].memory, 0, rtp.size[0]);
379   gst_buffer_append_memory (new_buffer, mem);
380
381   /* copy extension if any */
382   if (rtp.size[1]) {
383     mem = gst_memory_copy (rtp.map[1].memory, 0, rtp.size[1]);
384     gst_buffer_append_memory (new_buffer, mem);
385   }
386
387   /* copy payload and add OSN just before */
388   payload_len = 2 + rtp.size[2];
389   mem = gst_allocator_alloc (NULL, payload_len, NULL);
390
391   gst_memory_map (mem, &map, GST_MAP_WRITE);
392   GST_WRITE_UINT16_BE (map.data, gst_rtp_buffer_get_seq (&rtp));
393   if (rtp.size[2])
394     memcpy (map.data + 2, rtp.data[2], rtp.size[2]);
395   gst_memory_unmap (mem, &map);
396   gst_buffer_append_memory (new_buffer, mem);
397
398   /* everything needed is copied */
399   gst_rtp_buffer_unmap (&rtp);
400
401   /* set ssrc, seqnum and fmtp */
402   gst_rtp_buffer_map (new_buffer, GST_MAP_WRITE, &new_rtp);
403   gst_rtp_buffer_set_ssrc (&new_rtp, ssrc);
404   gst_rtp_buffer_set_seq (&new_rtp, seqnum);
405   gst_rtp_buffer_set_payload_type (&new_rtp, fmtp);
406   /* RFC 4588: let other elements do the padding, as normal */
407   gst_rtp_buffer_set_padding (&new_rtp, FALSE);
408   gst_rtp_buffer_unmap (&new_rtp);
409
410   return new_buffer;
411 }
412
413 /* psuh pending retransmission packet.
414  * it constructs rtx packet from original paclets */
415 static void
416 do_push (GstBuffer * buffer, GstRtpRtxSend * rtx)
417 {
418   /* RFC4588 two streams multiplexed by sending them in the same session using
419    * different SSRC values, i.e., SSRC-multiplexing.  */
420   GST_DEBUG_OBJECT (rtx,
421       "retransmit seqnum: %" G_GUINT16_FORMAT ", ssrc: %" G_GUINT32_FORMAT,
422       rtx->next_seqnum, rtx->rtx_ssrc);
423   gst_pad_push (rtx->srcpad, _gst_rtp_rtx_buffer_new (buffer, rtx->rtx_ssrc,
424           rtx->next_seqnum++, rtx->rtx_payload_type));
425 }
426
427 static GstFlowReturn
428 gst_rtp_rtx_send_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
429 {
430   GstRtpRtxSend *rtx = GST_RTP_RTX_SEND (parent);
431   GstFlowReturn ret = GST_FLOW_ERROR;
432   GList *pending = NULL;
433   GstRTPBuffer rtp = GST_RTP_BUFFER_INIT;
434   guint seqnum = 0;
435
436   g_mutex_lock (&rtx->lock);
437
438   /* retrievemaster stream ssrc */
439   gst_rtp_buffer_map (buffer, GST_MAP_READ, &rtp);
440   rtx->master_ssrc = gst_rtp_buffer_get_ssrc (&rtp);
441   seqnum = gst_rtp_buffer_get_seq (&rtp);
442   gst_rtp_buffer_unmap (&rtp);
443
444   /* check if our initial aux ssrc is equal to master */
445   if (rtx->rtx_ssrc == rtx->master_ssrc)
446     choose_ssrc (rtx);
447
448   /* add current rtp buffer to queue history */
449   g_queue_push_head (rtx->queue, gst_buffer_ref (buffer));
450
451   /* remove oldest packets from history if they are too many */
452   if (rtx->max_size_packets) {
453     while (g_queue_get_length (rtx->queue) > rtx->max_size_packets)
454       gst_buffer_unref (g_queue_pop_tail (rtx->queue));
455   }
456
457   /* within lock, get packets that have to be retransmited */
458   pending = rtx->pending;
459   rtx->pending = NULL;
460
461   /* assume we will succeed to retransmit those packets */
462   rtx->num_rtx_packets += g_list_length (pending);
463
464   /* transfer payload type while holding the lock */
465   rtx->rtx_payload_type = rtx->rtx_payload_type_pending;
466
467   g_mutex_unlock (&rtx->lock);
468
469   /* no need to hold the lock to push rtx packets */
470   g_list_foreach (pending, (GFunc) do_push, rtx);
471   g_list_foreach (pending, (GFunc) gst_buffer_unref, NULL);
472   g_list_free (pending);
473
474   GST_LOG_OBJECT (rtx,
475       "push seqnum: %" G_GUINT16_FORMAT ", ssrc: %" G_GUINT32_FORMAT, seqnum,
476       rtx->master_ssrc);
477
478   /* push current rtp packet */
479   ret = gst_pad_push (rtx->srcpad, buffer);
480
481   return ret;
482 }
483
484 static void
485 gst_rtp_rtx_send_get_property (GObject * object,
486     guint prop_id, GValue * value, GParamSpec * pspec)
487 {
488   GstRtpRtxSend *rtx = GST_RTP_RTX_SEND (object);
489
490   switch (prop_id) {
491     case PROP_RTX_PAYLOAD_TYPE:
492       g_mutex_lock (&rtx->lock);
493       g_value_set_uint (value, rtx->rtx_payload_type_pending);
494       g_mutex_unlock (&rtx->lock);
495       break;
496     case PROP_MAX_SIZE_TIME:
497       g_mutex_lock (&rtx->lock);
498       g_value_set_uint (value, rtx->max_size_time);
499       g_mutex_unlock (&rtx->lock);
500       break;
501     case PROP_MAX_SIZE_PACKETS:
502       g_mutex_lock (&rtx->lock);
503       g_value_set_uint (value, rtx->max_size_packets);
504       g_mutex_unlock (&rtx->lock);
505       break;
506     case PROP_NUM_RTX_REQUESTS:
507       g_mutex_lock (&rtx->lock);
508       g_value_set_uint (value, rtx->num_rtx_requests);
509       g_mutex_unlock (&rtx->lock);
510       break;
511     case PROP_NUM_RTX_PACKETS:
512       g_mutex_lock (&rtx->lock);
513       g_value_set_uint (value, rtx->num_rtx_packets);
514       g_mutex_unlock (&rtx->lock);
515       break;
516     default:
517       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
518       break;
519   }
520 }
521
522 static void
523 gst_rtp_rtx_send_set_property (GObject * object,
524     guint prop_id, const GValue * value, GParamSpec * pspec)
525 {
526   GstRtpRtxSend *rtx = GST_RTP_RTX_SEND (object);
527
528   switch (prop_id) {
529     case PROP_RTX_PAYLOAD_TYPE:
530       g_mutex_lock (&rtx->lock);
531       rtx->rtx_payload_type_pending = g_value_get_uint (value);
532       g_mutex_unlock (&rtx->lock);
533       break;
534     case PROP_MAX_SIZE_TIME:
535       g_mutex_lock (&rtx->lock);
536       rtx->max_size_time = g_value_get_uint (value);
537       g_mutex_unlock (&rtx->lock);
538       break;
539     case PROP_MAX_SIZE_PACKETS:
540       g_mutex_lock (&rtx->lock);
541       rtx->max_size_packets = g_value_get_uint (value);
542       g_mutex_unlock (&rtx->lock);
543       break;
544     default:
545       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
546       break;
547   }
548 }
549
550 static GstStateChangeReturn
551 gst_rtp_rtx_send_change_state (GstElement * element, GstStateChange transition)
552 {
553   GstStateChangeReturn ret;
554   GstRtpRtxSend *rtx;
555
556   rtx = GST_RTP_RTX_SEND (element);
557
558   switch (transition) {
559     default:
560       break;
561   }
562
563   ret =
564       GST_ELEMENT_CLASS (gst_rtp_rtx_send_parent_class)->change_state (element,
565       transition);
566
567   switch (transition) {
568     case GST_STATE_CHANGE_PAUSED_TO_READY:
569       gst_rtp_rtx_send_reset (rtx, TRUE);
570       break;
571     default:
572       break;
573   }
574
575   return ret;
576 }
577
578 gboolean
579 gst_rtp_rtx_send_plugin_init (GstPlugin * plugin)
580 {
581   GST_DEBUG_CATEGORY_INIT (gst_rtp_rtx_send_debug, "rtprtxsend", 0,
582       "rtp retransmission sender");
583
584   return gst_element_register (plugin, "rtprtxsend", GST_RANK_NONE,
585       GST_TYPE_RTP_RTX_SEND);
586 }