diff --git a/gst/rtsp-server/rtsp-media.c b/gst/rtsp-server/rtsp-media.c index 35a9ec0cfd..45463f6652 100644 --- a/gst/rtsp-server/rtsp-media.c +++ b/gst/rtsp-server/rtsp-media.c @@ -152,6 +152,7 @@ struct _GstRTSPMediaPrivate /* Dynamic element handling */ guint nb_dynamic_elements; guint no_more_pads_pending; + gboolean expected_async_done; }; #define DEFAULT_SHARED FALSE @@ -476,6 +477,7 @@ gst_rtsp_media_init (GstRTSPMedia * media) priv->max_mcast_ttl = DEFAULT_MAX_MCAST_TTL; priv->bind_mcast_address = DEFAULT_BIND_MCAST_ADDRESS; priv->do_rate_control = DEFAULT_DO_RATE_CONTROL; + priv->expected_async_done = FALSE; } static void @@ -2819,6 +2821,30 @@ gst_rtsp_media_seek_trickmode (GstRTSPMedia * media, GstEvent *seek_event; gboolean unblock = FALSE; + /* Handle expected async-done before waiting on next async-done. + * + * Since the seek further down in code will cause a preroll and + * a async-done will be generated it's important to wait on async-done + * if that is expected. Otherwise there is the risk that the waiting + * for async-done after the seek is detecting the expected async-done + * instead of the one that corresponds to the seek. Then execution + * continue and act as if the pipeline is prerolled, but it's not. + * + * During wait_preroll message GST_MESSAGE_ASYNC_DONE will come + * and then the state will change from preparing to prepared */ + if (priv->expected_async_done) { + GST_DEBUG (" expected to get async-done, waiting "); + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); + g_rec_mutex_unlock (&priv->state_lock); + + /* wait until pipeline is prerolled */ + if (!wait_preroll (media)) + goto preroll_failed_expected_async_done; + + g_rec_mutex_lock (&priv->state_lock); + GST_DEBUG (" got expected async-done"); + } + gst_rtsp_media_set_status (media, GST_RTSP_MEDIA_STATUS_PREPARING); if (rate < 0.0) { @@ -2910,6 +2936,11 @@ preroll_failed: GST_WARNING ("failed to preroll after seek"); return FALSE; } +preroll_failed_expected_async_done: + { + GST_WARNING ("failed to preroll"); + return FALSE; + } } /** @@ -3118,6 +3149,8 @@ default_handle_message (GstRTSPMedia * media, GstMessage * message) case GST_MESSAGE_STREAM_STATUS: break; case GST_MESSAGE_ASYNC_DONE: + if (priv->expected_async_done) + priv->expected_async_done = FALSE; if (priv->complete) { /* receive the final ASYNC_DONE, that is posted by the media pipeline * after all the transport parts have been successfully added to @@ -4459,6 +4492,8 @@ static void media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) { GstRTSPMediaPrivate *priv = media->priv; + GstStateChangeReturn set_state_ret; + priv->expected_async_done = FALSE; if (state == GST_STATE_NULL) { gst_rtsp_media_unprepare (media); @@ -4474,11 +4509,15 @@ media_set_pipeline_state_locked (GstRTSPMedia * media, GstState state) /* make sure pads are not blocking anymore when going to PLAYING */ media_unblock_linked (media); - set_state (media, state); - - /* and suspend after pause */ - if (state == GST_STATE_PAUSED) + if (state == GST_STATE_PAUSED) { + set_state_ret = set_state (media, state); + if (set_state_ret == GST_STATE_CHANGE_ASYNC) + priv->expected_async_done = TRUE; + /* and suspend after pause */ gst_rtsp_media_suspend (media); + } else { + set_state (media, state); + } } } } diff --git a/tests/check/gst/rtspserver.c b/tests/check/gst/rtspserver.c index f3bd5d0676..da6df2d72f 100644 --- a/tests/check/gst/rtspserver.c +++ b/tests/check/gst/rtspserver.c @@ -483,6 +483,16 @@ do_simple_request (GstRTSPConnection * conn, GstRTSPMethod method, NULL, NULL, NULL, NULL, NULL); } +/* send an rtsp request with a method,session and range in, + * and receive response. range_in is the Range in req header */ +static GstRTSPStatusCode +do_simple_request_rangein (GstRTSPConnection * conn, GstRTSPMethod method, + const gchar * session, const gchar * rangein) +{ + return do_request (conn, method, NULL, session, NULL, rangein, NULL, + NULL, NULL, NULL, NULL, NULL); +} + /* send a DESCRIBE request and receive response. returns a received * GstSDPMessage that must be freed by the caller */ static GstSDPMessage * @@ -2473,6 +2483,121 @@ GST_START_TEST (test_suspend_mode_reset_only_audio) GST_END_TEST; + +static GstRTSPStatusCode +adjust_play_mode (GstRTSPClient * client, GstRTSPContext * ctx, + GstRTSPTimeRange ** range, GstSeekFlags * flags, gdouble * rate, + GstClockTime * trickmode_interval, gboolean * enable_rate_control) +{ + GstRTSPState rtspstate; + + rtspstate = gst_rtsp_session_media_get_rtsp_state (ctx->sessmedia); + if (rtspstate == GST_RTSP_STATE_PLAYING) { + if (!gst_rtsp_session_media_set_state (ctx->sessmedia, GST_STATE_PAUSED)) + return GST_RTSP_STS_INTERNAL_SERVER_ERROR; + + if (!gst_rtsp_media_unsuspend (ctx->media)) + return GST_RTSP_STS_INTERNAL_SERVER_ERROR; + } + + return GST_RTSP_STS_OK; +} + +GST_START_TEST (test_double_play) +{ + GstRTSPMountPoints *mounts; + gchar *service; + GstRTSPMediaFactory *factory; + GstRTSPConnection *conn; + GstSDPMessage *sdp_message = NULL; + const GstSDPMedia *sdp_media; + const gchar *video_control; + const gchar *audio_control; + GstRTSPRange client_port; + gchar *session = NULL; + GstRTSPTransport *audio_transport = NULL; + GstRTSPTransport *video_transport = NULL; + GSocket *rtp_socket, *rtcp_socket; + GstRTSPClient *client; + GstRTSPClientClass *klass; + + client = gst_rtsp_client_new (); + klass = GST_RTSP_CLIENT_GET_CLASS (client); + klass->adjust_play_mode = adjust_play_mode; + + mounts = gst_rtsp_server_get_mount_points (server); + + factory = gst_rtsp_media_factory_new (); + gst_rtsp_media_factory_set_launch (factory, + "( " VIDEO_PIPELINE " " AUDIO_PIPELINE " )"); + gst_rtsp_mount_points_add_factory (mounts, TEST_MOUNT_POINT, factory); + g_object_unref (mounts); + + + /* set port to any */ + gst_rtsp_server_set_service (server, "0"); + + /* attach to default main context */ + source_id = gst_rtsp_server_attach (server, NULL); + fail_if (source_id == 0); + + /* get port */ + service = gst_rtsp_server_get_service (server); + test_port = atoi (service); + fail_unless (test_port != 0); + g_free (service); + + conn = connect_to_server (test_port, TEST_MOUNT_POINT); + + sdp_message = do_describe (conn, TEST_MOUNT_POINT); + + /* get control strings from DESCRIBE response */ + fail_unless (gst_sdp_message_medias_len (sdp_message) == 2); + sdp_media = gst_sdp_message_get_media (sdp_message, 0); + video_control = gst_sdp_media_get_attribute_val (sdp_media, "control"); + sdp_media = gst_sdp_message_get_media (sdp_message, 1); + audio_control = gst_sdp_media_get_attribute_val (sdp_media, "control"); + + get_client_ports_full (&client_port, &rtp_socket, &rtcp_socket); + + /* do SETUP for video */ + fail_unless (do_setup (conn, video_control, &client_port, &session, + &video_transport) == GST_RTSP_STS_OK); + + /* do SETUP for audio */ + fail_unless (do_setup (conn, audio_control, &client_port, &session, + &audio_transport) == GST_RTSP_STS_OK); + + /* send PLAY request and check that we get 200 OK */ + fail_unless (do_simple_request_rangein (conn, GST_RTSP_PLAY, + session, "npt=0-") == GST_RTSP_STS_OK); + + /* let it play for a while, so it needs to seek + * for next play (npt=0-) */ + g_usleep (30000); + + /* send PLAY request and check that we get 200 OK */ + fail_unless (do_simple_request_rangein (conn, GST_RTSP_PLAY, + session, "npt=0-") == GST_RTSP_STS_OK); + + /* send TEARDOWN request and check that we get 200 OK */ + fail_unless (do_simple_request (conn, GST_RTSP_TEARDOWN, + session) == GST_RTSP_STS_OK); + + /* clean up and iterate so the clean-up can finish */ + g_free (session); + gst_rtsp_transport_free (video_transport); + gst_rtsp_transport_free (audio_transport); + gst_sdp_message_free (sdp_message); + gst_rtsp_connection_free (conn); + + stop_server (); + iterate (); +} + +GST_END_TEST; + + static Suite * rtspserver_suite (void) { @@ -2512,6 +2637,7 @@ rtspserver_suite (void) tcase_add_test (tc, test_record_tcp); tcase_add_test (tc, test_multiple_transports); tcase_add_test (tc, test_suspend_mode_reset_only_audio); + tcase_add_test (tc, test_double_play); return s; }