diff --git a/ChangeLog b/ChangeLog index 86e6f805b1..cc6c55bdb4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2006-09-29 Wim Taymans + + * gst/rtsp/URLS: + Add some more URLs. + + * gst/rtsp/gstrtspsrc.c: (gst_rtspsrc_class_init), + (gst_rtspsrc_init), (gst_rtspsrc_finalize), + (gst_rtspsrc_set_property), (gst_rtspsrc_get_property), + (gst_rtspsrc_stream_setup_rtp), (gst_rtspsrc_loop_interleaved), + (gst_rtspsrc_loop_udp), (gst_rtspsrc_loop_send_cmd), + (gst_rtspsrc_loop), (gst_rtspsrc_send), + (gst_rtspsrc_parse_methods), (gst_rtspsrc_open), + (gst_rtspsrc_close), (gst_rtspsrc_play), (gst_rtspsrc_pause), + (gst_rtspsrc_handle_message), (gst_rtspsrc_change_state): + * gst/rtsp/gstrtspsrc.h: + Add timeout property to control UDP timeouts. + Fix error messages. + Also start a loop function when operating in UDP mode so that we can + do some more stuff async. + Handle element messages from udpsrc to detect timeouts. If a timeout + happens we currently generate an error. + API: rtspsrc::timeout property. + + * gst/udp/gstudpsrc.c: (gst_udpsrc_class_init), + (gst_udpsrc_create): + Really implement the timeout in microseconds and not milliseconds. + 2006-09-29 Wim Taymans * gst/udp/gstudpsrc.c: (gst_udpsrc_class_init), (gst_udpsrc_init), diff --git a/gst/rtsp/URLS b/gst/rtsp/URLS index bb16ce553d..16d5d5a4e1 100644 --- a/gst/rtsp/URLS +++ b/gst/rtsp/URLS @@ -2,6 +2,7 @@ Some test URLS: SVQ3 video: rtsp://cumulus.creative.auckland.ac.nz/~shado/nelson_iv_512k.mov + rtsp://streamr.hitpops.jp/ngc/mov/m0609.mov ASF (audio/video): rtsp://aod.mylisten.com/aod/8/03/069803_0903135.wma @@ -11,3 +12,7 @@ ASF (audio/video): MP4V-ES/mpeg4-generic(ACC): rtsp://vod.nwec.jp/quicktime/505.mov + rtsp://203.140.68.241:554/hirakataeizou9.mp4 + +REAL: + rtsp://213.254.239.61/farm/*/encoder/tagesschau/live1high.rm diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index 0631bbed8b..c95362f77d 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -123,6 +123,7 @@ enum #define DEFAULT_PROTOCOLS GST_RTSP_PROTO_UDP_UNICAST | GST_RTSP_PROTO_UDP_MULTICAST | GST_RTSP_PROTO_TCP #define DEFAULT_DEBUG FALSE #define DEFAULT_RETRY 20 +#define DEFAULT_TIMEOUT 5000000 enum { @@ -131,7 +132,7 @@ enum PROP_PROTOCOLS, PROP_DEBUG, PROP_RETRY, - /* FILL ME */ + PROP_TIMEOUT, }; #define GST_TYPE_RTSP_PROTO (gst_rtsp_proto_get_type()) @@ -161,6 +162,7 @@ static GstCaps *gst_rtspsrc_media_to_caps (gint pt, SDPMedia * media); static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, GstStateChange transition); +static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message); static void gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); @@ -172,6 +174,11 @@ static gboolean gst_rtspsrc_uri_set_uri (GstURIHandler * handler, static void gst_rtspsrc_loop (GstRTSPSrc * src); +/* commands we send to out loop to notify it of events */ +#define CMD_WAIT 0 +#define CMD_RECONNECT 1 +#define CMD_STOP 2 + /*static guint gst_rtspsrc_signals[LAST_SIGNAL] = { 0 }; */ static void @@ -239,7 +246,15 @@ gst_rtspsrc_class_init (GstRTSPSrcClass * klass) 0, G_MAXUINT16, DEFAULT_RETRY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (gobject_class, PROP_TIMEOUT, + g_param_spec_uint64 ("timeout", "Timeout", + "Retry TCP transport after timeout microseconds (0 = disabled)", + 0, G_MAXUINT64, DEFAULT_TIMEOUT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + gstelement_class->change_state = gst_rtspsrc_change_state; + + gstbin_class->handle_message = gst_rtspsrc_handle_message; } static void @@ -248,6 +263,8 @@ gst_rtspsrc_init (GstRTSPSrc * src, GstRTSPSrcClass * g_class) src->stream_rec_lock = g_new (GStaticRecMutex, 1); g_static_rec_mutex_init (src->stream_rec_lock); + src->loop_cond = g_cond_new (); + src->location = DEFAULT_LOCATION; src->url = NULL; } @@ -261,6 +278,7 @@ gst_rtspsrc_finalize (GObject * object) g_static_rec_mutex_free (rtspsrc->stream_rec_lock); g_free (rtspsrc->stream_rec_lock); + g_cond_free (rtspsrc->loop_cond); G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -287,6 +305,9 @@ gst_rtspsrc_set_property (GObject * object, guint prop_id, const GValue * value, case PROP_RETRY: rtspsrc->retry = g_value_get_uint (value); break; + case PROP_TIMEOUT: + rtspsrc->timeout = g_value_get_uint64 (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -314,6 +335,9 @@ gst_rtspsrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_RETRY: g_value_set_uint (value, rtspsrc->retry); break; + case PROP_TIMEOUT: + g_value_set_uint64 (value, rtspsrc->timeout); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -666,6 +690,9 @@ again: if (*rtpport != tmp_rtp || *rtcpport != tmp_rtcp) goto port_error; + /* configure a timeout */ + g_object_set (G_OBJECT (rtpsrc), "timeout", src->timeout, NULL); + /* we manage these elements, we set the caps in configure_transport */ stream->rtpsrc = rtpsrc; stream->rtcpsrc = rtcpsrc; @@ -910,7 +937,7 @@ gst_rtspsrc_push_event (GstRTSPSrc * src, GstEvent * event) } static void -gst_rtspsrc_loop (GstRTSPSrc * src) +gst_rtspsrc_loop_interleaved (GstRTSPSrc * src) { RTSPMessage response = { 0 }; RTSPResult res; @@ -928,6 +955,7 @@ gst_rtspsrc_loop (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "doing receive"); if ((res = rtsp_connection_receive (src->connection, &response)) < 0) goto receive_error; + GST_DEBUG_OBJECT (src, "got packet type %d", response.type); } while (response.type != RTSP_MESSAGE_DATA); @@ -1003,8 +1031,8 @@ receive_error: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, READ, - ("Could not receive message. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); g_free (str); if (src->debug) rtsp_message_dump (&response); @@ -1014,8 +1042,8 @@ receive_error: } invalid_length: { - GST_ELEMENT_WARNING (src, RESOURCE, READ, - ("Short message received."), (NULL)); + GST_ELEMENT_WARNING (src, RESOURCE, READ, (NULL), + ("Short message received.")); rtsp_message_unset (&response); return; } @@ -1049,6 +1077,55 @@ need_pause: } } +static void +gst_rtspsrc_loop_udp (GstRTSPSrc * src) +{ + GST_OBJECT_LOCK (src); + if (src->loop_cmd == CMD_STOP) + goto stopping; + while (src->loop_cmd == CMD_WAIT) { + GST_DEBUG_OBJECT (src, "waiting"); + GST_RTSP_LOOP_WAIT (src); + GST_DEBUG_OBJECT (src, "waiting done"); + if (src->loop_cmd == CMD_STOP) + goto stopping; + } + if (src->loop_cmd == CMD_RECONNECT) { + /* FIXME, when we get here we have to reconnect using tcp */ + src->loop_cmd = CMD_WAIT; + } + GST_OBJECT_UNLOCK (src); + + return; + + /* ERRORS */ +stopping: + { + GST_OBJECT_UNLOCK (src); + src->running = FALSE; + gst_task_pause (src->task); + return; + } +} + +static void +gst_rtspsrc_loop_send_cmd (GstRTSPSrc * src, gint cmd) +{ + GST_OBJECT_LOCK (src); + src->loop_cmd = cmd; + GST_RTSP_LOOP_SIGNAL (src); + GST_OBJECT_UNLOCK (src); +} + +static void +gst_rtspsrc_loop (GstRTSPSrc * src) +{ + if (src->interleaved) + gst_rtspsrc_loop_interleaved (src); + else + gst_rtspsrc_loop_udp (src); +} + /** * gst_rtspsrc_send: * @src: the rtsp source @@ -1100,8 +1177,8 @@ send_error: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, - ("Could not send message. (%s)", res), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", res)); g_free (str); return FALSE; } @@ -1109,8 +1186,8 @@ receive_error: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, READ, - ("Could not receive message. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Could not receive message. (%s)", str)); g_free (str); return FALSE; } @@ -1118,13 +1195,13 @@ error_response: { switch (response->type_data.response.code) { case RTSP_STS_NOT_FOUND: - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, ("%s", - response->type_data.response.reason), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", + response->type_data.response.reason)); break; default: - GST_ELEMENT_ERROR (src, RESOURCE, READ, ("Got error response: %d (%s).", - response->type_data.response.code, - response->type_data.response.reason), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Got error response: %d (%s).", response->type_data.response.code, + response->type_data.response.reason)); break; } /* we return FALSE so we should unset the response ourselves */ @@ -1192,14 +1269,14 @@ done: /* ERRORS */ no_describe: { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, - ("Server does not support DESCRIBE."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("Server does not support DESCRIBE.")); return FALSE; } no_setup: { - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, - ("Server does not support SETUP."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("Server does not support SETUP.")); return FALSE; } } @@ -1216,6 +1293,7 @@ gst_rtspsrc_open (GstRTSPSrc * src) SDPMessage sdp = { 0 }; GstRTSPProto protocols; GstRTSPStream *stream = NULL; + gchar *respcont = NULL; /* can't continue without a valid url */ if (G_UNLIKELY (src->url == NULL)) @@ -1261,22 +1339,18 @@ gst_rtspsrc_open (GstRTSPSrc * src) goto send_error; /* check if reply is SDP */ - { - gchar *respcont = NULL; - - rtsp_message_get_header (&response, RTSP_HDR_CONTENT_TYPE, &respcont); - /* could not be set but since the request returned OK, we assume it - * was SDP, else check it. */ - if (respcont) { - if (!g_ascii_strcasecmp (respcont, "application/sdp") == 0) - goto wrong_content_type; - } + rtsp_message_get_header (&response, RTSP_HDR_CONTENT_TYPE, &respcont); + /* could not be set but since the request returned OK, we assume it + * was SDP, else check it. */ + if (respcont) { + if (!g_ascii_strcasecmp (respcont, "application/sdp") == 0) + goto wrong_content_type; } /* get message body and parse as SDP */ rtsp_message_get_body (&response, &data, &size); - GST_DEBUG_OBJECT (src, "parse sdp..."); + GST_DEBUG_OBJECT (src, "parse SDP..."); sdp_message_init (&sdp); sdp_message_parse_buffer (data, size, &sdp); @@ -1462,16 +1536,16 @@ gst_rtspsrc_open (GstRTSPSrc * src) /* ERRORS */ no_url: { - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, - ("No valid RTSP url was provided"), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), + ("No valid RTSP URL was provided")); goto cleanup_error; } could_not_create: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, - ("Could not create connection. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), + ("Could not create connection. (%s)", str)); g_free (str); goto cleanup_error; } @@ -1479,8 +1553,8 @@ could_not_connect: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, - ("Could not connect to server. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ_WRITE, (NULL), + ("Could not connect to server. (%s)", str)); g_free (str); goto cleanup_error; } @@ -1488,8 +1562,8 @@ create_request_failed: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - ("Could not create request. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request. (%s)", str)); g_free (str); goto cleanup_error; } @@ -1497,8 +1571,8 @@ send_error: { gchar *str = rtsp_strresult (res); - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, - ("Could not send message. (%s)", str), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message. (%s)", str)); g_free (str); goto cleanup_error; } @@ -1509,20 +1583,20 @@ methods_error: } wrong_content_type: { - GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, - ("Server does not support SDP."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server does not support SDP, got %s.", respcont)); goto cleanup_error; } setup_rtp_failed: { - GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, ("Could not setup rtp."), - (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Could not setup rtp.")); goto cleanup_error; } no_transport: { - GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, - ("Server did not select transport."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, (NULL), + ("Server did not select transport.")); goto cleanup_error; } cleanup_error: @@ -1542,6 +1616,8 @@ gst_rtspsrc_close (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "TEARDOWN..."); + gst_rtspsrc_loop_send_cmd (src, CMD_STOP); + /* stop task if any */ if (src->task) { gst_task_stop (src->task); @@ -1582,20 +1658,20 @@ gst_rtspsrc_close (GstRTSPSrc * src) /* ERRORS */ create_request_failed: { - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - ("Could not create request."), (NULL)); + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request.")); return FALSE; } send_error: { rtsp_message_unset (&request); - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, - ("Could not send message."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message.")); return FALSE; } close_failed: { - GST_ELEMENT_ERROR (src, RESOURCE, CLOSE, ("Close failed."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, CLOSE, (NULL), ("Close failed.")); return FALSE; } } @@ -1660,29 +1736,28 @@ gst_rtspsrc_play (GstRTSPSrc * src) /* for interleaved transport, we receive the data on the RTSP connection * instead of UDP. We start a task to select and read from that connection. */ - if (src->interleaved) { - if (src->task == NULL) { - src->task = gst_task_create ((GstTaskFunction) gst_rtspsrc_loop, src); - gst_task_set_lock (src->task, src->stream_rec_lock); - } - src->running = TRUE; - gst_task_start (src->task); + if (src->task == NULL) { + src->task = gst_task_create ((GstTaskFunction) gst_rtspsrc_loop, src); + gst_task_set_lock (src->task, src->stream_rec_lock); } + src->running = TRUE; + gst_rtspsrc_loop_send_cmd (src, CMD_WAIT); + gst_task_start (src->task); return TRUE; /* ERRORS */ create_request_failed: { - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - ("Could not create request."), (NULL)); + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request.")); return FALSE; } send_error: { rtsp_message_unset (&request); - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, - ("Could not send message."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message.")); return FALSE; } } @@ -1714,19 +1789,49 @@ gst_rtspsrc_pause (GstRTSPSrc * src) /* ERRORS */ create_request_failed: { - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - ("Could not create request."), (NULL)); + GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), + ("Could not create request.")); return FALSE; } send_error: { rtsp_message_unset (&request); - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, - ("Could not send message."), (NULL)); + GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), + ("Could not send message.")); return FALSE; } } +static void +gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message) +{ + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_ELEMENT: + { + GstRTSPSrc *rtspsrc; + const GstStructure *s = gst_message_get_structure (message); + + rtspsrc = GST_RTSPSRC (bin); + + if (gst_structure_has_name (s, "GstUDPSrcTimeout")) { + GST_DEBUG_OBJECT (bin, "timeout on UDP port"); + gst_rtspsrc_loop_send_cmd (rtspsrc, CMD_RECONNECT); + /* FIXME, we post an error message now to inform the user + * that nothing happened. It's most likely a firewall thing. */ + GST_ELEMENT_ERROR (rtspsrc, RESOURCE, READ, (NULL), + ("Could not receive any UDP packets for %" G_GUINT64_FORMAT + ".%d seconds, maybe your firewall is blocking it.", + rtspsrc->timeout / 1000000, rtspsrc->timeout % 1000000)); + return; + } + } + /* Fallthrough */ + default: + GST_BIN_CLASS (parent_class)->handle_message (bin, message); + break; + } +} + static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) { @@ -1735,7 +1840,6 @@ gst_rtspsrc_change_state (GstElement * element, GstStateChange transition) rtspsrc = GST_RTSPSRC (element); - switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: break; diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h index 53504cff09..21fb073edf 100644 --- a/gst/rtsp/gstrtspsrc.h +++ b/gst/rtsp/gstrtspsrc.h @@ -61,10 +61,16 @@ G_BEGIN_DECLS (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTSPSRC)) #define GST_IS_RTSPSRC_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTSPSRC)) +#define GST_RTSPSRC_CAST(obj) \ + ((GstRTSPSrc *)(obj)) typedef struct _GstRTSPSrc GstRTSPSrc; typedef struct _GstRTSPSrcClass GstRTSPSrcClass; +#define GST_RTSP_LOOP_GET_COND(rtsp) (GST_RTSPSRC_CAST(rtsp)->loop_cond) +#define GST_RTSP_LOOP_WAIT(rtsp) (g_cond_wait(GST_RTSP_LOOP_GET_COND (rtsp), GST_OBJECT_GET_LOCK (rtsp))) +#define GST_RTSP_LOOP_SIGNAL(rtsp) (g_cond_signal(GST_RTSP_LOOP_GET_COND (rtsp))) + /** * GstRTSPProto: * @GST_RTSP_PROTO_UDP_UNICAST: Use unicast UDP transfer. @@ -126,16 +132,21 @@ struct _GstRTSPSrc { GstSegment segment; gboolean running; + /* cond to signal loop */ + GCond *loop_cond; + gint loop_cmd; + gint numstreams; GList *streams; GstStructure *props; gchar *location; RTSPUrl *url; + GstRTSPProto protocols; gboolean debug; guint retry; + guint64 timeout; - GstRTSPProto protocols; /* supported methods */ gint methods; diff --git a/gst/udp/gstudpsrc.c b/gst/udp/gstudpsrc.c index ba395e385e..3d4d72f275 100644 --- a/gst/udp/gstudpsrc.c +++ b/gst/udp/gstudpsrc.c @@ -89,8 +89,8 @@ * * * #guint64 - * "timeout": the timeout that expired when - * waiting for data. + * "timeout": the timeout in microseconds that + * expired when waiting for data. * * * @@ -250,8 +250,8 @@ gst_udpsrc_class_init (GstUDPSrcClass * klass) UDP_DEFAULT_BUFFER_SIZE, G_PARAM_READWRITE)); g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TIMEOUT, g_param_spec_uint64 ("timeout", "Timeout", - "Post a message after this timeout (in microseconds) (0 = disabled)", - 0, G_MAXUINT64, UDP_DEFAULT_TIMEOUT, G_PARAM_READWRITE)); + "Post a message after timeout microseconds (0 = disabled)", 0, + G_MAXUINT64, UDP_DEFAULT_TIMEOUT, G_PARAM_READWRITE)); gstbasesrc_class->start = gst_udpsrc_start; gstbasesrc_class->stop = gst_udpsrc_stop; @@ -331,8 +331,8 @@ gst_udpsrc_create (GstPushSrc * psrc, GstBuffer ** buf) udpsrc->timeout); if (udpsrc->timeout > 0) { - timeval.tv_sec = udpsrc->timeout / 1000; - timeval.tv_usec = (udpsrc->timeout % 1000) * 1000; + timeval.tv_sec = udpsrc->timeout / 1000000; + timeval.tv_usec = udpsrc->timeout % 1000000; timeout = &timeval; } else { timeout = NULL;