|
1 /* GStreamer |
|
2 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> |
|
3 * 2005 Wim Taymans <wim@fluendo.com> |
|
4 * 2005 Andy Wingo <wingo@pobox.com> |
|
5 * |
|
6 * gstnetclientclock.h: clock that synchronizes itself to a time provider over |
|
7 * the network |
|
8 * |
|
9 * This library is free software; you can redistribute it and/or |
|
10 * modify it under the terms of the GNU Library General Public |
|
11 * License as published by the Free Software Foundation; either |
|
12 * version 2 of the License, or (at your option) any later version. |
|
13 * |
|
14 * This library is distributed in the hope that it will be useful, |
|
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
17 * Library General Public License for more details. |
|
18 * |
|
19 * You should have received a copy of the GNU Library General Public |
|
20 * License along with this library; if not, write to the |
|
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
22 * Boston, MA 02111-1307, USA. |
|
23 */ |
|
24 /** |
|
25 * SECTION:gstnetclientclock |
|
26 * @short_description: Special clock that synchronizes to a remote time |
|
27 * provider. |
|
28 * @see_also: #GstClock, #GstNetTimeProvider, #GstPipeline |
|
29 * |
|
30 * This object implements a custom #GstClock that synchronizes its time |
|
31 * to a remote time provider such as #GstNetTimeProvider. |
|
32 * |
|
33 * A new clock is created with gst_net_client_clock_new() which takes the |
|
34 * address and port of the remote time provider along with a name and |
|
35 * an initial time. |
|
36 * |
|
37 * This clock will poll the time provider and will update its calibration |
|
38 * parameters based on the local and remote observations. |
|
39 * |
|
40 * Various parameters of the clock can be configured with the parent #GstClock |
|
41 * "timeout", "window-size" and "window-threshold" object properties. |
|
42 * |
|
43 * A #GstNetClientClock is typically set on a #GstPipeline with |
|
44 * gst_pipeline_use_clock(). |
|
45 * |
|
46 * Last reviewed on 2005-11-23 (0.9.5) |
|
47 */ |
|
48 |
|
49 #ifdef HAVE_CONFIG_H |
|
50 #include "config.h" |
|
51 #endif |
|
52 |
|
53 #include "gstnettimepacket.h" |
|
54 #include "gstnetclientclock.h" |
|
55 |
|
56 #ifdef HAVE_UNISTD_H |
|
57 #include <unistd.h> |
|
58 #endif |
|
59 #ifdef __SYMBIAN32__ |
|
60 #include <sys/select.h> |
|
61 #include <glib_global.h> |
|
62 #endif |
|
63 |
|
64 #if defined (_MSC_VER) && _MSC_VER >= 1400 |
|
65 #include <io.h> |
|
66 #endif |
|
67 |
|
68 GST_DEBUG_CATEGORY_STATIC (ncc_debug); |
|
69 #define GST_CAT_DEFAULT (ncc_debug) |
|
70 |
|
71 #define DEFAULT_ADDRESS "127.0.0.1" |
|
72 #define DEFAULT_PORT 5637 |
|
73 #define DEFAULT_TIMEOUT GST_SECOND |
|
74 |
|
75 #ifdef G_OS_WIN32 |
|
76 #define getsockname(sock,addr,len) getsockname(sock,addr,(int *)len) |
|
77 #endif |
|
78 |
|
79 enum |
|
80 { |
|
81 PROP_0, |
|
82 PROP_ADDRESS, |
|
83 PROP_PORT, |
|
84 }; |
|
85 |
|
86 #define GST_NET_CLIENT_CLOCK_GET_PRIVATE(obj) \ |
|
87 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_NET_CLIENT_CLOCK, GstNetClientClockPrivate)) |
|
88 |
|
89 struct _GstNetClientClockPrivate |
|
90 { |
|
91 GstPollFD sock; |
|
92 GstPoll *fdset; |
|
93 }; |
|
94 |
|
95 #define _do_init(type) \ |
|
96 GST_DEBUG_CATEGORY_INIT (ncc_debug, "netclock", 0, "Network client clock"); |
|
97 |
|
98 GST_BOILERPLATE_FULL (GstNetClientClock, gst_net_client_clock, |
|
99 GstSystemClock, GST_TYPE_SYSTEM_CLOCK, _do_init); |
|
100 |
|
101 static void gst_net_client_clock_finalize (GObject * object); |
|
102 static void gst_net_client_clock_set_property (GObject * object, guint prop_id, |
|
103 const GValue * value, GParamSpec * pspec); |
|
104 static void gst_net_client_clock_get_property (GObject * object, guint prop_id, |
|
105 GValue * value, GParamSpec * pspec); |
|
106 |
|
107 static void gst_net_client_clock_stop (GstNetClientClock * self); |
|
108 |
|
109 #ifdef G_OS_WIN32 |
|
110 static int |
|
111 inet_aton (const char *c, struct in_addr *paddr) |
|
112 { |
|
113 /* note that inet_addr is deprecated on unix because |
|
114 * inet_addr returns -1 (INADDR_NONE) for the valid 255.255.255.255 |
|
115 * address. */ |
|
116 paddr->s_addr = inet_addr (c); |
|
117 if (paddr->s_addr == INADDR_NONE) |
|
118 return 0; |
|
119 |
|
120 return 1; |
|
121 } |
|
122 #endif |
|
123 |
|
124 static void |
|
125 gst_net_client_clock_base_init (gpointer g_class) |
|
126 { |
|
127 /* nop */ |
|
128 } |
|
129 |
|
130 static void |
|
131 gst_net_client_clock_class_init (GstNetClientClockClass * klass) |
|
132 { |
|
133 GObjectClass *gobject_class; |
|
134 |
|
135 gobject_class = G_OBJECT_CLASS (klass); |
|
136 |
|
137 g_type_class_add_private (klass, sizeof (GstNetClientClockPrivate)); |
|
138 |
|
139 gobject_class->finalize = gst_net_client_clock_finalize; |
|
140 gobject_class->get_property = gst_net_client_clock_get_property; |
|
141 gobject_class->set_property = gst_net_client_clock_set_property; |
|
142 |
|
143 g_object_class_install_property (gobject_class, PROP_ADDRESS, |
|
144 g_param_spec_string ("address", "address", |
|
145 "The address of the machine providing a time server, " |
|
146 "as a dotted quad (x.x.x.x)", DEFAULT_ADDRESS, G_PARAM_READWRITE)); |
|
147 g_object_class_install_property (gobject_class, PROP_PORT, |
|
148 g_param_spec_int ("port", "port", |
|
149 "The port on which the remote server is listening", 0, G_MAXUINT16, |
|
150 DEFAULT_PORT, G_PARAM_READWRITE)); |
|
151 } |
|
152 |
|
153 static void |
|
154 gst_net_client_clock_init (GstNetClientClock * self, |
|
155 GstNetClientClockClass * g_class) |
|
156 { |
|
157 GstClock *clock = GST_CLOCK_CAST (self); |
|
158 |
|
159 #ifdef G_OS_WIN32 |
|
160 WSADATA w; |
|
161 int error = WSAStartup (0x0202, &w); |
|
162 |
|
163 if (error) { |
|
164 GST_DEBUG_OBJECT (self, "Error on WSAStartup"); |
|
165 } |
|
166 if (w.wVersion != 0x0202) { |
|
167 WSACleanup (); |
|
168 } |
|
169 #endif |
|
170 self->priv = GST_NET_CLIENT_CLOCK_GET_PRIVATE (self); |
|
171 |
|
172 self->port = DEFAULT_PORT; |
|
173 self->address = g_strdup (DEFAULT_ADDRESS); |
|
174 |
|
175 clock->timeout = DEFAULT_TIMEOUT; |
|
176 |
|
177 self->priv->sock.fd = -1; |
|
178 self->thread = NULL; |
|
179 |
|
180 self->servaddr = NULL; |
|
181 } |
|
182 |
|
183 static void |
|
184 gst_net_client_clock_finalize (GObject * object) |
|
185 { |
|
186 GstNetClientClock *self = GST_NET_CLIENT_CLOCK (object); |
|
187 |
|
188 if (self->thread) { |
|
189 gst_net_client_clock_stop (self); |
|
190 g_assert (self->thread == NULL); |
|
191 } |
|
192 |
|
193 if (self->priv->fdset) { |
|
194 gst_poll_free (self->priv->fdset); |
|
195 self->priv->fdset = NULL; |
|
196 } |
|
197 |
|
198 g_free (self->address); |
|
199 self->address = NULL; |
|
200 |
|
201 g_free (self->servaddr); |
|
202 self->servaddr = NULL; |
|
203 |
|
204 #ifdef G_OS_WIN32 |
|
205 WSACleanup (); |
|
206 #endif |
|
207 |
|
208 G_OBJECT_CLASS (parent_class)->finalize (object); |
|
209 } |
|
210 |
|
211 static void |
|
212 gst_net_client_clock_set_property (GObject * object, guint prop_id, |
|
213 const GValue * value, GParamSpec * pspec) |
|
214 { |
|
215 GstNetClientClock *self = GST_NET_CLIENT_CLOCK (object); |
|
216 |
|
217 switch (prop_id) { |
|
218 case PROP_ADDRESS: |
|
219 g_free (self->address); |
|
220 if (g_value_get_string (value) == NULL) |
|
221 self->address = g_strdup (DEFAULT_ADDRESS); |
|
222 else |
|
223 self->address = g_strdup (g_value_get_string (value)); |
|
224 break; |
|
225 case PROP_PORT: |
|
226 self->port = g_value_get_int (value); |
|
227 break; |
|
228 default: |
|
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
230 break; |
|
231 } |
|
232 } |
|
233 |
|
234 static void |
|
235 gst_net_client_clock_get_property (GObject * object, guint prop_id, |
|
236 GValue * value, GParamSpec * pspec) |
|
237 { |
|
238 GstNetClientClock *self = GST_NET_CLIENT_CLOCK (object); |
|
239 |
|
240 switch (prop_id) { |
|
241 case PROP_ADDRESS: |
|
242 g_value_set_string (value, self->address); |
|
243 break; |
|
244 case PROP_PORT: |
|
245 g_value_set_int (value, self->port); |
|
246 break; |
|
247 default: |
|
248 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
249 break; |
|
250 } |
|
251 } |
|
252 |
|
253 static void |
|
254 gst_net_client_clock_observe_times (GstNetClientClock * self, |
|
255 GstClockTime local_1, GstClockTime remote, GstClockTime local_2) |
|
256 { |
|
257 GstClockTime local_avg; |
|
258 gdouble r_squared; |
|
259 GstClock *clock; |
|
260 |
|
261 if (local_2 < local_1) |
|
262 goto bogus_observation; |
|
263 |
|
264 local_avg = (local_2 + local_1) / 2; |
|
265 |
|
266 clock = GST_CLOCK_CAST (self); |
|
267 |
|
268 gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared); |
|
269 |
|
270 GST_CLOCK_SLAVE_LOCK (self); |
|
271 if (clock->filling) { |
|
272 self->current_timeout = 0; |
|
273 } else { |
|
274 /* geto formula */ |
|
275 self->current_timeout = |
|
276 (1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND; |
|
277 self->current_timeout = MIN (self->current_timeout, clock->timeout); |
|
278 } |
|
279 GST_CLOCK_SLAVE_UNLOCK (clock); |
|
280 |
|
281 return; |
|
282 |
|
283 bogus_observation: |
|
284 { |
|
285 GST_WARNING_OBJECT (self, "time packet receive time < send time (%" |
|
286 GST_TIME_FORMAT " < %" GST_TIME_FORMAT ")", GST_TIME_ARGS (local_1), |
|
287 GST_TIME_ARGS (local_2)); |
|
288 return; |
|
289 } |
|
290 } |
|
291 |
|
292 static gint |
|
293 gst_net_client_clock_do_select (GstNetClientClock * self) |
|
294 { |
|
295 while (TRUE) { |
|
296 GstClockTime diff; |
|
297 gint ret; |
|
298 |
|
299 GST_LOG_OBJECT (self, "doing select"); |
|
300 |
|
301 diff = gst_clock_get_internal_time (GST_CLOCK (self)); |
|
302 ret = gst_poll_wait (self->priv->fdset, self->current_timeout); |
|
303 diff = gst_clock_get_internal_time (GST_CLOCK (self)) - diff; |
|
304 |
|
305 if (diff > self->current_timeout) |
|
306 self->current_timeout = 0; |
|
307 else |
|
308 self->current_timeout -= diff; |
|
309 |
|
310 GST_LOG_OBJECT (self, "select returned %d", ret); |
|
311 |
|
312 if (ret < 0 && errno != EBUSY) { |
|
313 if (errno != EAGAIN && errno != EINTR) |
|
314 goto select_error; |
|
315 else |
|
316 continue; |
|
317 } else { |
|
318 return ret; |
|
319 } |
|
320 |
|
321 g_assert_not_reached (); |
|
322 |
|
323 /* log errors and keep going */ |
|
324 select_error: |
|
325 { |
|
326 GST_WARNING_OBJECT (self, "select error %d: %s (%d)", ret, |
|
327 g_strerror (errno), errno); |
|
328 continue; |
|
329 } |
|
330 } |
|
331 |
|
332 g_assert_not_reached (); |
|
333 return -1; |
|
334 } |
|
335 |
|
336 static gpointer |
|
337 gst_net_client_clock_thread (gpointer data) |
|
338 { |
|
339 GstNetClientClock *self = data; |
|
340 struct sockaddr_in tmpaddr; |
|
341 socklen_t len; |
|
342 GstNetTimePacket *packet; |
|
343 gint ret; |
|
344 GstClock *clock = data; |
|
345 |
|
346 while (TRUE) { |
|
347 ret = gst_net_client_clock_do_select (self); |
|
348 |
|
349 if (ret < 0 && errno == EBUSY) { |
|
350 GST_LOG_OBJECT (self, "stop"); |
|
351 goto stopped; |
|
352 } else if (ret == 0) { |
|
353 /* timed out, let's send another packet */ |
|
354 GST_DEBUG_OBJECT (self, "timed out"); |
|
355 |
|
356 packet = gst_net_time_packet_new (NULL); |
|
357 |
|
358 packet->local_time = gst_clock_get_internal_time (GST_CLOCK (self)); |
|
359 |
|
360 GST_DEBUG_OBJECT (self, "sending packet, local time = %" GST_TIME_FORMAT, |
|
361 GST_TIME_ARGS (packet->local_time)); |
|
362 gst_net_time_packet_send (packet, self->priv->sock.fd, |
|
363 (struct sockaddr *) self->servaddr, sizeof (struct sockaddr_in)); |
|
364 |
|
365 g_free (packet); |
|
366 |
|
367 /* reset timeout */ |
|
368 self->current_timeout = clock->timeout; |
|
369 continue; |
|
370 } else if (gst_poll_fd_can_read (self->priv->fdset, &self->priv->sock)) { |
|
371 /* got data in */ |
|
372 GstClockTime new_local = gst_clock_get_internal_time (GST_CLOCK (self)); |
|
373 |
|
374 len = sizeof (struct sockaddr); |
|
375 packet = gst_net_time_packet_receive (self->priv->sock.fd, |
|
376 (struct sockaddr *) &tmpaddr, &len); |
|
377 |
|
378 if (!packet) |
|
379 goto receive_error; |
|
380 |
|
381 GST_LOG_OBJECT (self, "got packet back"); |
|
382 GST_LOG_OBJECT (self, "local_1 = %" GST_TIME_FORMAT, |
|
383 GST_TIME_ARGS (packet->local_time)); |
|
384 GST_LOG_OBJECT (self, "remote = %" GST_TIME_FORMAT, |
|
385 GST_TIME_ARGS (packet->remote_time)); |
|
386 GST_LOG_OBJECT (self, "local_2 = %" GST_TIME_FORMAT, |
|
387 GST_TIME_ARGS (new_local)); |
|
388 |
|
389 /* observe_times will reset the timeout */ |
|
390 gst_net_client_clock_observe_times (self, packet->local_time, |
|
391 packet->remote_time, new_local); |
|
392 |
|
393 g_free (packet); |
|
394 continue; |
|
395 } else { |
|
396 GST_WARNING_OBJECT (self, "unhandled select return state?"); |
|
397 continue; |
|
398 } |
|
399 |
|
400 g_assert_not_reached (); |
|
401 |
|
402 stopped: |
|
403 { |
|
404 GST_DEBUG_OBJECT (self, "shutting down"); |
|
405 /* socket gets closed in _stop() */ |
|
406 return NULL; |
|
407 } |
|
408 receive_error: |
|
409 { |
|
410 GST_WARNING_OBJECT (self, "receive error"); |
|
411 continue; |
|
412 } |
|
413 |
|
414 g_assert_not_reached (); |
|
415 |
|
416 } |
|
417 |
|
418 g_assert_not_reached (); |
|
419 |
|
420 return NULL; |
|
421 } |
|
422 |
|
423 static gboolean |
|
424 gst_net_client_clock_start (GstNetClientClock * self) |
|
425 { |
|
426 struct sockaddr_in servaddr, myaddr; |
|
427 socklen_t len; |
|
428 gint ret; |
|
429 GError *error; |
|
430 |
|
431 g_return_val_if_fail (self->address != NULL, FALSE); |
|
432 g_return_val_if_fail (self->servaddr == NULL, FALSE); |
|
433 |
|
434 if ((ret = socket (AF_INET, SOCK_DGRAM, 0)) < 0) |
|
435 goto no_socket; |
|
436 |
|
437 self->priv->sock.fd = ret; |
|
438 |
|
439 len = sizeof (myaddr); |
|
440 #ifndef __SYMBIAN32__ |
|
441 ret = getsockname (self->priv->sock.fd, (struct sockaddr *) &myaddr, &len); |
|
442 if (ret < 0) |
|
443 goto getsockname_error; |
|
444 #endif |
|
445 memset (&servaddr, 0, sizeof (servaddr)); |
|
446 servaddr.sin_family = AF_INET; /* host byte order */ |
|
447 servaddr.sin_port = htons (self->port); /* short, network byte order */ |
|
448 |
|
449 GST_DEBUG_OBJECT (self, "socket opened on UDP port %hd", |
|
450 ntohs (servaddr.sin_port)); |
|
451 |
|
452 #ifndef __SYMBIAN32__ |
|
453 if (!inet_aton (self->address, &servaddr.sin_addr)) |
|
454 goto bad_address; |
|
455 #else |
|
456 if ((servaddr.sin_addr.s_addr=inet_addr(self->address))==-1) |
|
457 goto bad_address; |
|
458 #endif//__SYMBIAN32__ |
|
459 self->servaddr = g_malloc (sizeof (struct sockaddr_in)); |
|
460 memcpy (self->servaddr, &servaddr, sizeof (servaddr)); |
|
461 |
|
462 GST_DEBUG_OBJECT (self, "will communicate with %s:%d", self->address, |
|
463 self->port); |
|
464 |
|
465 gst_poll_add_fd (self->priv->fdset, &self->priv->sock); |
|
466 gst_poll_fd_ctl_read (self->priv->fdset, &self->priv->sock, TRUE); |
|
467 |
|
468 self->thread = g_thread_create (gst_net_client_clock_thread, self, TRUE, |
|
469 &error); |
|
470 if (!self->thread) |
|
471 goto no_thread; |
|
472 |
|
473 return TRUE; |
|
474 |
|
475 /* ERRORS */ |
|
476 no_socket: |
|
477 { |
|
478 GST_ERROR_OBJECT (self, "socket failed %d: %s (%d)", ret, |
|
479 g_strerror (errno), errno); |
|
480 return FALSE; |
|
481 } |
|
482 getsockname_error: |
|
483 { |
|
484 GST_ERROR_OBJECT (self, "getsockname failed %d: %s (%d)", ret, |
|
485 g_strerror (errno), errno); |
|
486 close (self->priv->sock.fd); |
|
487 self->priv->sock.fd = -1; |
|
488 return FALSE; |
|
489 } |
|
490 bad_address: |
|
491 { |
|
492 GST_ERROR_OBJECT (self, "inet_aton failed %d: %s (%d)", ret, |
|
493 g_strerror (errno), errno); |
|
494 close (self->priv->sock.fd); |
|
495 self->priv->sock.fd = -1; |
|
496 return FALSE; |
|
497 } |
|
498 no_thread: |
|
499 { |
|
500 GST_ERROR_OBJECT (self, "could not create thread: %s", error->message); |
|
501 gst_poll_remove_fd (self->priv->fdset, &self->priv->sock); |
|
502 close (self->priv->sock.fd); |
|
503 self->priv->sock.fd = -1; |
|
504 g_free (self->servaddr); |
|
505 self->servaddr = NULL; |
|
506 g_error_free (error); |
|
507 return FALSE; |
|
508 } |
|
509 } |
|
510 |
|
511 static void |
|
512 gst_net_client_clock_stop (GstNetClientClock * self) |
|
513 { |
|
514 gst_poll_set_flushing (self->priv->fdset, TRUE); |
|
515 g_thread_join (self->thread); |
|
516 self->thread = NULL; |
|
517 |
|
518 if (self->priv->sock.fd != -1) { |
|
519 gst_poll_remove_fd (self->priv->fdset, &self->priv->sock); |
|
520 close (self->priv->sock.fd); |
|
521 self->priv->sock.fd = -1; |
|
522 } |
|
523 } |
|
524 |
|
525 /** |
|
526 * gst_net_client_clock_new: |
|
527 * @name: a name for the clock |
|
528 * @remote_address: the address of the remote clock provider |
|
529 * @remote_port: the port of the remote clock provider |
|
530 * @base_time: initial time of the clock |
|
531 * |
|
532 * Create a new #GstNetClientClock that will report the time |
|
533 * provided by the #GstNetClockProvider on @remote_address and |
|
534 * @remote_port. |
|
535 * |
|
536 * Returns: a new #GstClock that receives a time from the remote |
|
537 * clock. |
|
538 */ |
|
539 #ifdef __SYMBIAN32__ |
|
540 EXPORT_C |
|
541 #endif |
|
542 |
|
543 GstClock * |
|
544 gst_net_client_clock_new (gchar * name, const gchar * remote_address, |
|
545 gint remote_port, GstClockTime base_time) |
|
546 { |
|
547 GstNetClientClock *ret; |
|
548 GstClockTime internal; |
|
549 |
|
550 g_return_val_if_fail (remote_address != NULL, NULL); |
|
551 g_return_val_if_fail (remote_port > 0, NULL); |
|
552 g_return_val_if_fail (remote_port <= G_MAXUINT16, NULL); |
|
553 g_return_val_if_fail (base_time != GST_CLOCK_TIME_NONE, NULL); |
|
554 |
|
555 ret = g_object_new (GST_TYPE_NET_CLIENT_CLOCK, "address", remote_address, |
|
556 "port", remote_port, NULL); |
|
557 |
|
558 /* gst_clock_get_time() values are guaranteed to be increasing. because no one |
|
559 * has called get_time on this clock yet we are free to adjust to any value |
|
560 * without worrying about worrying about MAX() issues with the clock's |
|
561 * internal time. |
|
562 */ |
|
563 |
|
564 /* update our internal time so get_time() give something around base_time. |
|
565 assume that the rate is 1 in the beginning. */ |
|
566 internal = gst_clock_get_internal_time (GST_CLOCK (ret)); |
|
567 gst_clock_set_calibration (GST_CLOCK (ret), internal, base_time, 1, 1); |
|
568 |
|
569 { |
|
570 GstClockTime now = gst_clock_get_time (GST_CLOCK (ret)); |
|
571 |
|
572 if (now < base_time || now > base_time + GST_SECOND) |
|
573 g_warning ("unable to set the base time, expect sync problems!"); |
|
574 } |
|
575 |
|
576 if ((ret->priv->fdset = gst_poll_new (TRUE)) == NULL) |
|
577 goto no_fdset; |
|
578 |
|
579 if (!gst_net_client_clock_start (ret)) |
|
580 goto failed_start; |
|
581 |
|
582 /* all systems go, cap'n */ |
|
583 return (GstClock *) ret; |
|
584 |
|
585 no_fdset: |
|
586 { |
|
587 GST_ERROR_OBJECT (ret, "could not create an fdset: %s (%d)", |
|
588 g_strerror (errno), errno); |
|
589 gst_object_unref (ret); |
|
590 return NULL; |
|
591 } |
|
592 failed_start: |
|
593 { |
|
594 /* already printed a nice error */ |
|
595 gst_object_unref (ret); |
|
596 return NULL; |
|
597 } |
|
598 } |